@craft-ng/dev-tools 0.1.0
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.
- package/README.md +324 -0
- package/package.json +55 -0
- package/src/bin/craft-brand.d.ts +2 -0
- package/src/bin/craft-brand.js +9 -0
- package/src/bin/craft-brand.js.map +1 -0
- package/src/eslint/angular.mjs +26 -0
- package/src/eslint/index.mjs +18 -0
- package/src/eslint-rules/brand-angular-deps-match.cjs +193 -0
- package/src/eslint-rules/index.cjs +12 -0
- package/src/eslint-rules/no-angular-inject.cjs +141 -0
- package/src/eslint-rules/no-direct-angular-class-export.cjs +234 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +3 -0
- package/src/index.js.map +1 -0
- package/src/scripts/angular-brand-codemod.d.ts +75 -0
- package/src/scripts/angular-brand-codemod.js +1124 -0
- package/src/scripts/angular-brand-codemod.js.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# @craft-ng/dev-tools
|
|
2
|
+
|
|
3
|
+
Development tools for ng-craft: ESLint configs, ESLint rules, and codemods.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
### Depuis npm (après publication)
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -D @craft-ng/dev-tools
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Publication sur npm
|
|
14
|
+
|
|
15
|
+
Pour publier le package sur npm (mainteneurs uniquement) :
|
|
16
|
+
|
|
17
|
+
#### 1. Se connecter à npm
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm login
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Vous aurez besoin d'un compte npm avec les droits de publication sur le scope `@craft-ng`.
|
|
24
|
+
|
|
25
|
+
#### 2. Builder le package
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npx nx build dev-tools
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
#### 3. Mettre à jour la version
|
|
32
|
+
|
|
33
|
+
Modifiez la version dans `libs/dev-tools/package.json` selon [semver](https://semver.org/) :
|
|
34
|
+
|
|
35
|
+
- **Patch** (0.1.0 → 0.1.1) : Corrections de bugs
|
|
36
|
+
- **Minor** (0.1.0 → 0.2.0) : Nouvelles fonctionnalités rétrocompatibles
|
|
37
|
+
- **Major** (0.1.0 → 1.0.0) : Changements non rétrocompatibles
|
|
38
|
+
|
|
39
|
+
#### 4. Publier
|
|
40
|
+
|
|
41
|
+
Depuis le répertoire de build :
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cd dist/libs/dev-tools
|
|
45
|
+
npm publish --access public
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> **Note** : L'option `--access public` est nécessaire pour les packages scoped (`@craft-ng/...`) si vous souhaitez les rendre publics.
|
|
49
|
+
|
|
50
|
+
#### 5. Créer un tag Git (recommandé)
|
|
51
|
+
|
|
52
|
+
Retournez à la racine et créez un tag :
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cd ../../..
|
|
56
|
+
git tag dev-tools-v0.1.0
|
|
57
|
+
git push origin dev-tools-v0.1.0
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### En développement local (avant publication)
|
|
61
|
+
|
|
62
|
+
Pour tester ce package dans un autre projet avant de le publier sur npm :
|
|
63
|
+
|
|
64
|
+
#### 1. Builder le package
|
|
65
|
+
|
|
66
|
+
Depuis la racine du monorepo ng-craft :
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
npx nx build dev-tools
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Cela génère les fichiers compilés dans `dist/libs/dev-tools/`.
|
|
73
|
+
|
|
74
|
+
#### 2. Créer un lien npm local
|
|
75
|
+
|
|
76
|
+
Depuis la racine du monorepo :
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
cd dist/libs/dev-tools
|
|
80
|
+
npm link
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### 3. Utiliser le lien dans votre autre projet
|
|
84
|
+
|
|
85
|
+
Dans votre projet Angular externe :
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
cd /chemin/vers/votre-projet
|
|
89
|
+
npm link @craft-ng/dev-tools
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
> **Note** : Après chaque modification du code de `@craft-ng/dev-tools`, vous devez rebuilder avec `npx nx build dev-tools` pour que les changements soient pris en compte dans les projets liés.
|
|
93
|
+
|
|
94
|
+
#### 4. Supprimer le lien (quand terminé)
|
|
95
|
+
|
|
96
|
+
Dans votre projet externe :
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm unlink @craft-ng/dev-tools
|
|
100
|
+
npm install
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## Configuration complète dans un nouveau projet
|
|
104
|
+
|
|
105
|
+
Voici un exemple de configuration complète pour utiliser tous les outils dans un projet Angular :
|
|
106
|
+
|
|
107
|
+
### 1. Installer les dépendances
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
npm install -D @craft-ng/dev-tools @craft-ng/core
|
|
111
|
+
npm install -D @angular-eslint/eslint-plugin @typescript-eslint/eslint-plugin eslint
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### 2. Configurer ESLint
|
|
115
|
+
|
|
116
|
+
Créez ou modifiez `eslint.config.mjs` :
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
import craftAngular from '@craft-ng/dev-tools/eslint/angular';
|
|
120
|
+
import craftRules from '@craft-ng/dev-tools/eslint-rules';
|
|
121
|
+
|
|
122
|
+
export default [
|
|
123
|
+
{
|
|
124
|
+
ignores: ['dist/**', 'node_modules/**'],
|
|
125
|
+
},
|
|
126
|
+
...craftAngular,
|
|
127
|
+
{
|
|
128
|
+
files: ['**/*.ts'],
|
|
129
|
+
plugins: {
|
|
130
|
+
'@craft-ng': craftRules,
|
|
131
|
+
},
|
|
132
|
+
rules: {
|
|
133
|
+
// Règles craft-ng recommandées
|
|
134
|
+
'@craft-ng/brand-angular-deps-match': 'error',
|
|
135
|
+
'@craft-ng/no-angular-inject': 'error',
|
|
136
|
+
'@craft-ng/no-direct-angular-class-export': 'error',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 3. Transformer votre code existant
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# Prévisualiser les changements (dry-run)
|
|
146
|
+
npx craft-brand --root ./src --dry-run
|
|
147
|
+
|
|
148
|
+
# Appliquer les transformations
|
|
149
|
+
npx craft-brand --root ./src
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 4. Ajouter des scripts npm
|
|
153
|
+
|
|
154
|
+
Dans votre `package.json` :
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"scripts": {
|
|
159
|
+
"lint": "eslint .",
|
|
160
|
+
"lint:fix": "eslint . --fix",
|
|
161
|
+
"codemod:preview": "craft-brand --dry-run",
|
|
162
|
+
"codemod:apply": "craft-brand"
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Features
|
|
168
|
+
|
|
169
|
+
### 1. ESLint Configurations
|
|
170
|
+
|
|
171
|
+
Pre-configured ESLint settings for ng-craft projects.
|
|
172
|
+
|
|
173
|
+
#### Basic TypeScript Config
|
|
174
|
+
|
|
175
|
+
```javascript
|
|
176
|
+
// eslint.config.mjs
|
|
177
|
+
import craftEslint from '@craft-ng/dev-tools/eslint';
|
|
178
|
+
|
|
179
|
+
export default [...craftEslint];
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
#### Angular Config
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
// eslint.config.mjs
|
|
186
|
+
import craftAngular from '@craft-ng/dev-tools/eslint/angular';
|
|
187
|
+
|
|
188
|
+
export default [...craftAngular];
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### 2. ESLint Rules
|
|
192
|
+
|
|
193
|
+
Custom ESLint rules for enforcing ng-craft patterns.
|
|
194
|
+
|
|
195
|
+
#### Available Rules
|
|
196
|
+
|
|
197
|
+
- **`brand-angular-deps-match`** - Ensures `deps({ injected, importDeps, providers })` matches the Angular symbol dependencies
|
|
198
|
+
- **`no-angular-inject`** - Disallows Angular `inject()`, `@Injectable`, and `@Service` usage
|
|
199
|
+
- **`no-direct-angular-class-export`** - Disallows direct exports of Angular symbols managed through `brandAngularSymbol`
|
|
200
|
+
|
|
201
|
+
#### Usage
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
// eslint.config.mjs
|
|
205
|
+
import craftAngular from '@craft-ng/dev-tools/eslint/angular';
|
|
206
|
+
import craftRules from '@craft-ng/dev-tools/eslint-rules';
|
|
207
|
+
|
|
208
|
+
export default [
|
|
209
|
+
...craftAngular,
|
|
210
|
+
{
|
|
211
|
+
plugins: {
|
|
212
|
+
'@craft-ng': craftRules,
|
|
213
|
+
},
|
|
214
|
+
rules: {
|
|
215
|
+
'@craft-ng/brand-angular-deps-match': 'error',
|
|
216
|
+
'@craft-ng/no-angular-inject': 'error',
|
|
217
|
+
'@craft-ng/no-direct-angular-class-export': 'error',
|
|
218
|
+
},
|
|
219
|
+
},
|
|
220
|
+
];
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
### 3. Codemod: `craft-brand`
|
|
224
|
+
|
|
225
|
+
Automatically transforms Angular components, directives, pipes, and injectables to use `brandAngularSymbol` and `deps()`.
|
|
226
|
+
|
|
227
|
+
#### CLI Usage
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
# Transform all TypeScript files in the current directory
|
|
231
|
+
npx craft-brand
|
|
232
|
+
|
|
233
|
+
# Transform with specific root directory
|
|
234
|
+
npx craft-brand --root ./src
|
|
235
|
+
|
|
236
|
+
# Dry run (preview changes without writing)
|
|
237
|
+
npx craft-brand --dry-run
|
|
238
|
+
|
|
239
|
+
# Only transform standalone components/directives
|
|
240
|
+
npx craft-brand --transform-only-standalone-declarables
|
|
241
|
+
|
|
242
|
+
# Custom helper import path
|
|
243
|
+
npx craft-brand --helper-import @my-lib/core
|
|
244
|
+
|
|
245
|
+
# Use specific tsconfig
|
|
246
|
+
npx craft-brand --tsconfig ./tsconfig.app.json
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
#### Programmatic Usage
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import { runAngularBrandCodemod } from '@craft-ng/dev-tools';
|
|
253
|
+
|
|
254
|
+
await runAngularBrandCodemod({
|
|
255
|
+
rootDir: './src',
|
|
256
|
+
dryRun: true,
|
|
257
|
+
transformOnlyStandaloneDeclarables: false,
|
|
258
|
+
includeProviders: true,
|
|
259
|
+
includeViewProviders: true,
|
|
260
|
+
helperImportPath: '@craft-ng/core',
|
|
261
|
+
log: console.log,
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
#### What it does
|
|
266
|
+
|
|
267
|
+
Transforms this:
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
import { Component, inject } from '@angular/core';
|
|
271
|
+
|
|
272
|
+
@Component({
|
|
273
|
+
standalone: true,
|
|
274
|
+
imports: [CommonModule],
|
|
275
|
+
providers: [MyService],
|
|
276
|
+
})
|
|
277
|
+
export class DemoComponent {
|
|
278
|
+
private api = inject(ApiService);
|
|
279
|
+
constructor(private http: HttpClient) {}
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Into this:
|
|
284
|
+
|
|
285
|
+
```typescript
|
|
286
|
+
import { Component, inject } from '@angular/core';
|
|
287
|
+
import { brandAngularSymbol, deps } from '@craft-ng/core';
|
|
288
|
+
|
|
289
|
+
@Component({
|
|
290
|
+
standalone: true,
|
|
291
|
+
imports: [CommonModule],
|
|
292
|
+
providers: [MyService],
|
|
293
|
+
})
|
|
294
|
+
class DemoComponent {
|
|
295
|
+
private api = inject(ApiService);
|
|
296
|
+
constructor(private http: HttpClient) {}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export default brandAngularSymbol(
|
|
300
|
+
DemoComponent,
|
|
301
|
+
deps({
|
|
302
|
+
injected: [HttpClient, ApiService],
|
|
303
|
+
importDeps: [CommonModule],
|
|
304
|
+
providers: [MyService],
|
|
305
|
+
}),
|
|
306
|
+
);
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
#### CLI Options
|
|
310
|
+
|
|
311
|
+
| Option | Description | Default |
|
|
312
|
+
| ----------------------------------------- | -------------------------------------------------- | ---------------------- |
|
|
313
|
+
| `--root <dir>` | Project root directory | `cwd` |
|
|
314
|
+
| `--tsconfig <path>` | Path to tsconfig.json | `<root>/tsconfig.json` |
|
|
315
|
+
| `--helper-import <path>` | Import path for `brandAngularSymbol` and `deps` | `@craft-ng/core` |
|
|
316
|
+
| `--transform-only-standalone-declarables` | Only transform standalone components/directives | `false` |
|
|
317
|
+
| `--no-providers` | Do not include metadata providers in `deps()` | `false` |
|
|
318
|
+
| `--no-view-providers` | Do not include component viewProviders in `deps()` | `false` |
|
|
319
|
+
| `--dry-run` | Print results without writing files | `false` |
|
|
320
|
+
| `--help` | Show help | - |
|
|
321
|
+
|
|
322
|
+
## License
|
|
323
|
+
|
|
324
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@craft-ng/dev-tools",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Development tools for ng-craft: ESLint configs, ESLint rules, and codemods",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"types": "./src/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./src/index.d.ts",
|
|
11
|
+
"default": "./src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./eslint": "./src/eslint/index.mjs",
|
|
14
|
+
"./eslint/angular": "./src/eslint/angular.mjs",
|
|
15
|
+
"./eslint-rules": "./src/eslint-rules/index.cjs",
|
|
16
|
+
"./package.json": "./package.json"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"craft-brand": "./src/bin/craft-brand.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"src/**/*",
|
|
23
|
+
"README.md"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"tslib": "^2.3.0",
|
|
27
|
+
"ts-morph": "^24.0.0"
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@angular-eslint/eslint-plugin": ">=21.0.0",
|
|
31
|
+
"@typescript-eslint/eslint-plugin": ">=8.0.0",
|
|
32
|
+
"eslint": ">=9.0.0"
|
|
33
|
+
},
|
|
34
|
+
"peerDependenciesMeta": {
|
|
35
|
+
"@angular-eslint/eslint-plugin": {
|
|
36
|
+
"optional": true
|
|
37
|
+
},
|
|
38
|
+
"@typescript-eslint/eslint-plugin": {
|
|
39
|
+
"optional": true
|
|
40
|
+
},
|
|
41
|
+
"eslint": {
|
|
42
|
+
"optional": true
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"ng-craft",
|
|
47
|
+
"angular",
|
|
48
|
+
"codemod",
|
|
49
|
+
"eslint",
|
|
50
|
+
"eslint-config",
|
|
51
|
+
"eslint-plugin",
|
|
52
|
+
"typescript"
|
|
53
|
+
],
|
|
54
|
+
"module": "./src/index.js"
|
|
55
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { runAngularBrandCodemod } from '../scripts/angular-brand-codemod.js';
|
|
3
|
+
runAngularBrandCodemod({
|
|
4
|
+
log: console.log,
|
|
5
|
+
}).catch((error) => {
|
|
6
|
+
console.error('Error running codemod:', error);
|
|
7
|
+
process.exit(1);
|
|
8
|
+
});
|
|
9
|
+
//# sourceMappingURL=craft-brand.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"craft-brand.js","sourceRoot":"","sources":["../../../../../libs/dev-tools/src/bin/craft-brand.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAE7E,sBAAsB,CAAC;IACrB,GAAG,EAAE,OAAO,CAAC,GAAG;CACjB,CAAC,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;IACjB,OAAO,CAAC,KAAK,CAAC,wBAAwB,EAAE,KAAK,CAAC,CAAC;IAC/C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import baseConfig from './index.mjs';
|
|
2
|
+
|
|
3
|
+
export default [
|
|
4
|
+
...baseConfig,
|
|
5
|
+
{
|
|
6
|
+
files: ['**/*.ts'],
|
|
7
|
+
rules: {
|
|
8
|
+
'@angular-eslint/directive-selector': [
|
|
9
|
+
'error',
|
|
10
|
+
{
|
|
11
|
+
type: 'attribute',
|
|
12
|
+
prefix: 'app',
|
|
13
|
+
style: 'camelCase',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
'@angular-eslint/component-selector': [
|
|
17
|
+
'error',
|
|
18
|
+
{
|
|
19
|
+
type: 'element',
|
|
20
|
+
prefix: 'app',
|
|
21
|
+
style: 'kebab-case',
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
];
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export default [
|
|
2
|
+
{
|
|
3
|
+
files: ['**/*.ts'],
|
|
4
|
+
rules: {
|
|
5
|
+
'@typescript-eslint/no-unused-vars': [
|
|
6
|
+
'warn',
|
|
7
|
+
{
|
|
8
|
+
argsIgnorePattern: '^_',
|
|
9
|
+
varsIgnorePattern: '^_',
|
|
10
|
+
caughtErrorsIgnorePattern: '^_',
|
|
11
|
+
},
|
|
12
|
+
],
|
|
13
|
+
'no-empty-object-type': 'off',
|
|
14
|
+
'@typescript-eslint/no-empty-object-type': 'off',
|
|
15
|
+
'@typescript-eslint/no-unsafe-function-type': 'off',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
];
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
const fs = require('node:fs');
|
|
2
|
+
const path = require('node:path');
|
|
3
|
+
|
|
4
|
+
const { Project } = require('ts-morph');
|
|
5
|
+
const {
|
|
6
|
+
analyzeSourceFileDependencies,
|
|
7
|
+
readExistingDependencyGroups,
|
|
8
|
+
} = require('../scripts/angular-brand-codemod.js');
|
|
9
|
+
|
|
10
|
+
const projectCache = new Map();
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'problem',
|
|
15
|
+
docs: {
|
|
16
|
+
description:
|
|
17
|
+
'Ensure deps({ injected, importDeps, providers }) matches the Angular symbol dependencies.',
|
|
18
|
+
},
|
|
19
|
+
schema: [],
|
|
20
|
+
},
|
|
21
|
+
create(context) {
|
|
22
|
+
return {
|
|
23
|
+
'Program:exit'() {
|
|
24
|
+
const sourceCode = context.sourceCode ?? context.getSourceCode();
|
|
25
|
+
const filePath = getFilePath(context);
|
|
26
|
+
if (!filePath || !filePath.endsWith('.ts')) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const text = sourceCode.getText();
|
|
31
|
+
if (!text.includes('brandAngularSymbol') || !text.includes('deps(')) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const sourceFile = getProjectSourceFile(
|
|
36
|
+
getProject(getCwd(context)),
|
|
37
|
+
filePath,
|
|
38
|
+
text,
|
|
39
|
+
);
|
|
40
|
+
const analysis = analyzeSourceFileDependencies(sourceFile);
|
|
41
|
+
if (
|
|
42
|
+
!analysis.classDeclaration ||
|
|
43
|
+
analysis.skipped ||
|
|
44
|
+
!analysis.className
|
|
45
|
+
) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const existing = readExistingDependencyGroups(
|
|
50
|
+
sourceFile,
|
|
51
|
+
analysis.className,
|
|
52
|
+
);
|
|
53
|
+
if (!existing.found) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (existing.warnings.length > 0) {
|
|
58
|
+
for (const warning of existing.warnings) {
|
|
59
|
+
reportNode(
|
|
60
|
+
context,
|
|
61
|
+
sourceCode,
|
|
62
|
+
existing.depsObjectNode ?? existing.exportAssignmentNode,
|
|
63
|
+
warning,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
reportIfMismatch(
|
|
70
|
+
context,
|
|
71
|
+
sourceCode,
|
|
72
|
+
existing,
|
|
73
|
+
'injected',
|
|
74
|
+
analysis.dependencyGroups.injected,
|
|
75
|
+
existing.dependencyGroups.injected,
|
|
76
|
+
);
|
|
77
|
+
reportIfMismatch(
|
|
78
|
+
context,
|
|
79
|
+
sourceCode,
|
|
80
|
+
existing,
|
|
81
|
+
'importDeps',
|
|
82
|
+
analysis.dependencyGroups.importDeps,
|
|
83
|
+
existing.dependencyGroups.importDeps,
|
|
84
|
+
);
|
|
85
|
+
reportIfMismatch(
|
|
86
|
+
context,
|
|
87
|
+
sourceCode,
|
|
88
|
+
existing,
|
|
89
|
+
'providers',
|
|
90
|
+
analysis.dependencyGroups.providers,
|
|
91
|
+
existing.dependencyGroups.providers,
|
|
92
|
+
);
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
},
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
function getProject(cwd) {
|
|
99
|
+
let project = projectCache.get(cwd);
|
|
100
|
+
if (project) {
|
|
101
|
+
return project;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const tsConfigFilePath = path.join(cwd, 'tsconfig.json');
|
|
105
|
+
project = fs.existsSync(tsConfigFilePath)
|
|
106
|
+
? new Project({ tsConfigFilePath })
|
|
107
|
+
: new Project({
|
|
108
|
+
compilerOptions: {
|
|
109
|
+
experimentalDecorators: true,
|
|
110
|
+
target: 9,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
projectCache.set(cwd, project);
|
|
115
|
+
return project;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function getProjectSourceFile(project, filePath, text) {
|
|
119
|
+
const normalizedPath = path.resolve(filePath);
|
|
120
|
+
const existingSourceFile = project.getSourceFile(normalizedPath);
|
|
121
|
+
if (existingSourceFile) {
|
|
122
|
+
existingSourceFile.replaceWithText(text);
|
|
123
|
+
return existingSourceFile;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const sourceFile = project.addSourceFileAtPathIfExists(normalizedPath);
|
|
127
|
+
if (sourceFile) {
|
|
128
|
+
sourceFile.replaceWithText(text);
|
|
129
|
+
return sourceFile;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return project.createSourceFile(normalizedPath, text, { overwrite: true });
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getFilePath(context) {
|
|
136
|
+
const filePath = context.filename ?? context.getFilename();
|
|
137
|
+
if (!filePath || filePath === '<input>') {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return filePath;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function getCwd(context) {
|
|
145
|
+
return context.cwd ?? process.cwd();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function reportIfMismatch(
|
|
149
|
+
context,
|
|
150
|
+
sourceCode,
|
|
151
|
+
existing,
|
|
152
|
+
propertyName,
|
|
153
|
+
expected,
|
|
154
|
+
actual,
|
|
155
|
+
) {
|
|
156
|
+
if (isSameDependencyList(expected, actual)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
reportNode(
|
|
161
|
+
context,
|
|
162
|
+
sourceCode,
|
|
163
|
+
existing.propertyNodes[propertyName] ??
|
|
164
|
+
existing.depsObjectNode ??
|
|
165
|
+
existing.exportAssignmentNode,
|
|
166
|
+
`deps.${propertyName} does not match the Angular symbol. Expected ${formatDependencyList(expected)} but found ${formatDependencyList(actual)}.`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function reportNode(context, sourceCode, node, message) {
|
|
171
|
+
context.report({
|
|
172
|
+
loc: node ? getNodeLoc(sourceCode, node) : undefined,
|
|
173
|
+
message,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getNodeLoc(sourceCode, node) {
|
|
178
|
+
return {
|
|
179
|
+
start: sourceCode.getLocFromIndex(node.getStart()),
|
|
180
|
+
end: sourceCode.getLocFromIndex(node.getEnd()),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function isSameDependencyList(left, right) {
|
|
185
|
+
return (
|
|
186
|
+
left.length === right.length &&
|
|
187
|
+
left.every((value, index) => value === right[index])
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function formatDependencyList(dependencies) {
|
|
192
|
+
return `[${dependencies.join(', ')}]`;
|
|
193
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// ESLint rules for ng-craft
|
|
2
|
+
const brandAngularDepsMatch = require('./brand-angular-deps-match.cjs');
|
|
3
|
+
const noAngularInject = require('./no-angular-inject.cjs');
|
|
4
|
+
const noDirectAngularClassExport = require('./no-direct-angular-class-export.cjs');
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
rules: {
|
|
8
|
+
'brand-angular-deps-match': brandAngularDepsMatch,
|
|
9
|
+
'no-angular-inject': noAngularInject,
|
|
10
|
+
'no-direct-angular-class-export': noDirectAngularClassExport,
|
|
11
|
+
},
|
|
12
|
+
};
|