@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.
- package/README.fr.md +10 -11
- package/README.md +10 -11
- package/dist/client.bundle.js +98 -98
- package/dist/src/config/migration.config.js +69 -0
- package/dist/src/core/app-analyzer.js +69 -79
- package/dist/src/core/ast/matchers/html/html-element-matcher.js +32 -7
- package/dist/src/core/ast/matchers/html/index.js +36 -4
- package/dist/src/core/ast/matchers/ts/collection-matcher.js +18 -0
- package/dist/src/core/ast/matchers/ts/decorator-matcher.js +33 -4
- package/dist/src/core/ast/matchers/ts/expression-matcher.js +1 -1
- package/dist/src/core/ast/matchers/ts/node-matcher.js +4 -0
- package/dist/src/core/project-detector.js +94 -106
- package/dist/src/core/project-strategy/nx-strategy.js +25 -22
- package/dist/src/core/project-strategy/standalone-strategy.js +43 -14
- package/dist/src/core/rules-loader.js +21 -13
- package/dist/src/core/scan-reporter.js +3 -1
- package/dist/src/core/scanner-orchestrator.js +1 -1
- package/dist/src/core/workload/constants.js +7 -4
- package/dist/src/data/markdown/angular-migration-20-21.md +1010 -0
- package/dist/src/data/rules/to19/rules-19-optionnelle.json +3 -0
- package/dist/src/data/rules/to21/rules-21-obligatoire.json +423 -0
- package/dist/src/data/rules/to21/rules-21-optionnelle.json +253 -0
- package/dist/src/data/rules/to21/rules-21-recommande.json +139 -0
- package/dist/src/index.js +1 -1
- package/dist/src/templates/landing/project-overview.template.js +2 -1
- package/dist/src/templates/page/migration-guide.template.js +1 -0
- package/dist/src/templates/workload/guide-rule-card.template.js +3 -1
- package/dist/src/templates/workload/hierarchy-shared.js +6 -5
- package/dist/src/templates/workload/unified-settings-panel.template.js +27 -33
- package/dist/src/utils/core/args-parser.js +19 -18
- package/dist/styles.css +1 -1
- 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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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:
|
|
61
|
+
name: targetName,
|
|
82
62
|
description,
|
|
83
|
-
path:
|
|
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
|
-
*
|
|
77
|
+
* Extrait les dépendances du package.json
|
|
98
78
|
*/
|
|
99
|
-
function
|
|
100
|
-
const libPath = path.join(projectPath, 'libs', libName);
|
|
79
|
+
function extractPackageDeps(projectPath) {
|
|
101
80
|
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
108
|
-
|
|
109
|
-
|
|
86
|
+
return {
|
|
87
|
+
description: packageJson.description,
|
|
88
|
+
dependencies: Object.keys(packageJson.dependencies || {}).length,
|
|
89
|
+
devDependencies: Object.keys(packageJson.devDependencies || {}).length
|
|
90
|
+
};
|
|
110
91
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
/**
|