@silvestv/migration-planificator 6.0.0 → 6.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/dist/src/core/app-analyzer.js +10 -2
  2. package/dist/src/core/ast/matchers/html/html-template-ref-collector.js +95 -0
  3. package/dist/src/core/ast/matchers/html/html-text-matcher.js +85 -32
  4. package/dist/src/core/ast/matchers/html/index.js +16 -10
  5. package/dist/src/core/ast/matchers/index.js +44 -0
  6. package/dist/src/core/ast/matchers/ts/collection-matcher.js +4 -0
  7. package/dist/src/core/ast/matchers/ts/context-matcher.js +7 -4
  8. package/dist/src/core/ast/matchers/ts/decorator-matcher.js +145 -144
  9. package/dist/src/core/ast/matchers/ts/file-matcher.js +199 -8
  10. package/dist/src/core/ast/matchers/ts/host-binding-property-matcher.js +327 -0
  11. package/dist/src/core/ast/matchers/ts/mutation-matcher.js +248 -0
  12. package/dist/src/core/ast/matchers/ts/node-matcher.js +17 -1
  13. package/dist/src/core/ast/matchers/ts/symbol-matcher.js +98 -2
  14. package/dist/src/core/ast/matchers/ts/type-matcher.js +5 -2
  15. package/dist/src/core/ast/matchers/utils/matcher-helpers.js +60 -0
  16. package/dist/src/core/ast/matchers/utils/template-cache.js +50 -0
  17. package/dist/src/core/project-detector.js +22 -10
  18. package/dist/src/core/project-strategy/nx-strategy.js +25 -22
  19. package/dist/src/data/angular-migration-rules.json +32 -57
  20. package/dist/src/data/rules/rearchitecture/rearchitecture-rules.json +30 -0
  21. package/dist/src/data/rules/to18/rules-18-obligatoire.json +8 -14
  22. package/dist/src/data/rules/to18/rules-18-optionnelle.json +0 -56
  23. package/dist/src/data/rules/to18/rules-18-recommande.json +32 -139
  24. package/dist/src/data/rules/to19/rules-19-obligatoire.json +4 -1
  25. package/dist/src/data/rules/to19/rules-19-optionnelle.json +3 -0
  26. package/dist/src/data/rules/to19/rules-19-recommande.json +30 -2
  27. package/dist/src/data/rules/to20/rules-20-optionnelle.json +0 -35
  28. package/dist/src/data/rules/to20/rules-20-recommande.json +44 -36
  29. package/dist/src/data/rules/to21/rules-21-obligatoire.json +23 -10
  30. package/package.json +1 -1
@@ -97,9 +97,17 @@ function extractPackageDeps(projectPath) {
97
97
  * Résout le chemin d'un target (app ou lib) selon le type de projet - DRY
98
98
  */
99
99
  function resolveTargetPath(targetName, projectPath, isNxMonorepo, nxFolder, angularJson) {
100
- // Nx : apps/xxx ou libs/xxx
100
+ // Nx : apps/xxx ou libs/xxx (ou src/ pour single-app)
101
101
  if (isNxMonorepo) {
102
- return path.join(projectPath, nxFolder, targetName);
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;
103
111
  }
104
112
  // Angular CLI : lire depuis angular.json si disponible
105
113
  const projectConfig = angularJson?.projects?.[targetName];
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.collectTemplateReferences = collectTemplateReferences;
4
+ const DEFER_SUB_BLOCKS = ['mainBlock', 'placeholder', 'loading', 'error'];
5
+ /**
6
+ * Collecte toutes les template reference variables (#xxx) d'un template HTML
7
+ * Parcourt récursivement l'AST pour extraire les noms de références
8
+ */
9
+ function collectTemplateReferences(nodes) {
10
+ const references = new Set();
11
+ traverse(nodes, references);
12
+ return references;
13
+ }
14
+ /**
15
+ * Parcourt récursivement les noeuds HTML pour collecter les références
16
+ */
17
+ function traverse(nodeList, references) {
18
+ for (const node of nodeList) {
19
+ if (!node || typeof node !== 'object')
20
+ continue;
21
+ const anyNode = node;
22
+ // Element nodes ont un tableau references contenant les #xxx
23
+ extractReferencesFromNode(anyNode, references);
24
+ // Parcourir récursivement les enfants
25
+ traverseChildren(anyNode, references);
26
+ traverseBranches(anyNode, references);
27
+ traverseCases(anyNode, references);
28
+ traverseEmpty(anyNode, references);
29
+ traverseDeferBlocks(anyNode, references);
30
+ }
31
+ }
32
+ /**
33
+ * Extrait les références (#xxx) d'un noeud Element
34
+ */
35
+ function extractReferencesFromNode(node, references) {
36
+ if (!Array.isArray(node.references))
37
+ return;
38
+ for (const ref of node.references) {
39
+ if (ref && typeof ref === 'object' && 'name' in ref && typeof ref.name === 'string') {
40
+ references.add(ref.name);
41
+ }
42
+ }
43
+ }
44
+ /**
45
+ * Parcourt les enfants directs d'un noeud
46
+ */
47
+ function traverseChildren(node, references) {
48
+ if (Array.isArray(node.children)) {
49
+ traverse(node.children, references);
50
+ }
51
+ }
52
+ /**
53
+ * Parcourt les branches (@if, @else if, @else)
54
+ */
55
+ function traverseBranches(node, references) {
56
+ if (!Array.isArray(node.branches))
57
+ return;
58
+ for (const branch of node.branches) {
59
+ if (branch && typeof branch === 'object' && Array.isArray(branch.children)) {
60
+ traverse(branch.children, references);
61
+ }
62
+ }
63
+ }
64
+ /**
65
+ * Parcourt les cases (@switch, @case, @default)
66
+ */
67
+ function traverseCases(node, references) {
68
+ if (!Array.isArray(node.cases))
69
+ return;
70
+ for (const caseNode of node.cases) {
71
+ if (caseNode && typeof caseNode === 'object' && Array.isArray(caseNode.children)) {
72
+ traverse(caseNode.children, references);
73
+ }
74
+ }
75
+ }
76
+ /**
77
+ * Parcourt @for @empty
78
+ */
79
+ function traverseEmpty(node, references) {
80
+ const empty = node.empty;
81
+ if (empty && Array.isArray(empty.children)) {
82
+ traverse(empty.children, references);
83
+ }
84
+ }
85
+ /**
86
+ * Parcourt les sous-blocs @defer (mainBlock, placeholder, loading, error)
87
+ */
88
+ function traverseDeferBlocks(node, references) {
89
+ for (const blockName of DEFER_SUB_BLOCKS) {
90
+ const block = node[blockName];
91
+ if (block && Array.isArray(block.children)) {
92
+ traverse(block.children, references);
93
+ }
94
+ }
95
+ }
@@ -2,51 +2,104 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.matchesBoundTextPattern = matchesBoundTextPattern;
4
4
  const html_parser_1 = require("./html-parser");
5
+ const DEFAULT_TEMPLATE_REF_EXTRACT_PATTERN = '\\bthis\\.(\\w+)';
5
6
  /**
6
7
  * Matche un pattern BoundText (interpolations {{ ... }})
8
+ * @param node Noeud HTML à vérifier
9
+ * @param pattern Pattern AST à matcher
10
+ * @param htmlContent Contenu HTML complet (pour line numbers)
11
+ * @param templateRefs Set des template references (#xxx) du template (optionnel)
7
12
  */
8
- function matchesBoundTextPattern(node, pattern, htmlContent) {
13
+ function matchesBoundTextPattern(node, pattern, htmlContent, templateRefs) {
9
14
  // 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') {
15
+ if (!isValidBoundTextNode(node)) {
11
16
  return null;
12
17
  }
13
18
  // Vérifier textMatches si spécifié
14
19
  if (!pattern.textMatches) {
15
20
  return null;
16
21
  }
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
- }
22
+ const nodeValue = node.value;
23
+ const source = nodeValue.source;
24
+ // Validation templateRefVariable : vérifier que this.xxx est une template ref
25
+ if (pattern.templateRefVariable) {
26
+ if (!isValidTemplateRefMatch(source, pattern, templateRefs)) {
27
+ return null;
41
28
  }
42
29
  }
30
+ const textRegex = new RegExp(pattern.textMatches);
31
+ // Tester via AST expressions
32
+ const astMatch = matchViaAstExpressions(nodeValue.ast, textRegex, node, htmlContent);
33
+ if (astMatch) {
34
+ return astMatch;
35
+ }
43
36
  // 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
- };
37
+ if (source && textRegex.test(source)) {
38
+ return {
39
+ lineNumber: (0, html_parser_1.getLineNumber)(node, htmlContent),
40
+ matchedText: source
41
+ };
42
+ }
43
+ return null;
44
+ }
45
+ /**
46
+ * Vérifie si le noeud est un BoundText valide avec une value object
47
+ */
48
+ function isValidBoundTextNode(node) {
49
+ if (!node || typeof node !== 'object')
50
+ return false;
51
+ const nodeObj = node;
52
+ return 'value' in nodeObj && nodeObj.value !== null && typeof nodeObj.value === 'object';
53
+ }
54
+ /**
55
+ * Vérifie que this.xxx référence bien une template reference variable
56
+ * Retourne true si la validation passe (match autorisé), false sinon
57
+ */
58
+ function isValidTemplateRefMatch(source, pattern, templateRefs) {
59
+ // Si pas de templateRefs fourni, on ne peut pas valider -> bloquer le match
60
+ if (!templateRefs || !source) {
61
+ return false;
62
+ }
63
+ const extractPattern = pattern.templateRefVariable?.extractPattern || DEFAULT_TEMPLATE_REF_EXTRACT_PATTERN;
64
+ const extractRegex = new RegExp(extractPattern);
65
+ const match = extractRegex.exec(source);
66
+ // Si pas de this.xxx trouvé ou variable pas dans les template refs -> pas de match
67
+ if (!match || !match[1]) {
68
+ return false;
69
+ }
70
+ const varName = match[1];
71
+ return templateRefs.has(varName);
72
+ }
73
+ /**
74
+ * Tente de matcher via les expressions AST (LiteralPrimitive, VoidExpression)
75
+ */
76
+ function matchViaAstExpressions(ast, textRegex, node, htmlContent) {
77
+ if (!ast || typeof ast !== 'object')
78
+ return null;
79
+ const astObj = ast;
80
+ if (!astObj.expressions || !Array.isArray(astObj.expressions))
81
+ return null;
82
+ for (const expr of astObj.expressions) {
83
+ if (!expr || typeof expr !== 'object')
84
+ continue;
85
+ const exprObj = expr;
86
+ // Pour LiteralPrimitive (mots-clés comme 'in')
87
+ if (exprObj.value !== undefined && typeof exprObj.value === 'string') {
88
+ if (textRegex.test(exprObj.value)) {
89
+ return {
90
+ lineNumber: (0, html_parser_1.getLineNumber)(node, htmlContent),
91
+ matchedText: (0, html_parser_1.getNodeText)(node, htmlContent)
92
+ };
93
+ }
94
+ }
95
+ // Pour VoidExpression (Angular parse 'void' comme opérateur)
96
+ if (exprObj.constructor?.name === 'VoidExpression') {
97
+ if (textRegex.test('void')) {
98
+ return {
99
+ lineNumber: (0, html_parser_1.getLineNumber)(node, htmlContent),
100
+ matchedText: (0, html_parser_1.getNodeText)(node, htmlContent)
101
+ };
102
+ }
50
103
  }
51
104
  }
52
105
  return null;
@@ -9,14 +9,15 @@ const html_attribute_matcher_1 = require("./html-attribute-matcher");
9
9
  const html_element_matcher_1 = require("./html-element-matcher");
10
10
  const html_text_matcher_1 = require("./html-text-matcher");
11
11
  const html_pipe_matcher_1 = require("./html-pipe-matcher");
12
+ const html_template_ref_collector_1 = require("./html-template-ref-collector");
12
13
  /**
13
14
  * Vérifie si un nœud HTML correspond à un pattern AST HTML
14
15
  * Supporte : Attribute, BoundAttribute, PipeExpression, BoundText, Element, DeferredBlock
15
16
  */
16
- function matchesHtmlNode(node, pattern, htmlContent) {
17
+ function matchesHtmlNode(node, pattern, htmlContent, templateRefs) {
17
18
  // Pattern: BoundText (interpolations {{ ... }})
18
19
  if (pattern.nodeType === 'BoundText') {
19
- return (0, html_text_matcher_1.matchesBoundTextPattern)(node, pattern, htmlContent);
20
+ return (0, html_text_matcher_1.matchesBoundTextPattern)(node, pattern, htmlContent, templateRefs);
20
21
  }
21
22
  // Pattern: Attribute (*ngIf, *ngFor, etc.) ou BoundAttribute ([ngClass], [ngStyle], etc.)
22
23
  if ((pattern.nodeType === 'Attribute' || pattern.nodeType === 'BoundAttribute') && (0, html_parser_1.isElement)(node)) {
@@ -39,10 +40,10 @@ function matchesHtmlNode(node, pattern, htmlContent) {
39
40
  /**
40
41
  * Parcourt récursivement l'AST HTML et trouve les matches
41
42
  */
42
- function traverseHtmlAst(nodes, pattern, htmlContent, matches) {
43
+ function traverseHtmlAst(nodes, pattern, htmlContent, matches, templateRefs) {
43
44
  for (const node of nodes) {
44
45
  // Vérifier si le nœud matche
45
- const match = matchesHtmlNode(node, pattern, htmlContent);
46
+ const match = matchesHtmlNode(node, pattern, htmlContent, templateRefs);
46
47
  if (match) {
47
48
  matches.push(match);
48
49
  }
@@ -79,13 +80,13 @@ function traverseHtmlAst(nodes, pattern, htmlContent, matches) {
79
80
  }
80
81
  // 1. Parcourir les enfants standards
81
82
  if ('children' in node && Array.isArray(node.children)) {
82
- traverseHtmlAst(node.children, pattern, htmlContent, matches);
83
+ traverseHtmlAst(node.children, pattern, htmlContent, matches, templateRefs);
83
84
  }
84
85
  // 2. Parcourir les branches (@if, @else if, @else)
85
86
  if ('branches' in node && Array.isArray(node.branches)) {
86
87
  for (const branch of node.branches) {
87
88
  if (branch.children && Array.isArray(branch.children)) {
88
- traverseHtmlAst(branch.children, pattern, htmlContent, matches);
89
+ traverseHtmlAst(branch.children, pattern, htmlContent, matches, templateRefs);
89
90
  }
90
91
  }
91
92
  }
@@ -93,13 +94,13 @@ function traverseHtmlAst(nodes, pattern, htmlContent, matches) {
93
94
  if ('cases' in node && Array.isArray(node.cases)) {
94
95
  for (const caseNode of node.cases) {
95
96
  if (caseNode.children && Array.isArray(caseNode.children)) {
96
- traverseHtmlAst(caseNode.children, pattern, htmlContent, matches);
97
+ traverseHtmlAst(caseNode.children, pattern, htmlContent, matches, templateRefs);
97
98
  }
98
99
  }
99
100
  }
100
101
  // 4. Parcourir @for @empty
101
102
  if ('empty' in node && node.empty?.children) {
102
- traverseHtmlAst(node.empty.children, pattern, htmlContent, matches);
103
+ traverseHtmlAst(node.empty.children, pattern, htmlContent, matches, templateRefs);
103
104
  }
104
105
  // 4b. Parcourir @for expression (for PipeExpression like | keyvalue)
105
106
  if (pattern.nodeType === 'PipeExpression' && 'expression' in node && node.expression) {
@@ -116,7 +117,7 @@ function traverseHtmlAst(nodes, pattern, htmlContent, matches) {
116
117
  const deferSubBlocks = ['mainBlock', 'placeholder', 'loading', 'error'];
117
118
  for (const blockName of deferSubBlocks) {
118
119
  if (blockName in node && node[blockName]?.children) {
119
- traverseHtmlAst(node[blockName].children, pattern, htmlContent, matches);
120
+ traverseHtmlAst(node[blockName].children, pattern, htmlContent, matches, templateRefs);
120
121
  }
121
122
  }
122
123
  }
@@ -131,8 +132,13 @@ function findHtmlPatternMatches(htmlContent, pattern) {
131
132
  const matches = [];
132
133
  // Parser le template HTML
133
134
  const ast = (0, html_parser_1.parseHtmlTemplate)(htmlContent);
135
+ // Collecter les template refs si le pattern utilise templateRefVariable
136
+ let templateRefs;
137
+ if (pattern.templateRefVariable) {
138
+ templateRefs = (0, html_template_ref_collector_1.collectTemplateReferences)(ast);
139
+ }
134
140
  // Parcourir l'AST et trouver les matches
135
- traverseHtmlAst(ast, pattern, htmlContent, matches);
141
+ traverseHtmlAst(ast, pattern, htmlContent, matches, templateRefs);
136
142
  return matches;
137
143
  }
138
144
  /**
@@ -61,6 +61,8 @@ exports.SymbolMatcher = SymbolMatcher;
61
61
  const HtmlMatcher = __importStar(require("./html"));
62
62
  exports.HtmlMatcher = HtmlMatcher;
63
63
  const html_pipe_variable_matcher_1 = require("./html/html-pipe-variable-matcher");
64
+ const host_binding_property_matcher_1 = require("./ts/host-binding-property-matcher");
65
+ const mutation_matcher_1 = require("./ts/mutation-matcher");
64
66
  /**
65
67
  * Fonction principale : vérifie si un nœud correspond à un pattern AST
66
68
  * OPTIMISÉ : excludeContext en premier (88% des règles l'utilisent)
@@ -206,12 +208,25 @@ function matchesAstPattern(node, pattern) {
206
208
  return false;
207
209
  }
208
210
  }
211
+ // Vérifier excludeModifiers
212
+ if (pattern.excludeModifiers !== undefined) {
213
+ if (!NodeMatcher.excludesModifiers(node, pattern.excludeModifiers)) {
214
+ return false;
215
+ }
216
+ }
209
217
  // Vérifier exported
210
218
  if (pattern.exported !== undefined) {
211
219
  if (!NodeMatcher.matchesExported(node, pattern.exported)) {
212
220
  return false;
213
221
  }
214
222
  }
223
+ // Vérifier isMutatedInClass (pour PropertyDeclaration)
224
+ if (pattern.isMutatedInClass !== undefined) {
225
+ const mutated = (0, mutation_matcher_1.isMutatedInClass)(node);
226
+ if (mutated !== pattern.isMutatedInClass) {
227
+ return false;
228
+ }
229
+ }
215
230
  // Vérifier parameters
216
231
  if (pattern.parameters !== undefined) {
217
232
  if (!DecoratorMatcher.matchesParameters(node, pattern.parameters)) {
@@ -377,6 +392,35 @@ function matchesAstPattern(node, pattern) {
377
392
  return false;
378
393
  }
379
394
  }
395
+ // Vérifier hostBindingProperty (résolution host binding → TS property → vérification wrappers)
396
+ if (pattern.hostBindingProperty !== undefined) {
397
+ if (!(0, host_binding_property_matcher_1.matchesHostBindingProperty)(node, pattern.hostBindingProperty)) {
398
+ return false;
399
+ }
400
+ }
401
+ // Vérifier fileMissing (identifiant absent de tout le fichier)
402
+ if (pattern.fileMissing !== undefined) {
403
+ const sourceFile = node.getSourceFile();
404
+ if (!FileMatcher.matchesFileMissing(sourceFile, pattern.fileMissing)) {
405
+ return false;
406
+ }
407
+ }
408
+ // Vérifier projectFileContains (cross-file avec fichiers config)
409
+ if (pattern.projectFileContains !== undefined) {
410
+ const sourceFile = node.getSourceFile();
411
+ if (!FileMatcher.matchesProjectFileContains(sourceFile, pattern.projectFileContains)) {
412
+ return false;
413
+ }
414
+ }
415
+ // Vérifier hasHostBindingDecorators (@HostBinding, @HostListener, ou host object literal dynamique)
416
+ // Utilise hasAnyHostBindings() de host-binding-property-matcher.ts (DRY)
417
+ if (pattern.hasHostBindingDecorators !== undefined) {
418
+ if (pattern.hasHostBindingDecorators === true) {
419
+ if (!(0, host_binding_property_matcher_1.hasAnyHostBindings)(node)) {
420
+ return false;
421
+ }
422
+ }
423
+ }
380
424
  return true;
381
425
  }
382
426
  // Export des fonctions utilitaires de cache
@@ -38,6 +38,10 @@ function matchesElements(node, elementsPattern, matchesAstPatternFn) {
38
38
  if (elementsPattern.contains) {
39
39
  return elements.some(element => matchesAstPatternFn(element, elementsPattern.contains));
40
40
  }
41
+ // Vérifier containsAny (au moins un élément matche l'un des patterns)
42
+ if (elementsPattern.containsAny && Array.isArray(elementsPattern.containsAny)) {
43
+ return elements.some(element => elementsPattern.containsAny.some((pattern) => matchesAstPatternFn(element, pattern)));
44
+ }
41
45
  // Cas où elementsPattern est directement un pattern AST
42
46
  return elements.some(element => matchesAstPatternFn(element, elementsPattern));
43
47
  }
@@ -190,11 +190,14 @@ function matchesNextProvider(node, nextProviderPattern) {
190
190
  if (currentIndex === -1) {
191
191
  return false;
192
192
  }
193
- // Vérifier missing : le provider ne doit PAS être présent dans les éléments restants
193
+ // Vérifier missing : le provider ne doit PAS être présent dans le tableau entier
194
194
  if (nextProviderPattern.missing) {
195
195
  const missingProviderName = nextProviderPattern.missing;
196
- // Vérifier tous les providers après le nœud courant
197
- for (let i = currentIndex + 1; i < elements.length; i++) {
196
+ // Vérifier tous les providers du tableau (pas seulement ceux après le nœud courant)
197
+ for (let i = 0; i < elements.length; i++) {
198
+ // Ignorer le nœud courant lui-même
199
+ if (i === currentIndex)
200
+ continue;
198
201
  const providerElement = elements[i];
199
202
  const providerText = providerElement.getText();
200
203
  // Vérifier si le provider manquant est présent
@@ -203,7 +206,7 @@ function matchesNextProvider(node, nextProviderPattern) {
203
206
  return false;
204
207
  }
205
208
  }
206
- // Le provider est bien absent de tous les providers restants
209
+ // Le provider est bien absent de tous les autres providers
207
210
  return true;
208
211
  }
209
212
  return true;