vue-model-contract-checker
v0.2.0
Published
Statically analyze Vue codebase to enforce v-model contract (no regular props for model props)
Maintainers
Readme
vue-model-contract-checker
Statically analyze a Vue codebase to ensure v-model contract compliance: any component prop that is part of a v-model contract is used only via v-model / v-model:prop, and never passed as a regular prop.
Inspired by vue-unused-components-checker: whole-project analysis, framework-aware, simple CLI.
Goal
- Components define a two-way binding contract via
defineModel(), orprops + emits (update:*). - Consumers must use
v-model/v-model:propfor those props, not:prop="value".
Installation (monorepo)
From the monorepo root:
yarn install # if the package was just added (may require lockfile update)No need to install the package separately; it’s part of the workspace.
Usage (monorepo)
From the monorepo root:
# Check the webapp package only (recommended)
yarn check-vmodel
# Check the whole repo (all packages with .vue files)
yarn check-vmodel:allFrom the checker package (paths relative to that package):
cd packages/vue-model-contract-checker
yarn build
yarn test # run tests (fixtures in test/fixtures/)
yarn exec node dist/index.js ../webapp # webapp only
yarn exec node dist/index.js ../.. # whole repoCLI options:
yarn check-vmodel -- --output-json # JSON output
yarn check-vmodel -- --stats # component/violation counts
yarn check-vmodel -- --debug # print component graph
yarn check-vmodel -- --no-fail-on-error # don’t exit 1 on violations
yarn check-vmodel -- --show-unresolved # list each unresolved import (default: only the count)Configuration
Optional config file at project root: vue-model-contract-checker.config.json
{
"include": ["src/**/*.vue"],
"exclude": ["**/node_modules/**", "**/dist/**"],
"extensions": [".vue"],
"failOnError": true
}Alias resolution (TypeScript paths + Vite)
Import aliases are resolved so that component paths match your project:
TypeScript / JavaScript – The checker looks for
tsconfig.jsonorjsconfig.jsonin the project root. If the root has nocompilerOptions.paths, it tries each entry inreferencesuntil one haspaths. Path mappings andbaseUrlare used to resolve non-relative.vueimports. JSONC comments are stripped before parsing.Vite – The checker also reads
resolve.aliasfromvite.config.ts/vite.config.js(or.mjs/.cjs). It parses the config file as text and supports:- Object form:
alias: { '@': path.join(__dirname, 'src'), '@ds': '@dilitrust/design-system/src' } - Array form:
alias: [ { find: '@', replacement: '...' } ] - Replacement values: string literals,
path.resolve/join(__dirname, '...'),fileURLToPath(new URL('...', import.meta.url)), and package-like strings (resolved fromnode_modules).
- Object form:
Resolution order: relative path → tsconfig paths → Vite alias. Non-relative .vue imports that cannot be resolved are reported as errors.
Violation example
Invalid (prop is a v-model contract):
<MyComp :modelValue="foo" />
<MyComp :title="foo" />Valid:
<MyComp v-model="foo" />
<MyComp v-model:title="foo" />To suppress checks for the next component opening (the line immediately below the comment), add a comment that contains vue-model-contract-checker-disable-next-component in the template:
- Disable all model props for that usage (default):
<!-- vue-model-contract-checker-disable-next-component --> - Disable only listed props (comma-separated, camelCase or kebab-case):
<!-- vue-model-contract-checker-disable-next-component:visible,title -->
Place the comment on the line directly above the component’s opening tag (multi-line tags are supported).
Output format
CLI:
[vue-model-contract-checker]
src/components/Parent.vue:12:10
❌ MyComp → prop "title" is a v-model contract
Use "v-model:title" instead of ":title"JSON (--output-json): object with violations and unresolvedImports arrays. Unresolved alias imports (no tsconfig path mapping) appear in unresolvedImports.
Architecture
- File Scanner – discovers
.vuefiles (include/exclude globs). - SFC Parser –
@vue/compiler-sfcto extract template and script. - Model extraction – from
defineModel(),defineModel('name'),defineEmits(['update:...']). - Component Registry – path → ComponentInfo (imports, modelProps).
- Template Analyzer –
@vue/compiler-domto parse template; for each component usage, flag:propwhenpropis in modelProps. - Reporter – CLI and optional JSON output.
Scope (MVP)
- Component resolution via relative imports and via TypeScript paths (tsconfig/jsconfig
paths+baseUrl). - Unknown components (e.g. from node_modules) are ignored.
Optional features (future)
--fix: rewrite:prop="x"tov-model:prop="x"where safe.- More emit patterns (e.g. Options API
emits: ['update:modelValue']).
License
MIT
