@craft-ng/dev-tools 0.5.1-beta.0 → 0.6.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/README.md +66 -0
  2. package/generators.json +26 -0
  3. package/package.json +18 -1
  4. package/src/bin/craft.d.ts +2 -0
  5. package/src/bin/craft.js +166 -0
  6. package/src/bin/craft.js.map +1 -0
  7. package/src/eslint-rules/craft-computed-name-match.cjs +8 -125
  8. package/src/eslint-rules/craft-method-name-match.cjs +8 -166
  9. package/src/eslint-rules/craft-name-match-utils.cjs +179 -0
  10. package/src/eslint-rules/craft-signal-source-name-match.cjs +8 -0
  11. package/src/eslint-rules/craft-source-name-match.cjs +8 -0
  12. package/src/eslint-rules/global-exception-registry-match.cjs +78 -13
  13. package/src/eslint-rules/index.cjs +8 -2
  14. package/src/eslint-rules/require-cascade-route-di-check.cjs +223 -0
  15. package/src/eslint-rules/require-primitive-generator-unwrap.cjs +267 -0
  16. package/src/generators/route/compat.d.ts +3 -0
  17. package/src/generators/route/compat.js +6 -0
  18. package/src/generators/route/compat.js.map +1 -0
  19. package/src/generators/route/generator.d.ts +31 -0
  20. package/src/generators/route/generator.js +434 -0
  21. package/src/generators/route/generator.js.map +1 -0
  22. package/src/generators/route/schema.json +50 -0
  23. package/src/generators/route/split-schema.json +19 -0
  24. package/src/index.d.ts +1 -0
  25. package/src/index.js +1 -0
  26. package/src/index.js.map +1 -1
  27. package/src/scripts/primitives/migrate-primitive-generators.d.ts +27 -0
  28. package/src/scripts/primitives/migrate-primitive-generators.js +315 -0
  29. package/src/scripts/primitives/migrate-primitive-generators.js.map +1 -0
  30. package/src/scripts/primitives/migrate-primitive-generators.ts +402 -0
  31. package/src/scripts/primitives/migrate-primitives.js +36 -4
  32. package/src/scripts/primitives/migrate-primitives.js.map +1 -1
  33. package/src/scripts/primitives/migrate-primitives.spec.ts +43 -0
  34. package/src/scripts/primitives/migrate-primitives.ts +62 -4
  35. package/src/scripts/primitives/migration-diagnostic.d.ts +1 -1
  36. package/src/scripts/primitives/migration-diagnostic.ts +1 -0
  37. package/src/scripts/routes/migrate-routes.js +4 -4
  38. package/src/scripts/routes/migrate-routes.js.map +1 -1
  39. package/src/scripts/routes/migrate-routes.ts +4 -6
  40. package/src/scripts/routes/route-command.d.ts +75 -0
  41. package/src/scripts/routes/route-command.js +1187 -0
  42. package/src/scripts/routes/route-command.js.map +1 -0
  43. package/src/scripts/routes/route-command.spec.ts +553 -0
  44. package/src/scripts/routes/route-command.ts +1790 -0
  45. package/src/scripts/services/migrate-services.js +45 -11
  46. package/src/scripts/services/migrate-services.js.map +1 -1
  47. package/src/scripts/services/migrate-services.spec.ts +46 -4
  48. package/src/scripts/services/migrate-services.ts +64 -13
  49. package/src/scripts/services/migration-diagnostic.d.ts +1 -1
  50. package/src/scripts/services/migration-diagnostic.ts +1 -0
  51. package/src/eslint-rules/require-track-on-dependent-primitives.cjs +0 -219
package/README.md CHANGED
@@ -147,6 +147,71 @@ The service migration also:
147
147
  statically resolvable component routes with `craftRoute`, generates or reuses
148
148
  their `GenDeps_*` type, and adds the file-level DI check.
149
149
 
150
+ For new routes, use the `craft` façade rather than the migration command:
151
+
152
+ ```bash
153
+ npx craft route add /users/:userId --component src/app/users/user-detail.ts#UserDetailComponent
154
+ npx craft route add /users/:userId --create-component users/user-detail
155
+ npx craft route add /legacy --redirect-to /users --parent src/app/app.routes.ts#appRoutes
156
+ npx craft route split --parent src/app/app.routes.ts#appRoutes --prefix users --target src/app/users/users.routes.ts
157
+ ```
158
+
159
+ The same route engine is also published as a native Nx generator and as an
160
+ Angular CLI schematic. Both use a virtual workspace tree, so their host CLI
161
+ provides project-name resolution and `--dry-run` without direct filesystem
162
+ writes:
163
+
164
+ ```bash
165
+ # Nx workspace
166
+ npx nx g @craft-ng/dev-tools:route /users/:userId \
167
+ --project=my-app \
168
+ --create-component=users/UserDetail
169
+
170
+ npx nx g @craft-ng/dev-tools:route-split \
171
+ --project=my-app \
172
+ --parent=apps/my-app/src/app/app.routes.ts#appRoutes \
173
+ --prefix=users \
174
+ --target=apps/my-app/src/app/users/users.routes.ts
175
+
176
+ # Angular CLI workspace
177
+ npx ng g @craft-ng/dev-tools:route /users/:userId \
178
+ --project=my-app \
179
+ --create-component=users/UserDetail
180
+ ```
181
+
182
+ Without route target options, the generator asks for the target kind. Component
183
+ creation then asks for the path relative to the application's `src/app` base,
184
+ followed by the component name. Before mutating the virtual tree it prints the
185
+ planned `CREATE` and `UPDATE` operations and asks for confirmation. Use `--yes`
186
+ for non-interactive automation; Nx/Angular `--dry-run` prints the preview without
187
+ asking for confirmation or writing files.
188
+
189
+ When `--parent` is omitted in an interactive terminal, the generator discovers
190
+ every `craftRoutes(...)` collection in the selected Angular project and lists
191
+ its exported routes name, source file, and prefix. Select `0` to retain route
192
+ path-based auto-detection, or choose a collection number. Scripted calls can
193
+ still pass `--parent=path/to/routes.ts#routesName` directly.
194
+
195
+ Pass `--skip-validation` when another task will run lint and the Angular build.
196
+ Otherwise validation runs after the virtual tree has been committed. Nx
197
+ workspaces compose the native `@nx/angular:component` generator; Angular CLI
198
+ workspaces compose the local `@schematics/angular:component` schematic. New
199
+ components use an inline template and inline styles, so the generator creates a
200
+ single component `.ts` file rather than separate `.ts`, `.html`, and `.css`
201
+ files. Component filenames are always normalized to kebab-case: a component
202
+ name such as `DemoPage` produces `demo-page.ts` while retaining the `DemoPage`
203
+ class name.
204
+
205
+ The add command defaults to a lazy routes file per feature and generates
206
+ `componentDeps`, `withRetry`, the cascade DI proof, exception assertion,
207
+ `.withParent`, and the parent mount assertion. Both commands print their plan
208
+ before writing. `--dry-run`, `--yes`, `--json`, `--project`, `--parent`, and
209
+ `--feature-file` support scripted usage. After writing, ESLint autofix and the
210
+ project TypeScript diagnostics run automatically; failed validation leaves the
211
+ changes in place and returns a non-zero exit code with diagnostics.
212
+ Component creation uses the local Angular CLI in an `angular.json` workspace,
213
+ or the local Nx Angular schematic in an `nx.json` workspace.
214
+
150
215
  Start with a dry run:
151
216
 
152
217
  ```bash
@@ -197,6 +262,7 @@ export default [
197
262
  'craft-ng/prefer-craft-http-client': 'error',
198
263
  'craft-ng/prefer-browser-boundaries': 'error',
199
264
  'craft-ng/require-lazy-load-with-retry': 'error',
265
+ 'craft-ng/require-cascade-route-di-check': 'error',
200
266
  }
201
267
  }
202
268
  ];
@@ -0,0 +1,26 @@
1
+ {
2
+ "generators": {
3
+ "route": {
4
+ "factory": "./src/generators/route/generator#routeGenerator",
5
+ "schema": "./src/generators/route/schema.json",
6
+ "description": "Add a type-safe Craft NG route."
7
+ },
8
+ "route-split": {
9
+ "factory": "./src/generators/route/generator#routeSplitGenerator",
10
+ "schema": "./src/generators/route/split-schema.json",
11
+ "description": "Split Craft NG routes into a lazy collection."
12
+ }
13
+ },
14
+ "schematics": {
15
+ "route": {
16
+ "factory": "./src/generators/route/compat#routeSchematic",
17
+ "schema": "./src/generators/route/schema.json",
18
+ "description": "Add a type-safe Craft NG route."
19
+ },
20
+ "route-split": {
21
+ "factory": "./src/generators/route/compat#routeSplitSchematic",
22
+ "schema": "./src/generators/route/split-schema.json",
23
+ "description": "Split Craft NG routes into a lazy collection."
24
+ }
25
+ }
26
+ }
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@craft-ng/dev-tools",
3
- "version": "0.5.1-beta.0",
3
+ "version": "0.6.0-beta.2",
4
4
  "description": "Development tools for ng-craft: ESLint configs, ESLint rules, and codemods",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/ng-angular-stack/ng-craft.git"
8
+ },
5
9
  "type": "module",
6
10
  "main": "./src/index.js",
7
11
  "types": "./src/index.d.ts",
@@ -16,7 +20,10 @@
16
20
  },
17
21
  "./package.json": "./package.json"
18
22
  },
23
+ "generators": "./generators.json",
24
+ "schematics": "./generators.json",
19
25
  "bin": {
26
+ "craft": "./src/bin/craft.js",
20
27
  "craft-brand": "./src/bin/craft-brand.js",
21
28
  "craft-migrate": "./src/bin/craft-migrate.js",
22
29
  "craft-migrate-primitives": "./src/bin/craft-migrate-primitives.js",
@@ -24,10 +31,12 @@
24
31
  "craft-migrate-routes": "./src/bin/craft-migrate-routes.js"
25
32
  },
26
33
  "files": [
34
+ "generators.json",
27
35
  "src/**/*",
28
36
  "README.md"
29
37
  ],
30
38
  "dependencies": {
39
+ "@nx/devkit": "^22.3.1",
31
40
  "tslib": "^2.3.0",
32
41
  "ts-morph": "^24.0.0",
33
42
  "ts-node": "^10.9.2",
@@ -35,6 +44,8 @@
35
44
  },
36
45
  "peerDependencies": {
37
46
  "@angular-eslint/eslint-plugin": ">=21.0.0",
47
+ "@nx/angular": ">=21.0.0 <24.0.0",
48
+ "@schematics/angular": ">=20.0.0 <23.0.0",
38
49
  "@typescript-eslint/eslint-plugin": ">=8.0.0",
39
50
  "eslint": ">=9.0.0"
40
51
  },
@@ -42,6 +53,12 @@
42
53
  "@angular-eslint/eslint-plugin": {
43
54
  "optional": true
44
55
  },
56
+ "@nx/angular": {
57
+ "optional": true
58
+ },
59
+ "@schematics/angular": {
60
+ "optional": true
61
+ },
45
62
  "@typescript-eslint/eslint-plugin": {
46
63
  "optional": true
47
64
  },
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import { __awaiter } from "tslib";
3
+ import { createInterface } from 'node:readline/promises';
4
+ import { stdin as input, stdout as output } from 'node:process';
5
+ import { listAngularProjects, runRouteAdd, runRouteSplit, } from '../scripts/routes/route-command.js';
6
+ function main(argv) {
7
+ return __awaiter(this, void 0, void 0, function* () {
8
+ var _a, _b;
9
+ if (argv[0] !== 'route' || !['add', 'split'].includes((_a = argv[1]) !== null && _a !== void 0 ? _a : '')) {
10
+ printHelp();
11
+ return argv.includes('--help') ? 0 : 1;
12
+ }
13
+ const command = argv[1];
14
+ const parsed = parseArgs(argv.slice(2));
15
+ if (parsed.help) {
16
+ printHelp();
17
+ return 0;
18
+ }
19
+ const common = {
20
+ rootDir: parsed.values['root'],
21
+ project: parsed.values['project'],
22
+ parent: parsed.values['parent'],
23
+ dryRun: parsed.flags.has('dry-run'),
24
+ yes: parsed.flags.has('yes'),
25
+ json: parsed.flags.has('json'),
26
+ };
27
+ const readline = createInterface({ input, output });
28
+ try {
29
+ if (!common.project && !common.yes) {
30
+ const projects = listAngularProjects(common.rootDir);
31
+ if (projects.length > 1) {
32
+ output.write(projects
33
+ .map((project, index) => `${index + 1}. ${project}`)
34
+ .join('\n') + '\n');
35
+ const selected = Number(yield readline.question('Angular project number: '));
36
+ common.project = projects[selected - 1];
37
+ if (!common.project)
38
+ throw new Error('Invalid project selection.');
39
+ }
40
+ }
41
+ const confirm = (_plan) => __awaiter(this, void 0, void 0, function* () { return /^y(?:es)?$/i.test(yield readline.question('Apply this plan? [y/N] ')); });
42
+ let result;
43
+ if (command === 'add') {
44
+ const options = Object.assign(Object.assign({}, common), { path: (_b = parsed.positionals[0]) !== null && _b !== void 0 ? _b : (yield readline.question('Route path: ')), component: parsed.values['component'], createComponent: parsed.values['create-component'], featureFile: parsed.values['feature-file'], redirectTo: parsed.values['redirect-to'], confirm });
45
+ if (!options.component &&
46
+ !options.createComponent &&
47
+ !options.redirectTo) {
48
+ const kind = (yield readline.question('Target: [e]xisting component, [c]reate component, [r]edirect? ')).toLowerCase();
49
+ if (kind.startsWith('e')) {
50
+ options.component = yield readline.question('Component <file#Class>: ');
51
+ }
52
+ else if (kind.startsWith('c')) {
53
+ options.createComponent = yield readline.question('Angular component name/path: ');
54
+ }
55
+ else if (kind.startsWith('r')) {
56
+ options.redirectTo = yield readline.question('Redirect target: ');
57
+ }
58
+ }
59
+ result = yield runRouteAdd(options);
60
+ }
61
+ else {
62
+ const parent = common.parent;
63
+ const prefix = parsed.values['prefix'];
64
+ const target = parsed.values['target'];
65
+ if (!parent || !prefix || !target) {
66
+ throw new Error('route split requires --parent, --prefix, and --target.');
67
+ }
68
+ const options = Object.assign(Object.assign({}, common), { parent,
69
+ prefix,
70
+ target,
71
+ confirm });
72
+ result = yield runRouteSplit(options);
73
+ }
74
+ printResult(result, common.json === true);
75
+ return result.exitCode;
76
+ }
77
+ finally {
78
+ readline.close();
79
+ }
80
+ });
81
+ }
82
+ function parseArgs(argv) {
83
+ const values = {};
84
+ const flags = new Set();
85
+ const positionals = [];
86
+ let help = false;
87
+ const valueOptions = new Set([
88
+ 'root',
89
+ 'project',
90
+ 'parent',
91
+ 'component',
92
+ 'create-component',
93
+ 'feature-file',
94
+ 'redirect-to',
95
+ 'prefix',
96
+ 'target',
97
+ ]);
98
+ const flagOptions = new Set(['dry-run', 'yes', 'json']);
99
+ for (let index = 0; index < argv.length; index += 1) {
100
+ const argument = argv[index];
101
+ if (argument === '--help' || argument === '-h') {
102
+ help = true;
103
+ }
104
+ else if (argument.startsWith('--')) {
105
+ const name = argument.slice(2);
106
+ if (valueOptions.has(name)) {
107
+ const value = argv[++index];
108
+ if (!value || value.startsWith('--')) {
109
+ throw new Error(`Missing value for --${name}.`);
110
+ }
111
+ values[name] = value;
112
+ }
113
+ else if (flagOptions.has(name)) {
114
+ flags.add(name);
115
+ }
116
+ else {
117
+ throw new Error(`Unknown option --${name}.`);
118
+ }
119
+ }
120
+ else {
121
+ positionals.push(argument);
122
+ }
123
+ }
124
+ return { values, flags, positionals, help };
125
+ }
126
+ function printResult(result, json) {
127
+ if (json) {
128
+ console.log(JSON.stringify(result, null, 2));
129
+ return;
130
+ }
131
+ for (const diagnostic of result.diagnostics) {
132
+ console.error(`${diagnostic.code}: ${diagnostic.message}`);
133
+ }
134
+ if (result.changedFiles.length > 0) {
135
+ console.log(`Changed:\n${result.changedFiles.map((file) => ` ${file}`).join('\n')}`);
136
+ }
137
+ }
138
+ function printHelp() {
139
+ console.log(`Usage:
140
+ craft route add [path] [options]
141
+ craft route split --parent <file#collection> --prefix <path> --target <file>
142
+
143
+ Options:
144
+ --root <dir> Workspace root (defaults to cwd)
145
+ --project <name|tsconfig> Angular project or tsconfig
146
+ --parent <file#collection> Parent craftRoutes collection
147
+ --component <file#Class> Existing routed component
148
+ --create-component <name> Generate with the local Angular CLI or Nx
149
+ --feature-file <file> Create/use an explicit lazy feature collection
150
+ --redirect-to <path> Add a static redirect
151
+ --prefix <path> Static prefix moved by route split
152
+ --target <file> New lazy collection written by route split
153
+ --dry-run Print the plan without writing
154
+ --yes Apply without confirmation
155
+ --json Emit machine-readable output
156
+ `);
157
+ }
158
+ main(process.argv.slice(2))
159
+ .then((exitCode) => {
160
+ process.exitCode = exitCode;
161
+ })
162
+ .catch((error) => {
163
+ console.error(error instanceof Error ? error.message : error);
164
+ process.exitCode = 1;
165
+ });
166
+ //# sourceMappingURL=craft.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"craft.js","sourceRoot":"","sources":["../../../../../libs/dev-tools/src/bin/craft.ts"],"names":[],"mappings":";;AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,cAAc,CAAC;AAChE,OAAO,EACL,mBAAmB,EACnB,WAAW,EACX,aAAa,GAKd,MAAM,oCAAoC,CAAC;AAW5C,SAAe,IAAI,CAAC,IAAc;;;QAChC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO,IAAI,CAAC,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAA,IAAI,CAAC,CAAC,CAAC,mCAAI,EAAE,CAAC,EAAE,CAAC;YACrE,SAAS,EAAE,CAAC;YACZ,OAAO,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzC,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACxB,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QACxC,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,SAAS,EAAE,CAAC;YACZ,OAAO,CAAC,CAAC;QACX,CAAC;QACD,MAAM,MAAM,GAAkB;YAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC;YAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC;YACjC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;YAC/B,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC;YACnC,GAAG,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC;YAC5B,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC;SAC/B,CAAC;QAEF,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACpD,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC;gBACnC,MAAM,QAAQ,GAAG,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;gBACrD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACxB,MAAM,CAAC,KAAK,CACV,QAAQ;yBACL,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,CAAC,GAAG,KAAK,GAAG,CAAC,KAAK,OAAO,EAAE,CAAC;yBACnD,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CACrB,CAAC;oBACF,MAAM,QAAQ,GAAG,MAAM,CACrB,MAAM,QAAQ,CAAC,QAAQ,CAAC,0BAA0B,CAAC,CACpD,CAAC;oBACF,MAAM,CAAC,OAAO,GAAG,QAAQ,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;oBACxC,IAAI,CAAC,MAAM,CAAC,OAAO;wBAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;gBACrE,CAAC;YACH,CAAC;YACD,MAAM,OAAO,GAAG,CAAO,KAAuB,EAAE,EAAE,gDAChD,OAAA,aAAa,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,yBAAyB,CAAC,CAAC,CAAA,GAAA,CAAC;YACzE,IAAI,MAA0B,CAAC;YAC/B,IAAI,OAAO,KAAK,KAAK,EAAE,CAAC;gBACtB,MAAM,OAAO,mCACR,MAAM,KACT,IAAI,EACF,MAAA,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,mCAAI,CAAC,MAAM,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC,EACpE,SAAS,EAAE,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC,EACrC,eAAe,EAAE,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC,EAClD,WAAW,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,EAC1C,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,EACxC,OAAO,GACR,CAAC;gBACF,IACE,CAAC,OAAO,CAAC,SAAS;oBAClB,CAAC,OAAO,CAAC,eAAe;oBACxB,CAAC,OAAO,CAAC,UAAU,EACnB,CAAC;oBACD,MAAM,IAAI,GAAG,CACX,MAAM,QAAQ,CAAC,QAAQ,CACrB,gEAAgE,CACjE,CACF,CAAC,WAAW,EAAE,CAAC;oBAChB,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;wBACzB,OAAO,CAAC,SAAS,GAAG,MAAM,QAAQ,CAAC,QAAQ,CACzC,0BAA0B,CAC3B,CAAC;oBACJ,CAAC;yBAAM,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;wBAChC,OAAO,CAAC,eAAe,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAC/C,+BAA+B,CAChC,CAAC;oBACJ,CAAC;yBAAM,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;wBAChC,OAAO,CAAC,UAAU,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;oBACpE,CAAC;gBACH,CAAC;gBACD,MAAM,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;YACtC,CAAC;iBAAM,CAAC;gBACN,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;gBAC7B,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACvC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBACvC,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC;oBAClC,MAAM,IAAI,KAAK,CACb,wDAAwD,CACzD,CAAC;gBACJ,CAAC;gBACD,MAAM,OAAO,mCACR,MAAM,KACT,MAAM;oBACN,MAAM;oBACN,MAAM;oBACN,OAAO,GACR,CAAC;gBACF,MAAM,GAAG,MAAM,aAAa,CAAC,OAAO,CAAC,CAAC;YACxC,CAAC;YACD,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YAC1C,OAAO,MAAM,CAAC,QAAQ,CAAC;QACzB,CAAC;gBAAS,CAAC;YACT,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;CAAA;AAED,SAAS,SAAS,CAAC,IAAc;IAC/B,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,MAAM,WAAW,GAAa,EAAE,CAAC;IACjC,IAAI,IAAI,GAAG,KAAK,CAAC;IACjB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC;QAC3B,MAAM;QACN,SAAS;QACT,QAAQ;QACR,WAAW;QACX,kBAAkB;QAClB,cAAc;QACd,aAAa;QACb,QAAQ;QACR,QAAQ;KACT,CAAC,CAAC;IACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;IACxD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACpD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;QAC7B,IAAI,QAAQ,KAAK,QAAQ,IAAI,QAAQ,KAAK,IAAI,EAAE,CAAC;YAC/C,IAAI,GAAG,IAAI,CAAC;QACd,CAAC;aAAM,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAC/B,IAAI,YAAY,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC3B,MAAM,KAAK,GAAG,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;gBAC5B,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;oBACrC,MAAM,IAAI,KAAK,CAAC,uBAAuB,IAAI,GAAG,CAAC,CAAC;gBAClD,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;YACvB,CAAC;iBAAM,IAAI,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACjC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAClB,CAAC;iBAAM,CAAC;gBACN,MAAM,IAAI,KAAK,CAAC,oBAAoB,IAAI,GAAG,CAAC,CAAC;YAC/C,CAAC;QACH,CAAC;aAAM,CAAC;YACN,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;AAC9C,CAAC;AAED,SAAS,WAAW,CAAC,MAA0B,EAAE,IAAa;IAC5D,IAAI,IAAI,EAAE,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7C,OAAO;IACT,CAAC;IACD,KAAK,MAAM,UAAU,IAAI,MAAM,CAAC,WAAW,EAAE,CAAC;QAC5C,OAAO,CAAC,KAAK,CAAC,GAAG,UAAU,CAAC,IAAI,KAAK,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,MAAM,CAAC,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACnC,OAAO,CAAC,GAAG,CACT,aAAa,MAAM,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CACzE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,CAAC,GAAG,CAAC;;;;;;;;;;;;;;;;;CAiBb,CAAC,CAAC;AACH,CAAC;AAED,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;KACxB,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;IACjB,OAAO,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC9B,CAAC,CAAC;KACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IACxB,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAC9D,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC"}
@@ -1,125 +1,8 @@
1
- module.exports = {
2
- meta: {
3
- type: 'problem',
4
- docs: {
5
- description:
6
- "Ensure craftComputed(name, ...) is called with a string literal first argument that matches the declared variable or class property name.",
7
- },
8
- fixable: 'code',
9
- schema: [],
10
- messages: {
11
- missingName:
12
- "craftComputed must be called with a string literal name matching '{{declaredName}}' as the first argument.",
13
- mismatchedName:
14
- "craftComputed first argument '{{actual}}' must match the declared name '{{declaredName}}'.",
15
- },
16
- },
17
- create(context) {
18
- const sourceCode = context.sourceCode ?? context.getSourceCode();
19
-
20
- return {
21
- CallExpression(node) {
22
- if (
23
- node.callee.type !== 'Identifier' ||
24
- node.callee.name !== 'craftComputed'
25
- ) {
26
- return;
27
- }
28
-
29
- const declaredName = getDeclaredName(node);
30
- if (!declaredName) {
31
- return;
32
- }
33
-
34
- const firstArg = node.arguments[0];
35
-
36
- if (!firstArg) {
37
- context.report({
38
- node,
39
- messageId: 'missingName',
40
- data: { declaredName },
41
- fix(fixer) {
42
- const openParen = sourceCode.getTokenAfter(
43
- node.callee,
44
- (token) => token.type === 'Punctuator' && token.value === '(',
45
- );
46
- if (!openParen) return null;
47
- return fixer.insertTextAfter(openParen, `'${declaredName}'`);
48
- },
49
- });
50
- return;
51
- }
52
-
53
- if (isStringLiteral(firstArg)) {
54
- const actual = getStringLiteralValue(firstArg);
55
- if (actual === declaredName) {
56
- return;
57
- }
58
- context.report({
59
- node: firstArg,
60
- messageId: 'mismatchedName',
61
- data: { declaredName, actual },
62
- fix(fixer) {
63
- return fixer.replaceText(firstArg, `'${declaredName}'`);
64
- },
65
- });
66
- return;
67
- }
68
-
69
- context.report({
70
- node: firstArg,
71
- messageId: 'missingName',
72
- data: { declaredName },
73
- fix(fixer) {
74
- return fixer.insertTextBefore(firstArg, `'${declaredName}', `);
75
- },
76
- });
77
- },
78
- };
79
- },
80
- };
81
-
82
- function getDeclaredName(callNode) {
83
- const parent = callNode.parent;
84
- if (!parent) return undefined;
85
-
86
- if (
87
- parent.type === 'VariableDeclarator' &&
88
- parent.init === callNode &&
89
- parent.id.type === 'Identifier'
90
- ) {
91
- return parent.id.name;
92
- }
93
-
94
- if (
95
- parent.type === 'PropertyDefinition' &&
96
- parent.value === callNode &&
97
- !parent.computed &&
98
- parent.key.type === 'Identifier'
99
- ) {
100
- return parent.key.name;
101
- }
102
-
103
- return undefined;
104
- }
105
-
106
- function isStringLiteral(node) {
107
- if (node.type === 'Literal' && typeof node.value === 'string') {
108
- return true;
109
- }
110
- if (
111
- node.type === 'TemplateLiteral' &&
112
- node.expressions.length === 0 &&
113
- node.quasis.length === 1
114
- ) {
115
- return true;
116
- }
117
- return false;
118
- }
119
-
120
- function getStringLiteralValue(node) {
121
- if (node.type === 'Literal') {
122
- return node.value;
123
- }
124
- return node.quasis[0].value.cooked;
125
- }
1
+ const { createNameMatchRule } = require('./craft-name-match-utils.cjs');
2
+
3
+ module.exports = createNameMatchRule({
4
+ calleeName: 'craftComputed',
5
+ description:
6
+ "Ensure craftComputed(name, ...) is called with a string literal first argument that matches the declared variable or class property name.",
7
+ supportsObjectConfigForm: false,
8
+ });
@@ -1,166 +1,8 @@
1
- module.exports = {
2
- meta: {
3
- type: 'problem',
4
- docs: {
5
- description:
6
- "Ensure craftMethod(name, ...) is called with a string literal first argument (or { name } object) that matches the declared variable or class property name.",
7
- },
8
- fixable: 'code',
9
- schema: [],
10
- messages: {
11
- missingName:
12
- "craftMethod must be called with a string literal name matching '{{declaredName}}' as the first argument.",
13
- mismatchedName:
14
- "craftMethod first argument '{{actual}}' must match the declared name '{{declaredName}}'.",
15
- },
16
- },
17
- create(context) {
18
- const sourceCode = context.sourceCode ?? context.getSourceCode();
19
-
20
- return {
21
- CallExpression(node) {
22
- if (
23
- node.callee.type !== 'Identifier' ||
24
- node.callee.name !== 'craftMethod'
25
- ) {
26
- return;
27
- }
28
-
29
- const declaredName = getDeclaredName(node);
30
- if (!declaredName) {
31
- return;
32
- }
33
-
34
- const firstArg = node.arguments[0];
35
-
36
- if (!firstArg) {
37
- context.report({
38
- node,
39
- messageId: 'missingName',
40
- data: { declaredName },
41
- fix(fixer) {
42
- const openParen = sourceCode.getTokenAfter(
43
- node.callee,
44
- (token) => token.type === 'Punctuator' && token.value === '(',
45
- );
46
- if (!openParen) return null;
47
- return fixer.insertTextAfter(openParen, `'${declaredName}'`);
48
- },
49
- });
50
- return;
51
- }
52
-
53
- // Handle object config form: { name: 'methodName', providers: [...] }
54
- if (firstArg.type === 'ObjectExpression') {
55
- const nameProp = firstArg.properties.find(
56
- (p) =>
57
- p.type === 'Property' &&
58
- !p.computed &&
59
- p.key.type === 'Identifier' &&
60
- p.key.name === 'name',
61
- );
62
- if (!nameProp) {
63
- context.report({
64
- node: firstArg,
65
- messageId: 'missingName',
66
- data: { declaredName },
67
- });
68
- return;
69
- }
70
- const nameValue = nameProp.value;
71
- if (isStringLiteral(nameValue)) {
72
- const actual = getStringLiteralValue(nameValue);
73
- if (actual === declaredName) {
74
- return;
75
- }
76
- context.report({
77
- node: nameValue,
78
- messageId: 'mismatchedName',
79
- data: { declaredName, actual },
80
- fix(fixer) {
81
- return fixer.replaceText(nameValue, `'${declaredName}'`);
82
- },
83
- });
84
- } else {
85
- context.report({
86
- node: nameValue,
87
- messageId: 'mismatchedName',
88
- data: { declaredName, actual: sourceCode.getText(nameValue) },
89
- });
90
- }
91
- return;
92
- }
93
-
94
- if (isStringLiteral(firstArg)) {
95
- const actual = getStringLiteralValue(firstArg);
96
- if (actual === declaredName) {
97
- return;
98
- }
99
- context.report({
100
- node: firstArg,
101
- messageId: 'mismatchedName',
102
- data: { declaredName, actual },
103
- fix(fixer) {
104
- return fixer.replaceText(firstArg, `'${declaredName}'`);
105
- },
106
- });
107
- return;
108
- }
109
-
110
- context.report({
111
- node: firstArg,
112
- messageId: 'missingName',
113
- data: { declaredName },
114
- fix(fixer) {
115
- return fixer.insertTextBefore(firstArg, `'${declaredName}', `);
116
- },
117
- });
118
- },
119
- };
120
- },
121
- };
122
-
123
- function getDeclaredName(callNode) {
124
- const parent = callNode.parent;
125
- if (!parent) return undefined;
126
-
127
- if (
128
- parent.type === 'VariableDeclarator' &&
129
- parent.init === callNode &&
130
- parent.id.type === 'Identifier'
131
- ) {
132
- return parent.id.name;
133
- }
134
-
135
- if (
136
- parent.type === 'PropertyDefinition' &&
137
- parent.value === callNode &&
138
- !parent.computed &&
139
- parent.key.type === 'Identifier'
140
- ) {
141
- return parent.key.name;
142
- }
143
-
144
- return undefined;
145
- }
146
-
147
- function isStringLiteral(node) {
148
- if (node.type === 'Literal' && typeof node.value === 'string') {
149
- return true;
150
- }
151
- if (
152
- node.type === 'TemplateLiteral' &&
153
- node.expressions.length === 0 &&
154
- node.quasis.length === 1
155
- ) {
156
- return true;
157
- }
158
- return false;
159
- }
160
-
161
- function getStringLiteralValue(node) {
162
- if (node.type === 'Literal') {
163
- return node.value;
164
- }
165
- return node.quasis[0].value.cooked;
166
- }
1
+ const { createNameMatchRule } = require('./craft-name-match-utils.cjs');
2
+
3
+ module.exports = createNameMatchRule({
4
+ calleeName: 'craftMethod',
5
+ description:
6
+ "Ensure craftMethod(name, ...) is called with a string literal first argument (or { name } object) that matches the declared variable or class property name.",
7
+ supportsObjectConfigForm: true,
8
+ });