@silvestv/migration-planificator 3.0.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/LICENSE +96 -0
- package/README.fr.md +359 -0
- package/README.md +360 -0
- package/SECURITY.md +187 -0
- package/dist/client.bundle.js +357 -0
- package/dist/src/core/app-analyzer.js +134 -0
- package/dist/src/core/ast/matchers/html/html-attribute-matcher.js +86 -0
- package/dist/src/core/ast/matchers/html/html-component-matcher.js +40 -0
- package/dist/src/core/ast/matchers/html/html-element-matcher.js +54 -0
- package/dist/src/core/ast/matchers/html/html-parser.js +58 -0
- package/dist/src/core/ast/matchers/html/html-pipe-matcher.js +95 -0
- package/dist/src/core/ast/matchers/html/html-text-matcher.js +53 -0
- package/dist/src/core/ast/matchers/html/index.js +118 -0
- package/dist/src/core/ast/matchers/index.js +377 -0
- package/dist/src/core/ast/matchers/ts/collection-matcher.js +51 -0
- package/dist/src/core/ast/matchers/ts/context-matcher.js +275 -0
- package/dist/src/core/ast/matchers/ts/decorator-matcher.js +465 -0
- package/dist/src/core/ast/matchers/ts/expression-matcher.js +237 -0
- package/dist/src/core/ast/matchers/ts/file-matcher.js +97 -0
- package/dist/src/core/ast/matchers/ts/hierarchy-matcher.js +172 -0
- package/dist/src/core/ast/matchers/ts/import-matcher.js +39 -0
- package/dist/src/core/ast/matchers/ts/index.js +53 -0
- package/dist/src/core/ast/matchers/ts/node-matcher.js +156 -0
- package/dist/src/core/ast/matchers/ts/symbol-matcher.js +281 -0
- package/dist/src/core/ast/matchers/ts/type-matcher.js +207 -0
- package/dist/src/core/ast/matchers/utils/matcher-helpers.js +37 -0
- package/dist/src/core/ast/scanner-ast.js +444 -0
- package/dist/src/core/project-detector.js +196 -0
- package/dist/src/core/project-strategy/index.js +9 -0
- package/dist/src/core/project-strategy/nx-strategy.js +130 -0
- package/dist/src/core/project-strategy/project-strategy.interface.js +2 -0
- package/dist/src/core/project-strategy/standalone-strategy.js +74 -0
- package/dist/src/core/project-strategy/strategy-factory.js +15 -0
- package/dist/src/core/rules-loader.js +89 -0
- package/dist/src/core/scan-reporter.js +316 -0
- package/dist/src/core/scanner-delta.js +339 -0
- package/dist/src/core/scanner-orchestrator.js +266 -0
- package/dist/src/core/scanner-regex.js +298 -0
- package/dist/src/core/workload/calculator.js +82 -0
- package/dist/src/core/workload/constants.js +15 -0
- package/dist/src/core/workload/grouping.js +18 -0
- package/dist/src/core/workload/hierarchy-calculator.js +127 -0
- package/dist/src/core/workload/index.js +11 -0
- package/dist/src/core/workload/metadata.js +20 -0
- package/dist/src/core/workload/special-workload.js +101 -0
- package/dist/src/core/workload/target-resolver.js +34 -0
- package/dist/src/data/angular-migration-rules.json +2337 -0
- package/dist/src/data/markdown/angular-migration-17-18.md +408 -0
- package/dist/src/data/markdown/angular-migration-18-19.md +600 -0
- package/dist/src/data/markdown/angular-migration-19-20.md +521 -0
- package/dist/src/data/rules/rearchitecture/rearchitecture-rules.json +66 -0
- package/dist/src/data/rules/to18/rules-18-obligatoire.json +374 -0
- package/dist/src/data/rules/to18/rules-18-optionnelle.json +188 -0
- package/dist/src/data/rules/to18/rules-18-recommande.json +218 -0
- package/dist/src/data/rules/to19/rules-19-obligatoire.json +348 -0
- package/dist/src/data/rules/to19/rules-19-optionnelle.json +223 -0
- package/dist/src/data/rules/to19/rules-19-recommande.json +200 -0
- package/dist/src/data/rules/to20/rules-20-obligatoire.json +556 -0
- package/dist/src/data/rules/to20/rules-20-optionnelle.json +190 -0
- package/dist/src/data/rules/to20/rules-20-recommande.json +151 -0
- package/dist/src/index.js +161 -0
- package/dist/src/models/chip-config.js +45 -0
- package/dist/src/models/interfaces/app-details.interface.js +2 -0
- package/dist/src/models/interfaces/ast-interfaces.js +5 -0
- package/dist/src/models/interfaces/ast-pattern.interface.js +2 -0
- package/dist/src/models/interfaces/client-interfaces.js +6 -0
- package/dist/src/models/interfaces/detection-stats.interface.js +2 -0
- package/dist/src/models/interfaces/html-match.interface.js +2 -0
- package/dist/src/models/interfaces/html-report-data.interface.js +2 -0
- package/dist/src/models/interfaces/lib-details.interface.js +2 -0
- package/dist/src/models/interfaces/migration-rules.interface.js +2 -0
- package/dist/src/models/interfaces/parsed-args.interface.js +2 -0
- package/dist/src/models/interfaces/project-info.interface.js +2 -0
- package/dist/src/models/interfaces/project-overview-data.interface.js +2 -0
- package/dist/src/models/interfaces/rule-match.interface.js +2 -0
- package/dist/src/models/interfaces/rule.interface.js +2 -0
- package/dist/src/models/interfaces/rules-by-priority.interface.js +2 -0
- package/dist/src/models/interfaces/scanner-comparison.interface.js +2 -0
- package/dist/src/models/interfaces/special-workload.interface.js +2 -0
- package/dist/src/models/interfaces/workload-report.interface.js +2 -0
- package/dist/src/models/types/build-block-blob.type.js +2 -0
- package/dist/src/models/types/migration-version.type.js +2 -0
- package/dist/src/models/types/project-type.type.js +2 -0
- package/dist/src/models/types/risk-level.type.js +2 -0
- package/dist/src/models/types/rule-category.type.js +2 -0
- package/dist/src/models/types/rule-priority.type.js +2 -0
- package/dist/src/models/types/rule-workload-type.type.js +2 -0
- package/dist/src/templates/landing/applications-analyzed.template.js +18 -0
- package/dist/src/templates/landing/card-app-info.template.js +63 -0
- package/dist/src/templates/landing/card-lib-info.template.js +67 -0
- package/dist/src/templates/landing/libs-analyzed.template.js +22 -0
- package/dist/src/templates/landing/nx-summary.template.js +115 -0
- package/dist/src/templates/landing/project-overview.template.js +27 -0
- package/dist/src/templates/page/index-page.template.js +95 -0
- package/dist/src/templates/page/main.template.js +83 -0
- package/dist/src/templates/page/migration-guide.template.js +175 -0
- package/dist/src/templates/page/workload-report.template.js +53 -0
- package/dist/src/templates/workload/dashboard.template.js +184 -0
- package/dist/src/templates/workload/filters-panel.template.js +215 -0
- package/dist/src/templates/workload/guide-rule-card.template.js +107 -0
- package/dist/src/templates/workload/hierarchy-nx.template.js +104 -0
- package/dist/src/templates/workload/hierarchy-shared.js +163 -0
- package/dist/src/templates/workload/hierarchy-standalone.template.js +36 -0
- package/dist/src/templates/workload/hierarchy.template.js +35 -0
- package/dist/src/templates/workload/rule-modal.template.js +280 -0
- package/dist/src/utils/core/args-parser.js +123 -0
- package/dist/src/utils/core/array-helpers.js +18 -0
- package/dist/src/utils/core/ast-helpers.js +99 -0
- package/dist/src/utils/core/file-helpers.js +109 -0
- package/dist/src/utils/core/html-helpers.js +36 -0
- package/dist/src/utils/core/index.js +28 -0
- package/dist/src/utils/core/logger.js +38 -0
- package/dist/src/utils/core/rule-helpers.js +15 -0
- package/dist/src/utils/core/workload-formatter.js +6 -0
- package/dist/src/utils/shared/array-helpers.js +25 -0
- package/dist/src/utils/shared/date-helpers.js +109 -0
- package/dist/src/utils/shared/html-helpers.js +37 -0
- package/dist/src/utils/shared/index.js +25 -0
- package/dist/src/utils/shared/rule-helpers.js +20 -0
- package/dist/src/utils/shared/time-formatters.js +76 -0
- package/dist/styles.css +2 -0
- package/package.json +107 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.analyzeApp = analyzeApp;
|
|
37
|
+
exports.analyzeLib = analyzeLib;
|
|
38
|
+
const fs = __importStar(require("fs"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const core_1 = require("../utils/core");
|
|
41
|
+
/**
|
|
42
|
+
* Analyse les détails d'une application Angular
|
|
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$/);
|
|
80
|
+
return {
|
|
81
|
+
name: appName,
|
|
82
|
+
description,
|
|
83
|
+
path: appPath,
|
|
84
|
+
dependencies,
|
|
85
|
+
devDependencies,
|
|
86
|
+
components,
|
|
87
|
+
modules,
|
|
88
|
+
services,
|
|
89
|
+
directives,
|
|
90
|
+
pipes,
|
|
91
|
+
guards,
|
|
92
|
+
interceptors,
|
|
93
|
+
resolvers
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Analyse les détails d'une librairie Angular (Nx uniquement)
|
|
98
|
+
*/
|
|
99
|
+
function analyzeLib(libName, projectPath) {
|
|
100
|
+
const libPath = path.join(projectPath, 'libs', libName);
|
|
101
|
+
const packageJsonPath = path.join(projectPath, 'package.json');
|
|
102
|
+
let description;
|
|
103
|
+
let dependencies = 0;
|
|
104
|
+
let devDependencies = 0;
|
|
105
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
106
|
+
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;
|
|
110
|
+
}
|
|
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
|
+
};
|
|
134
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAttributeValue = getAttributeValue;
|
|
4
|
+
exports.matchesAttributePattern = matchesAttributePattern;
|
|
5
|
+
const html_parser_1 = require("./html-parser");
|
|
6
|
+
const matcher_helpers_1 = require("../utils/matcher-helpers");
|
|
7
|
+
/**
|
|
8
|
+
* Récupère le nom d'un attribut
|
|
9
|
+
*/
|
|
10
|
+
function getAttributeName(attr) {
|
|
11
|
+
if ('name' in attr) {
|
|
12
|
+
return attr.name;
|
|
13
|
+
}
|
|
14
|
+
if ('key' in attr && 'span' in attr.key) {
|
|
15
|
+
return attr.key.span.toString();
|
|
16
|
+
}
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Récupère la valeur d'un attribut
|
|
21
|
+
*/
|
|
22
|
+
function getAttributeValue(attr) {
|
|
23
|
+
// BoundAttribute (property binding like [ngModel] or [(ngModel)])
|
|
24
|
+
if ('value' in attr && attr.value) {
|
|
25
|
+
if (typeof attr.value === 'string') {
|
|
26
|
+
return attr.value;
|
|
27
|
+
}
|
|
28
|
+
// Pour les inputs bindings, la valeur est dans value.source
|
|
29
|
+
if ('source' in attr.value) {
|
|
30
|
+
return attr.value.source;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// TextAttribute (regular attribute)
|
|
34
|
+
if ('value' in attr && typeof attr.value === 'string') {
|
|
35
|
+
return attr.value;
|
|
36
|
+
}
|
|
37
|
+
return '';
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Vérifie si un attribut/input contient des erreurs de parsing
|
|
41
|
+
* (utilisé pour détecter des assignations interdites dans [(ngModel)])
|
|
42
|
+
*/
|
|
43
|
+
function hasBindingErrors(attr) {
|
|
44
|
+
if ('value' in attr && attr.value && 'errors' in attr.value && Array.isArray(attr.value.errors)) {
|
|
45
|
+
return attr.value.errors.length > 0;
|
|
46
|
+
}
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Matche un pattern Attribute (*ngIf, *ngFor, [(ngModel)], etc.)
|
|
51
|
+
*/
|
|
52
|
+
function matchesAttributePattern(element, pattern, htmlContent) {
|
|
53
|
+
const patternNames = (0, matcher_helpers_1.ensureArray)(pattern.name);
|
|
54
|
+
// Vérifier les attributs de l'élément (attributes, inputs, templateAttrs)
|
|
55
|
+
const allAttributes = [
|
|
56
|
+
...element.attributes,
|
|
57
|
+
...element.inputs,
|
|
58
|
+
...(element.templateAttrs || [])
|
|
59
|
+
];
|
|
60
|
+
for (const attr of allAttributes) {
|
|
61
|
+
let attrName = getAttributeName(attr);
|
|
62
|
+
// Pour les directives structurelles, ajouter le préfixe *
|
|
63
|
+
if (element.templateAttrs && element.templateAttrs.includes(attr)) {
|
|
64
|
+
attrName = '*' + attrName;
|
|
65
|
+
}
|
|
66
|
+
if (patternNames.includes(attrName)) {
|
|
67
|
+
// Vérifier valueMatches si spécifié
|
|
68
|
+
if (pattern.valueMatches) {
|
|
69
|
+
const attrValue = getAttributeValue(attr);
|
|
70
|
+
const valueRegex = new RegExp(pattern.valueMatches);
|
|
71
|
+
// Pour ngModel, on peut aussi utiliser les erreurs de parsing Angular
|
|
72
|
+
const hasErrors = hasBindingErrors(attr);
|
|
73
|
+
const valueMatches = valueRegex.test(attrValue);
|
|
74
|
+
// Si ni la regex ne matche, ni d'erreurs détectées, skip
|
|
75
|
+
if (!valueMatches && !hasErrors) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
lineNumber: (0, html_parser_1.getLineNumber)(attr, htmlContent),
|
|
81
|
+
matchedText: (0, html_parser_1.getNodeText)(element, htmlContent)
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchesHtmlPatternInComponent = matchesHtmlPatternInComponent;
|
|
4
|
+
const index_1 = require("./index");
|
|
5
|
+
/**
|
|
6
|
+
* Vérifie si un nœud Decorator @Component contient un template HTML qui matche le pattern
|
|
7
|
+
* Extrait le template du @Component et utilise findHtmlPatternMatches
|
|
8
|
+
*/
|
|
9
|
+
function matchesHtmlPatternInComponent(node, pattern) {
|
|
10
|
+
// Le nœud doit être un Decorator @Component
|
|
11
|
+
if (!node.getKindName || node.getKindName() !== 'Decorator') {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
// Extraire le template du @Component
|
|
15
|
+
const expression = node.getExpression();
|
|
16
|
+
if (!expression || !expression.getKindName || expression.getKindName() !== 'CallExpression') {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
const args = expression.getArguments();
|
|
20
|
+
if (args.length === 0) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
const configObject = args[0];
|
|
24
|
+
if (!configObject || configObject.getKindName() !== 'ObjectLiteralExpression') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const templateProperty = configObject.getProperty('template');
|
|
28
|
+
if (!templateProperty) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
const templateValue = templateProperty.getInitializer();
|
|
32
|
+
if (!templateValue) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
// Extraire le texte du template et retirer les quotes/backticks
|
|
36
|
+
const templateContent = templateValue.getText().replace(/^[`'"]+|[`'"]+$/g, '');
|
|
37
|
+
// Utiliser findHtmlPatternMatches pour vérifier le pattern
|
|
38
|
+
const matches = (0, index_1.findHtmlPatternMatches)(templateContent, pattern);
|
|
39
|
+
return matches.length > 0;
|
|
40
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchesElementPattern = matchesElementPattern;
|
|
4
|
+
exports.matchesDeferredBlockPattern = matchesDeferredBlockPattern;
|
|
5
|
+
const html_parser_1 = require("./html-parser");
|
|
6
|
+
const matcher_helpers_1 = require("../utils/matcher-helpers");
|
|
7
|
+
/**
|
|
8
|
+
* Matche un pattern Element (ng-content, div, etc.)
|
|
9
|
+
*/
|
|
10
|
+
function matchesElementPattern(node, pattern, htmlContent) {
|
|
11
|
+
// Vérifier si c'est un Element ou Content (ng-content)
|
|
12
|
+
const isContentOrElement = node.constructor &&
|
|
13
|
+
(node.constructor.name === 'Element' || node.constructor.name === 'Content');
|
|
14
|
+
if (!isContentOrElement || !node.name) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const patternNames = (0, matcher_helpers_1.ensureArray)(pattern.name);
|
|
18
|
+
if (!patternNames.includes(node.name)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
// Vérifier hasChildren si spécifié
|
|
22
|
+
if (pattern.hasChildren !== undefined) {
|
|
23
|
+
// Filtrer les enfants pour exclure les whitespace-only Text nodes
|
|
24
|
+
const nonWhitespaceChildren = node.children?.filter((child) => {
|
|
25
|
+
// Si c'est un Text node, vérifier s'il contient autre chose que du whitespace
|
|
26
|
+
if (child.constructor?.name === 'Text') {
|
|
27
|
+
return child.value && child.value.trim().length > 0;
|
|
28
|
+
}
|
|
29
|
+
// Les autres types de nœuds (Element, Comment, etc.) sont toujours considérés
|
|
30
|
+
return true;
|
|
31
|
+
}) || [];
|
|
32
|
+
const hasChildren = nonWhitespaceChildren.length > 0;
|
|
33
|
+
if (pattern.hasChildren !== hasChildren) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
lineNumber: (0, html_parser_1.getLineNumber)(node, htmlContent),
|
|
39
|
+
matchedText: (0, html_parser_1.getNodeText)(node, htmlContent)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Matche un pattern DeferredBlock (@defer blocks)
|
|
44
|
+
*/
|
|
45
|
+
function matchesDeferredBlockPattern(node, htmlContent) {
|
|
46
|
+
// 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
|
+
};
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseHtmlTemplate = parseHtmlTemplate;
|
|
4
|
+
exports.getLineNumber = getLineNumber;
|
|
5
|
+
exports.getNodeText = getNodeText;
|
|
6
|
+
exports.isElement = isElement;
|
|
7
|
+
const compiler_1 = require("@angular/compiler");
|
|
8
|
+
/**
|
|
9
|
+
* Parse un template HTML Angular et retourne l'AST
|
|
10
|
+
* NOTE: Retourne les nœuds même en présence d'erreurs de parsing
|
|
11
|
+
* (certaines erreurs sont précisément ce qu'on cherche à détecter)
|
|
12
|
+
* Support complet Angular 18-20: @if, @for, @switch, @defer, @let
|
|
13
|
+
*/
|
|
14
|
+
function parseHtmlTemplate(htmlContent) {
|
|
15
|
+
try {
|
|
16
|
+
const parsed = (0, compiler_1.parseTemplate)(htmlContent, '', {
|
|
17
|
+
preserveWhitespaces: false,
|
|
18
|
+
interpolationConfig: undefined,
|
|
19
|
+
enableBlockSyntax: true, // Support @if, @for, @switch, @defer
|
|
20
|
+
enableLetSyntax: true // Support @let
|
|
21
|
+
});
|
|
22
|
+
return parsed.nodes;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Trouve le numéro de ligne d'un nœud dans le template
|
|
30
|
+
*/
|
|
31
|
+
function getLineNumber(node, htmlContent) {
|
|
32
|
+
if (node.sourceSpan && node.sourceSpan.start && node.sourceSpan.start.line !== undefined) {
|
|
33
|
+
return node.sourceSpan.start.line + 1; // Angular utilise 0-indexed
|
|
34
|
+
}
|
|
35
|
+
// Fallback: estimer via l'offset
|
|
36
|
+
if (node.sourceSpan && node.sourceSpan.start && node.sourceSpan.start.offset !== undefined) {
|
|
37
|
+
const textBeforeNode = htmlContent.substring(0, node.sourceSpan.start.offset);
|
|
38
|
+
return textBeforeNode.split('\n').length;
|
|
39
|
+
}
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Extrait le texte d'un nœud
|
|
44
|
+
*/
|
|
45
|
+
function getNodeText(node, htmlContent) {
|
|
46
|
+
if (node.sourceSpan && node.sourceSpan.start && node.sourceSpan.end) {
|
|
47
|
+
const start = node.sourceSpan.start.offset;
|
|
48
|
+
const end = node.sourceSpan.end.offset;
|
|
49
|
+
return htmlContent.substring(start, end).trim();
|
|
50
|
+
}
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Vérifie si un nœud est un Element
|
|
55
|
+
*/
|
|
56
|
+
function isElement(node) {
|
|
57
|
+
return 'attributes' in node && 'inputs' in node;
|
|
58
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchesPipePattern = matchesPipePattern;
|
|
4
|
+
exports.traversePipeExpression = traversePipeExpression;
|
|
5
|
+
const html_parser_1 = require("./html-parser");
|
|
6
|
+
/**
|
|
7
|
+
* Extrait la valeur d'un argument de pipe
|
|
8
|
+
*/
|
|
9
|
+
function getPipeArgumentValue(arg) {
|
|
10
|
+
if (!arg)
|
|
11
|
+
return '';
|
|
12
|
+
// LiteralPrimitive (string literal dans l'AST)
|
|
13
|
+
if ('value' in arg && typeof arg.value === 'string') {
|
|
14
|
+
return arg.value;
|
|
15
|
+
}
|
|
16
|
+
// Pour autres types d'expressions, utiliser source si disponible
|
|
17
|
+
if ('source' in arg && typeof arg.source === 'string') {
|
|
18
|
+
return arg.source;
|
|
19
|
+
}
|
|
20
|
+
// Fallback: essayer d'extraire le texte brut
|
|
21
|
+
if (typeof arg === 'string') {
|
|
22
|
+
return arg;
|
|
23
|
+
}
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Matche un pattern PipeExpression (| async, | date, etc.)
|
|
28
|
+
*/
|
|
29
|
+
function matchesPipePattern(node, pattern, htmlContent) {
|
|
30
|
+
if (!pattern.pipeName) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
// Vérifier si c'est un BindingPipe avec le bon nom
|
|
34
|
+
if (!node.name || node.name !== pattern.pipeName) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
// Si le pattern spécifie des contraintes sur les arguments de pipe
|
|
38
|
+
if (pattern.pipeArgs && pattern.pipeArgs.formatMatches) {
|
|
39
|
+
// Vérifier que la pipe a des arguments
|
|
40
|
+
if (!node.args || node.args.length === 0) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const firstArgValue = getPipeArgumentValue(node.args[0]);
|
|
44
|
+
const formatRegex = new RegExp(pattern.pipeArgs.formatMatches);
|
|
45
|
+
// Matcher uniquement si le format correspond
|
|
46
|
+
if (!formatRegex.test(firstArgValue)) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
lineNumber: (0, html_parser_1.getLineNumber)(node, htmlContent),
|
|
51
|
+
matchedText: (0, html_parser_1.getNodeText)(node, htmlContent).substring(0, 100)
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Pas de contrainte sur les args, match basé sur le nom de pipe seul
|
|
55
|
+
const nodeText = (0, html_parser_1.getNodeText)(node, htmlContent);
|
|
56
|
+
const pipeRegex = new RegExp(`\\|\\s*${pattern.pipeName}\\b`);
|
|
57
|
+
if (pipeRegex.test(nodeText)) {
|
|
58
|
+
return {
|
|
59
|
+
lineNumber: (0, html_parser_1.getLineNumber)(node, htmlContent),
|
|
60
|
+
matchedText: nodeText.substring(0, 100)
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Parcourt récursivement une expression AST à la recherche de BindingPipe
|
|
67
|
+
*/
|
|
68
|
+
function traversePipeExpression(expr, pattern, htmlContent, matches) {
|
|
69
|
+
if (!expr)
|
|
70
|
+
return;
|
|
71
|
+
// Si pattern cherche une PipeExpression, chercher BindingPipe
|
|
72
|
+
if (pattern.nodeType === 'PipeExpression' && pattern.pipeName) {
|
|
73
|
+
if (expr.constructor && expr.constructor.name === 'BindingPipe') {
|
|
74
|
+
const match = matchesPipePattern(expr, pattern, htmlContent);
|
|
75
|
+
if (match) {
|
|
76
|
+
matches.push(match);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Parcourir les expressions d'une Interpolation
|
|
81
|
+
if (expr.expressions && Array.isArray(expr.expressions)) {
|
|
82
|
+
for (const subExpr of expr.expressions) {
|
|
83
|
+
traversePipeExpression(subExpr, pattern, htmlContent, matches);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Parcourir récursivement les sous-expressions
|
|
87
|
+
if (expr.exp) {
|
|
88
|
+
traversePipeExpression(expr.exp, pattern, htmlContent, matches);
|
|
89
|
+
}
|
|
90
|
+
if (expr.args && Array.isArray(expr.args)) {
|
|
91
|
+
for (const arg of expr.args) {
|
|
92
|
+
traversePipeExpression(arg, pattern, htmlContent, matches);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchesBoundTextPattern = matchesBoundTextPattern;
|
|
4
|
+
const html_parser_1 = require("./html-parser");
|
|
5
|
+
/**
|
|
6
|
+
* Matche un pattern BoundText (interpolations {{ ... }})
|
|
7
|
+
*/
|
|
8
|
+
function matchesBoundTextPattern(node, pattern, htmlContent) {
|
|
9
|
+
// Vérifier si c'est bien un BoundText (interpolation) et non un Text simple
|
|
10
|
+
if (!('value' in node) || !node.value || typeof node.value !== 'object') {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
// Vérifier textMatches si spécifié
|
|
14
|
+
if (!pattern.textMatches) {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
const textRegex = new RegExp(pattern.textMatches);
|
|
18
|
+
// value.ast contient l'AST de l'expression (Interpolation, etc.)
|
|
19
|
+
const ast = node.value.ast;
|
|
20
|
+
// Si l'AST est une Interpolation avec expressions
|
|
21
|
+
if (ast && ast.expressions && Array.isArray(ast.expressions)) {
|
|
22
|
+
for (const expr of ast.expressions) {
|
|
23
|
+
// Pour LiteralPrimitive (mots-clés comme 'in')
|
|
24
|
+
if (expr.value !== undefined && typeof expr.value === 'string') {
|
|
25
|
+
if (textRegex.test(expr.value)) {
|
|
26
|
+
return {
|
|
27
|
+
lineNumber: (0, html_parser_1.getLineNumber)(node, htmlContent),
|
|
28
|
+
matchedText: (0, html_parser_1.getNodeText)(node, htmlContent)
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Pour VoidExpression (Angular parse 'void' comme opérateur)
|
|
33
|
+
if (expr.constructor && expr.constructor.name === 'VoidExpression') {
|
|
34
|
+
if (textRegex.test('void')) {
|
|
35
|
+
return {
|
|
36
|
+
lineNumber: (0, html_parser_1.getLineNumber)(node, htmlContent),
|
|
37
|
+
matchedText: (0, html_parser_1.getNodeText)(node, htmlContent)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
// Fallback: tester le source complet
|
|
44
|
+
if ('source' in node.value && typeof node.value.source === 'string') {
|
|
45
|
+
if (textRegex.test(node.value.source)) {
|
|
46
|
+
return {
|
|
47
|
+
lineNumber: (0, html_parser_1.getLineNumber)(node, htmlContent),
|
|
48
|
+
matchedText: node.value.source
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.matchesHtmlPatternInComponent = void 0;
|
|
4
|
+
exports.findHtmlPatternMatches = findHtmlPatternMatches;
|
|
5
|
+
exports.isHtmlPattern = isHtmlPattern;
|
|
6
|
+
const html_parser_1 = require("./html-parser");
|
|
7
|
+
const html_attribute_matcher_1 = require("./html-attribute-matcher");
|
|
8
|
+
const html_element_matcher_1 = require("./html-element-matcher");
|
|
9
|
+
const html_text_matcher_1 = require("./html-text-matcher");
|
|
10
|
+
const html_pipe_matcher_1 = require("./html-pipe-matcher");
|
|
11
|
+
/**
|
|
12
|
+
* Vérifie si un nœud HTML correspond à un pattern AST HTML
|
|
13
|
+
* Supporte : Attribute, PipeExpression, BoundText, Element, DeferredBlock
|
|
14
|
+
*/
|
|
15
|
+
function matchesHtmlNode(node, pattern, htmlContent) {
|
|
16
|
+
// Pattern: BoundText (interpolations {{ ... }})
|
|
17
|
+
if (pattern.nodeType === 'BoundText') {
|
|
18
|
+
return (0, html_text_matcher_1.matchesBoundTextPattern)(node, pattern, htmlContent);
|
|
19
|
+
}
|
|
20
|
+
// Pattern: Attribute (*ngIf, *ngFor, etc.)
|
|
21
|
+
if (pattern.nodeType === 'Attribute' && (0, html_parser_1.isElement)(node)) {
|
|
22
|
+
return (0, html_attribute_matcher_1.matchesAttributePattern)(node, pattern, htmlContent);
|
|
23
|
+
}
|
|
24
|
+
// Pattern: Element (ng-content, div, etc.)
|
|
25
|
+
if (pattern.nodeType === 'Element') {
|
|
26
|
+
return (0, html_element_matcher_1.matchesElementPattern)(node, pattern, htmlContent);
|
|
27
|
+
}
|
|
28
|
+
// Pattern: DeferredBlock (@defer blocks)
|
|
29
|
+
if (pattern.nodeType === 'DeferredBlock') {
|
|
30
|
+
return (0, html_element_matcher_1.matchesDeferredBlockPattern)(node, htmlContent);
|
|
31
|
+
}
|
|
32
|
+
// Pattern: PipeExpression (| async, | date, etc.)
|
|
33
|
+
if (pattern.nodeType === 'PipeExpression' && pattern.pipeName) {
|
|
34
|
+
return (0, html_pipe_matcher_1.matchesPipePattern)(node, pattern, htmlContent);
|
|
35
|
+
}
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Parcourt récursivement l'AST HTML et trouve les matches
|
|
40
|
+
*/
|
|
41
|
+
function traverseHtmlAst(nodes, pattern, htmlContent, matches) {
|
|
42
|
+
for (const node of nodes) {
|
|
43
|
+
// Vérifier si le nœud matche
|
|
44
|
+
const match = matchesHtmlNode(node, pattern, htmlContent);
|
|
45
|
+
if (match) {
|
|
46
|
+
matches.push(match);
|
|
47
|
+
}
|
|
48
|
+
// Si pattern cherche PipeExpression et node est BoundText, parcourir son expression
|
|
49
|
+
if (pattern.nodeType === 'PipeExpression' && 'value' in node && node.value) {
|
|
50
|
+
const value = node.value;
|
|
51
|
+
// value est ASTWithSource, l'AST réel est dans value.ast
|
|
52
|
+
if (value.ast) {
|
|
53
|
+
(0, html_pipe_matcher_1.traversePipeExpression)(value.ast, pattern, htmlContent, matches);
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
(0, html_pipe_matcher_1.traversePipeExpression)(value, pattern, htmlContent, matches);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// 1. Parcourir les enfants standards
|
|
60
|
+
if ('children' in node && Array.isArray(node.children)) {
|
|
61
|
+
traverseHtmlAst(node.children, pattern, htmlContent, matches);
|
|
62
|
+
}
|
|
63
|
+
// 2. Parcourir les branches (@if, @else if, @else)
|
|
64
|
+
if ('branches' in node && Array.isArray(node.branches)) {
|
|
65
|
+
for (const branch of node.branches) {
|
|
66
|
+
if (branch.children && Array.isArray(branch.children)) {
|
|
67
|
+
traverseHtmlAst(branch.children, pattern, htmlContent, matches);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// 3. Parcourir les cases (@switch, @case, @default)
|
|
72
|
+
if ('cases' in node && Array.isArray(node.cases)) {
|
|
73
|
+
for (const caseNode of node.cases) {
|
|
74
|
+
if (caseNode.children && Array.isArray(caseNode.children)) {
|
|
75
|
+
traverseHtmlAst(caseNode.children, pattern, htmlContent, matches);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// 4. Parcourir @for @empty
|
|
80
|
+
if ('empty' in node && node.empty?.children) {
|
|
81
|
+
traverseHtmlAst(node.empty.children, pattern, htmlContent, matches);
|
|
82
|
+
}
|
|
83
|
+
// 5-8. Parcourir @defer sub-blocks (mainBlock, placeholder, loading, error)
|
|
84
|
+
const deferSubBlocks = ['mainBlock', 'placeholder', 'loading', 'error'];
|
|
85
|
+
for (const blockName of deferSubBlocks) {
|
|
86
|
+
if (blockName in node && node[blockName]?.children) {
|
|
87
|
+
traverseHtmlAst(node[blockName].children, pattern, htmlContent, matches);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Trouve les matches d'un pattern AST HTML dans un template
|
|
94
|
+
* @param htmlContent Contenu du template HTML
|
|
95
|
+
* @param pattern Pattern AST à rechercher
|
|
96
|
+
* @returns Liste des matches trouvés
|
|
97
|
+
*/
|
|
98
|
+
function findHtmlPatternMatches(htmlContent, pattern) {
|
|
99
|
+
const matches = [];
|
|
100
|
+
// Parser le template HTML
|
|
101
|
+
const ast = (0, html_parser_1.parseHtmlTemplate)(htmlContent);
|
|
102
|
+
// Parcourir l'AST et trouver les matches
|
|
103
|
+
traverseHtmlAst(ast, pattern, htmlContent, matches);
|
|
104
|
+
return matches;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Vérifie si un pattern est un pattern HTML
|
|
108
|
+
* (nécessite @angular/compiler)
|
|
109
|
+
*/
|
|
110
|
+
function isHtmlPattern(pattern) {
|
|
111
|
+
return pattern.nodeType === 'Attribute' ||
|
|
112
|
+
pattern.nodeType === 'PipeExpression' ||
|
|
113
|
+
pattern.nodeType === 'BoundText' ||
|
|
114
|
+
pattern.nodeType === 'Element' ||
|
|
115
|
+
pattern.nodeType === 'DeferredBlock';
|
|
116
|
+
}
|
|
117
|
+
var html_component_matcher_1 = require("./html-component-matcher");
|
|
118
|
+
Object.defineProperty(exports, "matchesHtmlPatternInComponent", { enumerable: true, get: function () { return html_component_matcher_1.matchesHtmlPatternInComponent; } });
|