@silvestv/migration-planificator 5.0.2 → 6.0.1

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 (32) hide show
  1. package/README.fr.md +10 -11
  2. package/README.md +10 -11
  3. package/dist/client.bundle.js +98 -98
  4. package/dist/src/config/migration.config.js +69 -0
  5. package/dist/src/core/app-analyzer.js +69 -79
  6. package/dist/src/core/ast/matchers/html/html-element-matcher.js +32 -7
  7. package/dist/src/core/ast/matchers/html/index.js +36 -4
  8. package/dist/src/core/ast/matchers/ts/collection-matcher.js +18 -0
  9. package/dist/src/core/ast/matchers/ts/decorator-matcher.js +33 -4
  10. package/dist/src/core/ast/matchers/ts/expression-matcher.js +1 -1
  11. package/dist/src/core/ast/matchers/ts/node-matcher.js +4 -0
  12. package/dist/src/core/project-detector.js +94 -106
  13. package/dist/src/core/project-strategy/nx-strategy.js +25 -22
  14. package/dist/src/core/project-strategy/standalone-strategy.js +43 -14
  15. package/dist/src/core/rules-loader.js +21 -13
  16. package/dist/src/core/scan-reporter.js +3 -1
  17. package/dist/src/core/scanner-orchestrator.js +1 -1
  18. package/dist/src/core/workload/constants.js +7 -4
  19. package/dist/src/data/markdown/angular-migration-20-21.md +1010 -0
  20. package/dist/src/data/rules/to19/rules-19-optionnelle.json +3 -0
  21. package/dist/src/data/rules/to21/rules-21-obligatoire.json +423 -0
  22. package/dist/src/data/rules/to21/rules-21-optionnelle.json +253 -0
  23. package/dist/src/data/rules/to21/rules-21-recommande.json +139 -0
  24. package/dist/src/index.js +1 -1
  25. package/dist/src/templates/landing/project-overview.template.js +2 -1
  26. package/dist/src/templates/page/migration-guide.template.js +1 -0
  27. package/dist/src/templates/workload/guide-rule-card.template.js +3 -1
  28. package/dist/src/templates/workload/hierarchy-shared.js +6 -5
  29. package/dist/src/templates/workload/unified-settings-panel.template.js +27 -33
  30. package/dist/src/utils/core/args-parser.js +19 -18
  31. package/dist/styles.css +1 -1
  32. package/package.json +3 -2
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ /**
3
+ * Configuration centralisée des migrations Angular
4
+ * POINT D'ENTRÉE UNIQUE pour ajouter une nouvelle version (ex: to22)
5
+ *
6
+ * Pour ajouter une nouvelle migration :
7
+ * 1. Ajouter une entrée dans MIGRATION_CONFIGS
8
+ * 2. Créer le dossier src/data/rules/toXX/ avec les fichiers JSON
9
+ * 3. C'est tout ! Tous les autres fichiers s'adaptent automatiquement
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MIGRATION_LABEL_MAP = exports.MIGRATION_COLOR_MAP = exports.MIGRATION_ORDER = exports.MIGRATION_VERSIONS = exports.MIGRATION_CONFIGS = void 0;
13
+ exports.getTargetVersionNumber = getTargetVersionNumber;
14
+ exports.getMigrationConfig = getMigrationConfig;
15
+ exports.isValidMigrationVersion = isValidMigrationVersion;
16
+ /**
17
+ * LISTE ORDONNÉE des migrations supportées
18
+ * Pour ajouter to22 : ajouter UNE SEULE ligne ici
19
+ */
20
+ exports.MIGRATION_CONFIGS = [
21
+ { version: 'to18', sourceVersion: 17, targetVersion: 18, label: 'Angular 17 → 18', color: 'blue' },
22
+ { version: 'to19', sourceVersion: 18, targetVersion: 19, label: 'Angular 18 → 19', color: 'indigo' },
23
+ { version: 'to20', sourceVersion: 19, targetVersion: 20, label: 'Angular 19 → 20', color: 'purple' },
24
+ { version: 'to21', sourceVersion: 20, targetVersion: 21, label: 'Angular 20 → 21', color: 'pink' },
25
+ ];
26
+ // ============================================================================
27
+ // UTILITAIRES DÉRIVÉS AUTOMATIQUEMENT
28
+ // ============================================================================
29
+ /**
30
+ * Liste des versions de migration (ex: ['to18', 'to19', 'to20', 'to21'])
31
+ */
32
+ exports.MIGRATION_VERSIONS = exports.MIGRATION_CONFIGS.map(c => c.version);
33
+ /**
34
+ * Ordre d'affichage des migrations (identique à MIGRATION_VERSIONS)
35
+ * Rétrocompatibilité avec core/workload/constants.ts
36
+ */
37
+ exports.MIGRATION_ORDER = [...exports.MIGRATION_VERSIONS];
38
+ /**
39
+ * Map version → couleur TailwindCSS
40
+ * Ex: { to18: 'blue', to19: 'indigo', to20: 'purple', to21: 'pink' }
41
+ */
42
+ exports.MIGRATION_COLOR_MAP = Object.fromEntries(exports.MIGRATION_CONFIGS.map(c => [c.version, c.color]));
43
+ /**
44
+ * Map version → label UI
45
+ * Ex: { to18: 'Angular 17 → 18', to19: 'Angular 18 → 19', ... }
46
+ */
47
+ exports.MIGRATION_LABEL_MAP = Object.fromEntries(exports.MIGRATION_CONFIGS.map(c => [c.version, c.label]));
48
+ // ============================================================================
49
+ // HELPERS
50
+ // ============================================================================
51
+ /**
52
+ * Obtenir le numéro de version cible depuis un identifiant (ex: 'to21' → 21)
53
+ */
54
+ function getTargetVersionNumber(version) {
55
+ const config = exports.MIGRATION_CONFIGS.find(c => c.version === version);
56
+ return config?.targetVersion ?? 0;
57
+ }
58
+ /**
59
+ * Obtenir la configuration complète d'une migration
60
+ */
61
+ function getMigrationConfig(version) {
62
+ return exports.MIGRATION_CONFIGS.find(c => c.version === version);
63
+ }
64
+ /**
65
+ * Vérifie si une version est valide
66
+ */
67
+ function isValidMigrationVersion(version) {
68
+ return exports.MIGRATION_VERSIONS.includes(version);
69
+ }
@@ -41,94 +41,84 @@ const core_1 = require("../utils/core");
41
41
  /**
42
42
  * Analyse les détails d'une application Angular
43
43
  */
44
- function analyzeApp(appName, projectPath, isNxMonorepo) {
45
- let appPath = projectPath;
46
- if (isNxMonorepo) {
47
- appPath = path.join(projectPath, 'apps', appName);
48
- }
49
- else {
50
- const angularJsonPath = path.join(projectPath, 'angular.json');
51
- if (fs.existsSync(angularJsonPath)) {
52
- const angularJson = JSON.parse(fs.readFileSync(angularJsonPath, 'utf-8'));
53
- const appConfig = angularJson.projects?.[appName];
54
- if (appConfig?.root) {
55
- appPath = path.join(projectPath, appConfig.root);
56
- }
57
- else if (appConfig?.sourceRoot) {
58
- appPath = path.join(projectPath, appConfig.sourceRoot);
59
- }
60
- }
61
- }
62
- const packageJsonPath = path.join(projectPath, 'package.json');
63
- let description;
64
- let dependencies = 0;
65
- let devDependencies = 0;
66
- if (fs.existsSync(packageJsonPath)) {
67
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
68
- description = packageJson.description;
69
- dependencies = Object.keys(packageJson.dependencies || {}).length;
70
- devDependencies = Object.keys(packageJson.devDependencies || {}).length;
71
- }
72
- const components = (0, core_1.countFiles)(appPath, /\.component\.ts$/);
73
- const modules = (0, core_1.countFiles)(appPath, /\.module\.ts$/);
74
- const services = (0, core_1.countFiles)(appPath, /\.service\.ts$/);
75
- const directives = (0, core_1.countFiles)(appPath, /\.directive\.ts$/);
76
- const pipes = (0, core_1.countFiles)(appPath, /\.pipe\.ts$/);
77
- const guards = (0, core_1.countFiles)(appPath, /\.guard\.ts$/);
78
- const interceptors = (0, core_1.countFiles)(appPath, /\.interceptor\.ts$/);
79
- const resolvers = (0, core_1.countFiles)(appPath, /\.resolver\.ts$/);
44
+ function analyzeApp(appName, projectPath, isNxMonorepo, angularJson) {
45
+ const appPath = resolveTargetPath(appName, projectPath, isNxMonorepo, 'apps', angularJson);
46
+ return analyzeTarget(appName, appPath, projectPath);
47
+ }
48
+ /**
49
+ * Analyse les détails d'une librairie Angular (Nx ou Angular CLI workspace)
50
+ */
51
+ function analyzeLib(libName, projectPath, isNxMonorepo = true, angularJson) {
52
+ const libPath = resolveTargetPath(libName, projectPath, isNxMonorepo, 'libs', angularJson);
53
+ return analyzeTarget(libName, libPath, projectPath);
54
+ }
55
+ /**
56
+ * Analyse les artefacts Angular d'un target (app ou lib) - DRY
57
+ */
58
+ function analyzeTarget(targetName, targetPath, projectPath) {
59
+ const { description, dependencies, devDependencies } = extractPackageDeps(projectPath);
80
60
  return {
81
- name: appName,
61
+ name: targetName,
82
62
  description,
83
- path: appPath,
63
+ path: targetPath,
84
64
  dependencies,
85
65
  devDependencies,
86
- components,
87
- modules,
88
- services,
89
- directives,
90
- pipes,
91
- guards,
92
- interceptors,
93
- resolvers
66
+ components: (0, core_1.countFiles)(targetPath, /\.component\.ts$/),
67
+ modules: (0, core_1.countFiles)(targetPath, /\.module\.ts$/),
68
+ services: (0, core_1.countFiles)(targetPath, /\.service\.ts$/),
69
+ directives: (0, core_1.countFiles)(targetPath, /\.directive\.ts$/),
70
+ pipes: (0, core_1.countFiles)(targetPath, /\.pipe\.ts$/),
71
+ guards: (0, core_1.countFiles)(targetPath, /\.guard\.ts$/),
72
+ interceptors: (0, core_1.countFiles)(targetPath, /\.interceptor\.ts$/),
73
+ resolvers: (0, core_1.countFiles)(targetPath, /\.resolver\.ts$/)
94
74
  };
95
75
  }
96
76
  /**
97
- * Analyse les détails d'une librairie Angular (Nx uniquement)
77
+ * Extrait les dépendances du package.json
98
78
  */
99
- function analyzeLib(libName, projectPath) {
100
- const libPath = path.join(projectPath, 'libs', libName);
79
+ function extractPackageDeps(projectPath) {
101
80
  const packageJsonPath = path.join(projectPath, 'package.json');
102
- let description;
103
- let dependencies = 0;
104
- let devDependencies = 0;
105
- if (fs.existsSync(packageJsonPath)) {
81
+ if (!fs.existsSync(packageJsonPath)) {
82
+ return { dependencies: 0, devDependencies: 0 };
83
+ }
84
+ try {
106
85
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
107
- description = packageJson.description;
108
- dependencies = Object.keys(packageJson.dependencies || {}).length;
109
- devDependencies = Object.keys(packageJson.devDependencies || {}).length;
86
+ return {
87
+ description: packageJson.description,
88
+ dependencies: Object.keys(packageJson.dependencies || {}).length,
89
+ devDependencies: Object.keys(packageJson.devDependencies || {}).length
90
+ };
110
91
  }
111
- const components = (0, core_1.countFiles)(libPath, /\.component\.ts$/);
112
- const modules = (0, core_1.countFiles)(libPath, /\.module\.ts$/);
113
- const services = (0, core_1.countFiles)(libPath, /\.service\.ts$/);
114
- const directives = (0, core_1.countFiles)(libPath, /\.directive\.ts$/);
115
- const pipes = (0, core_1.countFiles)(libPath, /\.pipe\.ts$/);
116
- const guards = (0, core_1.countFiles)(libPath, /\.guard\.ts$/);
117
- const interceptors = (0, core_1.countFiles)(libPath, /\.interceptor\.ts$/);
118
- const resolvers = (0, core_1.countFiles)(libPath, /\.resolver\.ts$/);
119
- return {
120
- name: libName,
121
- description,
122
- path: libPath,
123
- dependencies,
124
- devDependencies,
125
- components,
126
- modules,
127
- services,
128
- directives,
129
- pipes,
130
- guards,
131
- interceptors,
132
- resolvers
133
- };
92
+ catch {
93
+ return { dependencies: 0, devDependencies: 0 };
94
+ }
95
+ }
96
+ /**
97
+ * Résout le chemin d'un target (app ou lib) selon le type de projet - DRY
98
+ */
99
+ function resolveTargetPath(targetName, projectPath, isNxMonorepo, nxFolder, angularJson) {
100
+ // Nx : apps/xxx ou libs/xxx (ou src/ pour single-app)
101
+ if (isNxMonorepo) {
102
+ const standardPath = path.join(projectPath, nxFolder, targetName);
103
+ // Nx single-app : si apps/xxx n'existe pas, utiliser src/
104
+ if (!fs.existsSync(standardPath) && nxFolder === 'apps') {
105
+ const srcPath = path.join(projectPath, 'src');
106
+ if (fs.existsSync(srcPath)) {
107
+ return srcPath;
108
+ }
109
+ }
110
+ return standardPath;
111
+ }
112
+ // Angular CLI : lire depuis angular.json si disponible
113
+ const projectConfig = angularJson?.projects?.[targetName];
114
+ if (projectConfig?.root) {
115
+ return path.join(projectPath, projectConfig.root);
116
+ }
117
+ if (projectConfig?.sourceRoot) {
118
+ return path.join(projectPath, projectConfig.sourceRoot);
119
+ }
120
+ // Fallback : convention projects/xxx ou src/ pour standalone
121
+ return nxFolder === 'apps' && !angularJson?.projects
122
+ ? path.join(projectPath, 'src')
123
+ : path.join(projectPath, 'projects', targetName);
134
124
  }
@@ -41,14 +41,39 @@ function matchesElementPattern(node, pattern, htmlContent) {
41
41
  }
42
42
  /**
43
43
  * Matche un pattern DeferredBlock (@defer blocks)
44
+ * Supporte deferTrigger pour filtrer par type de trigger et options
44
45
  */
45
- function matchesDeferredBlockPattern(node, htmlContent) {
46
+ function matchesDeferredBlockPattern(node, pattern, htmlContent) {
46
47
  // Vérifier si c'est un DeferredBlock
47
- if (node.constructor && node.constructor.name === 'DeferredBlock') {
48
- return {
49
- lineNumber: (0, html_parser_1.getLineNumber)(node, htmlContent),
50
- matchedText: (0, html_parser_1.getNodeText)(node, htmlContent).substring(0, 100)
51
- };
48
+ if (!node.constructor || node.constructor.name !== 'DeferredBlock') {
49
+ return null;
52
50
  }
53
- return null;
51
+ // Si deferTrigger est spécifié, vérifier les triggers
52
+ if (pattern.deferTrigger) {
53
+ const triggerType = pattern.deferTrigger.type;
54
+ const triggers = node.triggers || {};
55
+ const trigger = triggers[triggerType];
56
+ // Le trigger doit exister
57
+ if (!trigger) {
58
+ return null;
59
+ }
60
+ // Vérifier hasOptions (le trigger a des options si reference est non vide)
61
+ if (pattern.deferTrigger.hasOptions !== undefined) {
62
+ const hasOptions = trigger.reference && trigger.reference.trim().length > 0;
63
+ if (pattern.deferTrigger.hasOptions !== hasOptions) {
64
+ return null;
65
+ }
66
+ }
67
+ // Vérifier optionsMatch (regex sur reference)
68
+ if (pattern.deferTrigger.optionsMatch && trigger.reference) {
69
+ const regex = new RegExp(pattern.deferTrigger.optionsMatch);
70
+ if (!regex.test(trigger.reference)) {
71
+ return null;
72
+ }
73
+ }
74
+ }
75
+ return {
76
+ lineNumber: (0, html_parser_1.getLineNumber)(node, htmlContent),
77
+ matchedText: (0, html_parser_1.getNodeText)(node, htmlContent).substring(0, 100)
78
+ };
54
79
  }
@@ -11,15 +11,15 @@ const html_text_matcher_1 = require("./html-text-matcher");
11
11
  const html_pipe_matcher_1 = require("./html-pipe-matcher");
12
12
  /**
13
13
  * Vérifie si un nœud HTML correspond à un pattern AST HTML
14
- * Supporte : Attribute, PipeExpression, BoundText, Element, DeferredBlock
14
+ * Supporte : Attribute, BoundAttribute, PipeExpression, BoundText, Element, DeferredBlock
15
15
  */
16
16
  function matchesHtmlNode(node, pattern, htmlContent) {
17
17
  // Pattern: BoundText (interpolations {{ ... }})
18
18
  if (pattern.nodeType === 'BoundText') {
19
19
  return (0, html_text_matcher_1.matchesBoundTextPattern)(node, pattern, htmlContent);
20
20
  }
21
- // Pattern: Attribute (*ngIf, *ngFor, etc.)
22
- if (pattern.nodeType === 'Attribute' && (0, html_parser_1.isElement)(node)) {
21
+ // Pattern: Attribute (*ngIf, *ngFor, etc.) ou BoundAttribute ([ngClass], [ngStyle], etc.)
22
+ if ((pattern.nodeType === 'Attribute' || pattern.nodeType === 'BoundAttribute') && (0, html_parser_1.isElement)(node)) {
23
23
  return (0, html_attribute_matcher_1.matchesAttributePattern)(node, pattern, htmlContent);
24
24
  }
25
25
  // Pattern: Element (ng-content, div, etc.)
@@ -28,7 +28,7 @@ function matchesHtmlNode(node, pattern, htmlContent) {
28
28
  }
29
29
  // Pattern: DeferredBlock (@defer blocks)
30
30
  if (pattern.nodeType === 'DeferredBlock') {
31
- return (0, html_element_matcher_1.matchesDeferredBlockPattern)(node, htmlContent);
31
+ return (0, html_element_matcher_1.matchesDeferredBlockPattern)(node, pattern, htmlContent);
32
32
  }
33
33
  // Pattern: PipeExpression (| async, | date, etc.)
34
34
  if (pattern.nodeType === 'PipeExpression' && pattern.pipeName) {
@@ -57,6 +57,26 @@ function traverseHtmlAst(nodes, pattern, htmlContent, matches) {
57
57
  (0, html_pipe_matcher_1.traversePipeExpression)(value, pattern, htmlContent, matches);
58
58
  }
59
59
  }
60
+ // Si pattern cherche PipeExpression et node est Element, parcourir les valeurs d'attributs (pour *ngFor avec | pipe)
61
+ if (pattern.nodeType === 'PipeExpression' && (0, html_parser_1.isElement)(node)) {
62
+ const element = node;
63
+ // Parcourir inputs (property bindings) et templateAttrs (*ngFor, *ngIf, etc.)
64
+ const allBoundAttrs = [
65
+ ...element.inputs,
66
+ ...(element.templateAttrs || [])
67
+ ];
68
+ for (const attr of allBoundAttrs) {
69
+ if ('value' in attr && attr.value) {
70
+ const attrValue = attr.value;
71
+ if (attrValue.ast) {
72
+ (0, html_pipe_matcher_1.traversePipeExpression)(attrValue.ast, pattern, htmlContent, matches);
73
+ }
74
+ else {
75
+ (0, html_pipe_matcher_1.traversePipeExpression)(attrValue, pattern, htmlContent, matches);
76
+ }
77
+ }
78
+ }
79
+ }
60
80
  // 1. Parcourir les enfants standards
61
81
  if ('children' in node && Array.isArray(node.children)) {
62
82
  traverseHtmlAst(node.children, pattern, htmlContent, matches);
@@ -81,6 +101,17 @@ function traverseHtmlAst(nodes, pattern, htmlContent, matches) {
81
101
  if ('empty' in node && node.empty?.children) {
82
102
  traverseHtmlAst(node.empty.children, pattern, htmlContent, matches);
83
103
  }
104
+ // 4b. Parcourir @for expression (for PipeExpression like | keyvalue)
105
+ if (pattern.nodeType === 'PipeExpression' && 'expression' in node && node.expression) {
106
+ const forExpression = node.expression;
107
+ // expression est ASTWithSource, l'AST réel est dans expression.ast
108
+ if (forExpression.ast) {
109
+ (0, html_pipe_matcher_1.traversePipeExpression)(forExpression.ast, pattern, htmlContent, matches);
110
+ }
111
+ else {
112
+ (0, html_pipe_matcher_1.traversePipeExpression)(forExpression, pattern, htmlContent, matches);
113
+ }
114
+ }
84
115
  // 5-8. Parcourir @defer sub-blocks (mainBlock, placeholder, loading, error)
85
116
  const deferSubBlocks = ['mainBlock', 'placeholder', 'loading', 'error'];
86
117
  for (const blockName of deferSubBlocks) {
@@ -110,6 +141,7 @@ function findHtmlPatternMatches(htmlContent, pattern) {
110
141
  */
111
142
  function isHtmlPattern(pattern) {
112
143
  return pattern.nodeType === 'Attribute' ||
144
+ pattern.nodeType === 'BoundAttribute' ||
113
145
  pattern.nodeType === 'PipeExpression' ||
114
146
  pattern.nodeType === 'BoundText' ||
115
147
  pattern.nodeType === 'Element' ||
@@ -16,6 +16,24 @@ function matchesElements(node, elementsPattern, matchesAstPatternFn) {
16
16
  if (typeof elementsPattern === 'object' && elementsPattern.notEmpty === true) {
17
17
  return elements.length > 0;
18
18
  }
19
+ // Vérifier missing (l'élément nommé NE doit PAS être présent dans le tableau)
20
+ if (typeof elementsPattern === 'object' && elementsPattern.missing) {
21
+ const missingName = elementsPattern.missing;
22
+ const hasElement = elements.some(element => {
23
+ // CallExpression: provideZoneChangeDetection()
24
+ if (ts_morph_1.Node.isCallExpression(element)) {
25
+ const funcName = element.getExpression().getText().split('.').pop() || '';
26
+ return funcName === missingName;
27
+ }
28
+ // Identifier: someProvider
29
+ if (ts_morph_1.Node.isIdentifier(element)) {
30
+ return element.getText() === missingName;
31
+ }
32
+ return false;
33
+ });
34
+ // Le pattern matche si l'élément est ABSENT
35
+ return !hasElement;
36
+ }
19
37
  // Vérifier contains (au moins un élément matche le pattern)
20
38
  if (elementsPattern.contains) {
21
39
  return elements.some(element => matchesAstPatternFn(element, elementsPattern.contains));
@@ -172,7 +172,7 @@ function checkObjectProperties(objectNode, properties, matchesAstPatternFn) {
172
172
  return false;
173
173
  }
174
174
  }
175
- // Vérifier elements avec referTo (cas spécial pour résolution de symboles)
175
+ // Vérifier elements avec referTo ou missing (cas spéciaux pour résolution de symboles ou vérification d'absence)
176
176
  if (typeof propPattern === 'object' && 'elements' in propPattern && 'exists' in propPattern) {
177
177
  // Ce pattern a à la fois exists (pour vérifier que la propriété existe)
178
178
  // ET elements (pour vérifier le contenu du tableau)
@@ -180,8 +180,6 @@ function checkObjectProperties(objectNode, properties, matchesAstPatternFn) {
180
180
  if (!initializer) {
181
181
  return false;
182
182
  }
183
- // IMPORTANT : Si elements.referTo existe, on doit construire un pattern AST complet
184
- // avec nodeType: ArrayLiteralExpression pour que matchesAstPattern le reconnaisse
185
183
  const elementsPattern = propPattern.elements;
186
184
  // Si referTo est présent, créer un pattern AST complet
187
185
  if (typeof elementsPattern === 'object' && 'referTo' in elementsPattern) {
@@ -194,8 +192,19 @@ function checkObjectProperties(objectNode, properties, matchesAstPatternFn) {
194
192
  return false;
195
193
  }
196
194
  }
195
+ else if (typeof elementsPattern === 'object' && 'missing' in elementsPattern) {
196
+ // Cas spécial: vérifier que l'élément nommé est ABSENT du tableau
197
+ // Créer un pattern AST complet pour ArrayLiteralExpression avec elements.missing
198
+ const fullPattern = {
199
+ nodeType: 'ArrayLiteralExpression',
200
+ elements: elementsPattern
201
+ };
202
+ if (matchesAstPatternFn && !matchesAstPatternFn(initializer, fullPattern)) {
203
+ return false;
204
+ }
205
+ }
197
206
  else {
198
- // Pattern normal sans referTo
207
+ // Pattern normal
199
208
  if (matchesAstPatternFn && !matchesAstPatternFn(initializer, elementsPattern)) {
200
209
  return false;
201
210
  }
@@ -361,6 +370,26 @@ function matchesParameters(node, parametersPattern) {
361
370
  return false;
362
371
  }
363
372
  }
373
+ // Vérifier first.type (type du premier paramètre - sans générique)
374
+ if (parametersPattern.first?.type) {
375
+ const firstParam = parameters[0];
376
+ if (!firstParam) {
377
+ return false;
378
+ }
379
+ const typeNode = firstParam.getTypeNode();
380
+ if (!typeNode) {
381
+ return false;
382
+ }
383
+ // Obtenir le texte du type
384
+ const typeText = typeNode.getText();
385
+ // Vérifier que le type correspond EXACTEMENT (sans générique)
386
+ // Ex: "SimpleChanges" doit matcher, mais pas "SimpleChanges<UserComponent>"
387
+ const expectedType = parametersPattern.first.type;
388
+ // Le type doit être exactement le type attendu (pas un générique)
389
+ if (typeText !== expectedType) {
390
+ return false;
391
+ }
392
+ }
364
393
  return true;
365
394
  }
366
395
  /**
@@ -111,7 +111,7 @@ function matchesArguments(node, argumentsPattern, matchesAstPatternFn) {
111
111
  // Chercher dans tous les arguments pour trouver un ObjectLiteralExpression
112
112
  for (const arg of args) {
113
113
  if (ts_morph_1.Node.isObjectLiteralExpression(arg)) {
114
- const result = (0, decorator_matcher_1.checkObjectProperties)(arg, argumentsPattern.properties);
114
+ const result = (0, decorator_matcher_1.checkObjectProperties)(arg, argumentsPattern.properties, matchesAstPatternFn);
115
115
  // Si on trouve un objet qui matche (ou qui ne matche pas les missing), on retourne le résultat
116
116
  if (result || Object.values(argumentsPattern.properties).some((p) => p && typeof p === 'object' && p.missing)) {
117
117
  return result;
@@ -53,6 +53,10 @@ function matchesName(node, name) {
53
53
  if (ts_morph_1.Node.isVariableDeclaration(node)) {
54
54
  return names.includes(node.getName());
55
55
  }
56
+ // Pour PropertyDeclaration (ex: content: any[][] = [])
57
+ if (ts_morph_1.Node.isPropertyDeclaration(node)) {
58
+ return names.includes(node.getName());
59
+ }
56
60
  return false;
57
61
  }
58
62
  /**