@depup/angular-eslint__eslint-plugin 21.3.1-depup.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.
Files changed (166) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/changes.json +10 -0
  4. package/dist/configs/README.md +44 -0
  5. package/dist/configs/all.json +54 -0
  6. package/dist/configs/recommended.json +18 -0
  7. package/dist/index.d.ts +233 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +145 -0
  10. package/dist/rules/component-class-suffix.d.ts +15 -0
  11. package/dist/rules/component-class-suffix.d.ts.map +1 -0
  12. package/dist/rules/component-class-suffix.js +56 -0
  13. package/dist/rules/component-max-inline-declarations.d.ts +17 -0
  14. package/dist/rules/component-max-inline-declarations.d.ts.map +1 -0
  15. package/dist/rules/component-max-inline-declarations.js +107 -0
  16. package/dist/rules/component-selector.d.ts +14 -0
  17. package/dist/rules/component-selector.d.ts.map +1 -0
  18. package/dist/rules/component-selector.js +218 -0
  19. package/dist/rules/computed-must-return.d.ts +11 -0
  20. package/dist/rules/computed-must-return.d.ts.map +1 -0
  21. package/dist/rules/computed-must-return.js +62 -0
  22. package/dist/rules/consistent-component-styles.d.ts +12 -0
  23. package/dist/rules/consistent-component-styles.d.ts.map +1 -0
  24. package/dist/rules/consistent-component-styles.js +97 -0
  25. package/dist/rules/contextual-decorator.d.ts +12 -0
  26. package/dist/rules/contextual-decorator.d.ts.map +1 -0
  27. package/dist/rules/contextual-decorator.js +61 -0
  28. package/dist/rules/contextual-lifecycle.d.ts +11 -0
  29. package/dist/rules/contextual-lifecycle.d.ts.map +1 -0
  30. package/dist/rules/contextual-lifecycle.js +61 -0
  31. package/dist/rules/directive-class-suffix.d.ts +13 -0
  32. package/dist/rules/directive-class-suffix.d.ts.map +1 -0
  33. package/dist/rules/directive-class-suffix.js +60 -0
  34. package/dist/rules/directive-selector.d.ts +14 -0
  35. package/dist/rules/directive-selector.d.ts.map +1 -0
  36. package/dist/rules/directive-selector.js +156 -0
  37. package/dist/rules/no-async-lifecycle-method.d.ts +11 -0
  38. package/dist/rules/no-async-lifecycle-method.d.ts.map +1 -0
  39. package/dist/rules/no-async-lifecycle-method.js +45 -0
  40. package/dist/rules/no-attribute-decorator.d.ts +11 -0
  41. package/dist/rules/no-attribute-decorator.d.ts.map +1 -0
  42. package/dist/rules/no-attribute-decorator.js +32 -0
  43. package/dist/rules/no-conflicting-lifecycle.d.ts +11 -0
  44. package/dist/rules/no-conflicting-lifecycle.d.ts.map +1 -0
  45. package/dist/rules/no-conflicting-lifecycle.js +74 -0
  46. package/dist/rules/no-developer-preview.d.ts +11 -0
  47. package/dist/rules/no-developer-preview.d.ts.map +1 -0
  48. package/dist/rules/no-developer-preview.js +45 -0
  49. package/dist/rules/no-duplicates-in-metadata-arrays.d.ts +11 -0
  50. package/dist/rules/no-duplicates-in-metadata-arrays.d.ts.map +1 -0
  51. package/dist/rules/no-duplicates-in-metadata-arrays.js +67 -0
  52. package/dist/rules/no-empty-lifecycle-method.d.ts +11 -0
  53. package/dist/rules/no-empty-lifecycle-method.d.ts.map +1 -0
  54. package/dist/rules/no-empty-lifecycle-method.js +75 -0
  55. package/dist/rules/no-experimental.d.ts +11 -0
  56. package/dist/rules/no-experimental.d.ts.map +1 -0
  57. package/dist/rules/no-experimental.js +45 -0
  58. package/dist/rules/no-forward-ref.d.ts +12 -0
  59. package/dist/rules/no-forward-ref.d.ts.map +1 -0
  60. package/dist/rules/no-forward-ref.js +33 -0
  61. package/dist/rules/no-implicit-take-until-destroyed.d.ts +11 -0
  62. package/dist/rules/no-implicit-take-until-destroyed.d.ts.map +1 -0
  63. package/dist/rules/no-implicit-take-until-destroyed.js +83 -0
  64. package/dist/rules/no-input-prefix.d.ts +13 -0
  65. package/dist/rules/no-input-prefix.d.ts.map +1 -0
  66. package/dist/rules/no-input-prefix.js +118 -0
  67. package/dist/rules/no-input-rename.d.ts +13 -0
  68. package/dist/rules/no-input-rename.d.ts.map +1 -0
  69. package/dist/rules/no-input-rename.js +213 -0
  70. package/dist/rules/no-inputs-metadata-property.d.ts +11 -0
  71. package/dist/rules/no-inputs-metadata-property.d.ts.map +1 -0
  72. package/dist/rules/no-inputs-metadata-property.js +53 -0
  73. package/dist/rules/no-lifecycle-call.d.ts +11 -0
  74. package/dist/rules/no-lifecycle-call.d.ts.map +1 -0
  75. package/dist/rules/no-lifecycle-call.js +49 -0
  76. package/dist/rules/no-output-native.d.ts +11 -0
  77. package/dist/rules/no-output-native.d.ts.map +1 -0
  78. package/dist/rules/no-output-native.js +47 -0
  79. package/dist/rules/no-output-on-prefix.d.ts +11 -0
  80. package/dist/rules/no-output-on-prefix.d.ts.map +1 -0
  81. package/dist/rules/no-output-on-prefix.js +48 -0
  82. package/dist/rules/no-output-rename.d.ts +11 -0
  83. package/dist/rules/no-output-rename.d.ts.map +1 -0
  84. package/dist/rules/no-output-rename.js +172 -0
  85. package/dist/rules/no-outputs-metadata-property.d.ts +11 -0
  86. package/dist/rules/no-outputs-metadata-property.d.ts.map +1 -0
  87. package/dist/rules/no-outputs-metadata-property.js +53 -0
  88. package/dist/rules/no-pipe-impure.d.ts +11 -0
  89. package/dist/rules/no-pipe-impure.d.ts.map +1 -0
  90. package/dist/rules/no-pipe-impure.js +42 -0
  91. package/dist/rules/no-queries-metadata-property.d.ts +11 -0
  92. package/dist/rules/no-queries-metadata-property.d.ts.map +1 -0
  93. package/dist/rules/no-queries-metadata-property.js +38 -0
  94. package/dist/rules/no-uncalled-signals.d.ts +12 -0
  95. package/dist/rules/no-uncalled-signals.d.ts.map +1 -0
  96. package/dist/rules/no-uncalled-signals.js +92 -0
  97. package/dist/rules/pipe-prefix.d.ts +15 -0
  98. package/dist/rules/pipe-prefix.d.ts.map +1 -0
  99. package/dist/rules/pipe-prefix.js +92 -0
  100. package/dist/rules/prefer-host-metadata-property.d.ts +10 -0
  101. package/dist/rules/prefer-host-metadata-property.d.ts.map +1 -0
  102. package/dist/rules/prefer-host-metadata-property.js +40 -0
  103. package/dist/rules/prefer-inject.d.ts +11 -0
  104. package/dist/rules/prefer-inject.d.ts.map +1 -0
  105. package/dist/rules/prefer-inject.js +86 -0
  106. package/dist/rules/prefer-on-push-component-change-detection.d.ts +11 -0
  107. package/dist/rules/prefer-on-push-component-change-detection.d.ts.map +1 -0
  108. package/dist/rules/prefer-on-push-component-change-detection.js +81 -0
  109. package/dist/rules/prefer-output-emitter-ref.d.ts +10 -0
  110. package/dist/rules/prefer-output-emitter-ref.d.ts.map +1 -0
  111. package/dist/rules/prefer-output-emitter-ref.js +30 -0
  112. package/dist/rules/prefer-output-readonly.d.ts +11 -0
  113. package/dist/rules/prefer-output-readonly.d.ts.map +1 -0
  114. package/dist/rules/prefer-output-readonly.js +51 -0
  115. package/dist/rules/prefer-signal-model.d.ts +10 -0
  116. package/dist/rules/prefer-signal-model.d.ts.map +1 -0
  117. package/dist/rules/prefer-signal-model.js +71 -0
  118. package/dist/rules/prefer-signals.d.ts +20 -0
  119. package/dist/rules/prefer-signals.d.ts.map +1 -0
  120. package/dist/rules/prefer-signals.js +174 -0
  121. package/dist/rules/prefer-standalone.d.ts +11 -0
  122. package/dist/rules/prefer-standalone.d.ts.map +1 -0
  123. package/dist/rules/prefer-standalone.js +66 -0
  124. package/dist/rules/relative-url-prefix.d.ts +11 -0
  125. package/dist/rules/relative-url-prefix.d.ts.map +1 -0
  126. package/dist/rules/relative-url-prefix.js +59 -0
  127. package/dist/rules/require-lifecycle-on-prototype.d.ts +11 -0
  128. package/dist/rules/require-lifecycle-on-prototype.d.ts.map +1 -0
  129. package/dist/rules/require-lifecycle-on-prototype.js +72 -0
  130. package/dist/rules/require-localize-metadata.d.ts +17 -0
  131. package/dist/rules/require-localize-metadata.d.ts.map +1 -0
  132. package/dist/rules/require-localize-metadata.js +123 -0
  133. package/dist/rules/runtime-localize.d.ts +11 -0
  134. package/dist/rules/runtime-localize.d.ts.map +1 -0
  135. package/dist/rules/runtime-localize.js +45 -0
  136. package/dist/rules/sort-keys-in-type-decorator.d.ts +16 -0
  137. package/dist/rules/sort-keys-in-type-decorator.d.ts.map +1 -0
  138. package/dist/rules/sort-keys-in-type-decorator.js +191 -0
  139. package/dist/rules/sort-lifecycle-methods.d.ts +11 -0
  140. package/dist/rules/sort-lifecycle-methods.d.ts.map +1 -0
  141. package/dist/rules/sort-lifecycle-methods.js +45 -0
  142. package/dist/rules/use-component-selector.d.ts +11 -0
  143. package/dist/rules/use-component-selector.d.ts.map +1 -0
  144. package/dist/rules/use-component-selector.js +41 -0
  145. package/dist/rules/use-component-view-encapsulation.d.ts +11 -0
  146. package/dist/rules/use-component-view-encapsulation.d.ts.map +1 -0
  147. package/dist/rules/use-component-view-encapsulation.js +49 -0
  148. package/dist/rules/use-injectable-provided-in.d.ts +13 -0
  149. package/dist/rules/use-injectable-provided-in.d.ts.map +1 -0
  150. package/dist/rules/use-injectable-provided-in.js +63 -0
  151. package/dist/rules/use-lifecycle-interface.d.ts +11 -0
  152. package/dist/rules/use-lifecycle-interface.d.ts.map +1 -0
  153. package/dist/rules/use-lifecycle-interface.js +65 -0
  154. package/dist/rules/use-pipe-transform-interface.d.ts +11 -0
  155. package/dist/rules/use-pipe-transform-interface.d.ts.map +1 -0
  156. package/dist/rules/use-pipe-transform-interface.js +49 -0
  157. package/dist/utils/create-eslint-rule.d.ts +8 -0
  158. package/dist/utils/create-eslint-rule.d.ts.map +1 -0
  159. package/dist/utils/create-eslint-rule.js +28 -0
  160. package/dist/utils/jsdoc.d.ts +11 -0
  161. package/dist/utils/jsdoc.d.ts.map +1 -0
  162. package/dist/utils/jsdoc.js +174 -0
  163. package/dist/utils/signals.d.ts +2 -0
  164. package/dist/utils/signals.d.ts.map +1 -0
  165. package/dist/utils/signals.js +10 -0
  166. package/package.json +56 -0
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0;
4
+ const utils_1 = require("@angular-eslint/utils");
5
+ const create_eslint_rule_1 = require("../utils/create-eslint-rule");
6
+ exports.RULE_NAME = 'prefer-standalone';
7
+ const RECOMMENDED_GUIDE_URL = 'https://angular.dev/reference/migrations/standalone';
8
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
9
+ name: exports.RULE_NAME,
10
+ meta: {
11
+ type: 'suggestion',
12
+ docs: {
13
+ description: `Ensures Components, Directives and Pipes do not opt out of standalone.`,
14
+ recommended: 'recommended',
15
+ },
16
+ hasSuggestions: true,
17
+ schema: [],
18
+ messages: {
19
+ preferStandalone: `Components, Directives and Pipes should not opt out of standalone. Following this guide is highly recommended: ${RECOMMENDED_GUIDE_URL}`,
20
+ removeStandaloneFalse: `Quickly remove 'standalone: false'. NOTE - Following this guide is highly recommended: ${RECOMMENDED_GUIDE_URL}`,
21
+ },
22
+ },
23
+ defaultOptions: [],
24
+ create(context) {
25
+ const standaloneRuleFactory = (type) => (node) => {
26
+ const standalone = utils_1.ASTUtils.getDecoratorPropertyValue(node, 'standalone');
27
+ // Leave the standalone property alone if it was set to true or not present
28
+ if (!standalone ||
29
+ (utils_1.ASTUtils.isLiteral(standalone) && standalone.value === true)) {
30
+ return;
31
+ }
32
+ if (!utils_1.ASTUtils.getDecoratorArgument(node)) {
33
+ return;
34
+ }
35
+ context.report({
36
+ node: standalone.parent,
37
+ messageId: 'preferStandalone',
38
+ data: { type },
39
+ suggest: [
40
+ {
41
+ messageId: 'removeStandaloneFalse',
42
+ fix: (fixer) => {
43
+ // Remove the standalone property altogether if it was set to false
44
+ const tokenAfter = context.sourceCode.getTokenAfter(standalone.parent);
45
+ // Remove the trailing comma, if present
46
+ const removeStart = standalone.parent.range[0];
47
+ let removeEnd = standalone.parent.range[1];
48
+ if (tokenAfter && tokenAfter.value === ',') {
49
+ removeEnd = tokenAfter.range[1];
50
+ }
51
+ return fixer.removeRange([removeStart, removeEnd]);
52
+ },
53
+ },
54
+ ],
55
+ });
56
+ };
57
+ return {
58
+ [utils_1.Selectors.COMPONENT_CLASS_DECORATOR]: standaloneRuleFactory('component'),
59
+ [utils_1.Selectors.DIRECTIVE_CLASS_DECORATOR]: standaloneRuleFactory('directive'),
60
+ [utils_1.Selectors.PIPE_CLASS_DECORATOR]: standaloneRuleFactory('pipe'),
61
+ };
62
+ },
63
+ });
64
+ exports.RULE_DOCS_EXTENSION = {
65
+ rationale: 'Standalone components, directives, and pipes are the recommended way to build Angular applications. Setting standalone: false opts out of the standalone API, tying your code to the older NgModule-based architecture. Standalone components simplify Angular applications by eliminating the need for NgModules in most cases, reducing boilerplate and making dependencies more explicit. Each standalone component declares its own dependencies directly, making it self-contained and easier to understand, test, and reuse. Standalone components also enable better tree-shaking and lazy loading. Angular provides comprehensive migration guides to help transition existing applications, such as https://angular.dev/reference/migrations/standalone. New projects should use standalone components from the start, and existing projects should avoid adding new non-standalone components as they will make future migrations harder.',
66
+ };
@@ -0,0 +1,11 @@
1
+ export type Options = [];
2
+ export type MessageIds = 'relativeUrlPrefix';
3
+ export declare const RULE_NAME = "relative-url-prefix";
4
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"relativeUrlPrefix", [], import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
5
+ name: string;
6
+ };
7
+ export default _default;
8
+ export declare const RULE_DOCS_EXTENSION: {
9
+ rationale: string;
10
+ };
11
+ //# sourceMappingURL=relative-url-prefix.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relative-url-prefix.d.ts","sourceRoot":"","sources":["../../src/rules/relative-url-prefix.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,mBAAmB,CAAC;AAC7C,eAAO,MAAM,SAAS,wBAAwB,CAAC;;;;AAI/C,wBA0CG;AAiBH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0;
4
+ const utils_1 = require("@angular-eslint/utils");
5
+ const create_eslint_rule_1 = require("../utils/create-eslint-rule");
6
+ exports.RULE_NAME = 'relative-url-prefix';
7
+ const RELATIVE_URL_PREFIX_MATCHER = /^\.\.?\/.+/;
8
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
9
+ name: exports.RULE_NAME,
10
+ meta: {
11
+ type: 'suggestion',
12
+ docs: {
13
+ description: `The ./ and ../ prefix is standard syntax for relative URLs; don't depend on Angular's current ability to do without that prefix.`,
14
+ },
15
+ schema: [],
16
+ messages: {
17
+ relativeUrlPrefix: `The ./ and ../ prefix is standard syntax for relative URLs.`,
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ return {
23
+ [`${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='templateUrl']`]({ value, }) {
24
+ if (!isUrlInvalid(value))
25
+ return;
26
+ context.report({
27
+ node: value,
28
+ messageId: 'relativeUrlPrefix',
29
+ });
30
+ },
31
+ [`${utils_1.Selectors.COMPONENT_CLASS_DECORATOR} Property[key.name='styleUrls']`]({ value, }) {
32
+ if (!utils_1.ASTUtils.isArrayExpression(value))
33
+ return;
34
+ value.elements.filter(isUrlInvalid).forEach((element) => {
35
+ if (!element) {
36
+ return;
37
+ }
38
+ context.report({
39
+ node: element,
40
+ messageId: 'relativeUrlPrefix',
41
+ });
42
+ });
43
+ },
44
+ };
45
+ },
46
+ });
47
+ function isUrlInvalid(node) {
48
+ if (!node) {
49
+ return false;
50
+ }
51
+ if (utils_1.ASTUtils.isTemplateLiteral(node)) {
52
+ return !RELATIVE_URL_PREFIX_MATCHER.test(node.quasis[0].value.raw);
53
+ }
54
+ return (!utils_1.ASTUtils.isStringLiteral(node) ||
55
+ !RELATIVE_URL_PREFIX_MATCHER.test(node.value));
56
+ }
57
+ exports.RULE_DOCS_EXTENSION = {
58
+ rationale: "Using relative URLs (like './user-profile.component.html') instead of absolute URLs (like 'app/users/user-profile.component.html') for templateUrl and styleUrls makes components more portable and easier to refactor. When you move a component to a different directory, relative URLs don't need to be updated as long as the template and styles move with the component. This follows the principle of co-locating related files and makes refactoring safer. Relative URLs should typically start with './' for files in the same directory or '../' for files in parent directories.",
59
+ };
@@ -0,0 +1,11 @@
1
+ export type Options = [];
2
+ export type MessageIds = 'defineOnPrototype';
3
+ export declare const RULE_NAME = "require-lifecycle-on-prototype";
4
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"defineOnPrototype", [], import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
5
+ name: string;
6
+ };
7
+ export default _default;
8
+ export declare const RULE_DOCS_EXTENSION: {
9
+ rationale: string;
10
+ };
11
+ //# sourceMappingURL=require-lifecycle-on-prototype.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-lifecycle-on-prototype.d.ts","sourceRoot":"","sources":["../../src/rules/require-lifecycle-on-prototype.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AAIzB,MAAM,MAAM,UAAU,GAAG,mBAAmB,CAAC;AAC7C,eAAO,MAAM,SAAS,mCAAmC,CAAC;;;;AAY1D,wBA2CG;AAkBH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const create_eslint_rule_1 = require("../utils/create-eslint-rule");
6
+ const utils_2 = require("@angular-eslint/utils");
7
+ const ISSUE_LINK = 'https://github.com/angular/angular/issues/38241';
8
+ exports.RULE_NAME = 'require-lifecycle-on-prototype';
9
+ const angularLifecycleMethodsPattern = (0, utils_2.toPattern)([
10
+ ...utils_2.ASTUtils.ANGULAR_LIFECYCLE_METHODS,
11
+ ]);
12
+ const propertyDefinitionSelector = `PropertyDefinition > ${createAngularLifecycleMethodsPattern('key')}`;
13
+ const assignmentSelector = `AssignmentExpression[operator="="] > MemberExpression.left > ${createAngularLifecycleMethodsPattern('property')}`;
14
+ function createAngularLifecycleMethodsPattern(parentProperty) {
15
+ return `:matches(Literal.${parentProperty}[value=${angularLifecycleMethodsPattern}], Identifier.${parentProperty}[name=${angularLifecycleMethodsPattern}])`;
16
+ }
17
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
18
+ name: exports.RULE_NAME,
19
+ meta: {
20
+ type: 'problem',
21
+ docs: {
22
+ description: "Ensures that lifecycle methods are defined on the object's prototype instead of on an instance.",
23
+ },
24
+ schema: [],
25
+ messages: {
26
+ defineOnPrototype: `The {{ method }} lifecycle method should be defined on the object's prototype. See more at ${ISSUE_LINK}`,
27
+ },
28
+ },
29
+ defaultOptions: [],
30
+ create(context) {
31
+ return {
32
+ [propertyDefinitionSelector](node) {
33
+ context.report({
34
+ node,
35
+ messageId: 'defineOnPrototype',
36
+ data: {
37
+ method: node.type === utils_1.AST_NODE_TYPES.Literal ? node.value : node.name,
38
+ },
39
+ });
40
+ },
41
+ [assignmentSelector](node) {
42
+ // Assigning to the prototype is OK.
43
+ if (!isPrototype(node.parent.object)) {
44
+ context.report({
45
+ node,
46
+ messageId: 'defineOnPrototype',
47
+ data: {
48
+ method: node.type === utils_1.AST_NODE_TYPES.Literal ? node.value : node.name,
49
+ },
50
+ });
51
+ }
52
+ },
53
+ };
54
+ },
55
+ });
56
+ function isPrototype(node) {
57
+ while (node.type === utils_1.AST_NODE_TYPES.TSAsExpression) {
58
+ node = node.expression;
59
+ }
60
+ if (node.type === utils_1.AST_NODE_TYPES.MemberExpression) {
61
+ switch (node.property.type) {
62
+ case utils_1.AST_NODE_TYPES.Identifier:
63
+ return node.property.name === 'prototype';
64
+ case utils_1.AST_NODE_TYPES.Literal:
65
+ return node.property.value === 'prototype';
66
+ }
67
+ }
68
+ return false;
69
+ }
70
+ exports.RULE_DOCS_EXTENSION = {
71
+ rationale: "Lifecycle methods in Angular must be defined on the class prototype (using method syntax) rather than as instance properties (using arrow functions or property assignments). Angular's change detection looks for lifecycle methods on the prototype chain, and defining them as instance properties can prevent Angular from finding and invoking them correctly. This is a subtle but critical issue that can cause lifecycle hooks to silently fail. For example, if ngOnInit is defined as a class property (ngOnInit = () => {}), Angular will not call it, leading to missing initialization logic. Using standard method definitions ensures lifecycle hooks are placed on the prototype where Angular expects them.",
72
+ };
@@ -0,0 +1,17 @@
1
+ export type Options = [
2
+ {
3
+ readonly requireDescription?: boolean;
4
+ readonly requireMeaning?: boolean;
5
+ readonly requireCustomId?: boolean | string;
6
+ }
7
+ ];
8
+ export type MessageIds = 'requireLocalizeDescription' | 'requireLocalizeMeaning' | 'requireLocalizeCustomId';
9
+ export declare const RULE_NAME = "require-localize-metadata";
10
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<MessageIds, Options, import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
11
+ name: string;
12
+ };
13
+ export default _default;
14
+ export declare const RULE_DOCS_EXTENSION: {
15
+ rationale: string;
16
+ };
17
+ //# sourceMappingURL=require-localize-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-localize-metadata.d.ts","sourceRoot":"","sources":["../../src/rules/require-localize-metadata.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,OAAO,GAAG;IACpB;QACE,QAAQ,CAAC,kBAAkB,CAAC,EAAE,OAAO,CAAC;QACtC,QAAQ,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;QAClC,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;KAC7C;CACF,CAAC;AAYF,MAAM,MAAM,UAAU,GAClB,4BAA4B,GAC5B,wBAAwB,GACxB,yBAAyB,CAAC;AAC9B,eAAO,MAAM,SAAS,8BAA8B,CAAC;;;;AAErD,wBAyFG;AAyCH,eAAO,MAAM,mBAAmB;;CAE/B,CAAC"}
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const create_eslint_rule_1 = require("../utils/create-eslint-rule");
6
+ const DEFAULT_OPTIONS = {
7
+ requireDescription: false,
8
+ requireMeaning: false,
9
+ requireCustomId: false,
10
+ };
11
+ const STYLE_GUIDE_LINK = 'https://angular.dev/guide/i18n';
12
+ const STYLE_GUIDE_LINK_PREPARE = `${STYLE_GUIDE_LINK}/prepare`;
13
+ const STYLE_GUIDE_LINK_METADATA_FOR_TRANSLATION = `${STYLE_GUIDE_LINK_PREPARE}#i18n-metadata-for-translation`;
14
+ exports.RULE_NAME = 'require-localize-metadata';
15
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
16
+ name: exports.RULE_NAME,
17
+ meta: {
18
+ type: 'suggestion',
19
+ docs: {
20
+ description: 'Ensures that $localize tagged messages contain helpful metadata to aid with translations.',
21
+ },
22
+ schema: [
23
+ {
24
+ type: 'object',
25
+ properties: {
26
+ requireDescription: {
27
+ type: 'boolean',
28
+ default: DEFAULT_OPTIONS.requireDescription,
29
+ },
30
+ requireMeaning: {
31
+ type: 'boolean',
32
+ default: DEFAULT_OPTIONS.requireMeaning,
33
+ },
34
+ requireCustomId: {
35
+ oneOf: [{ type: 'boolean' }, { type: 'string' }],
36
+ default: DEFAULT_OPTIONS.requireCustomId,
37
+ },
38
+ },
39
+ additionalProperties: false,
40
+ },
41
+ ],
42
+ messages: {
43
+ requireLocalizeDescription: `$localize tagged messages should contain a description. See more at ${STYLE_GUIDE_LINK_METADATA_FOR_TRANSLATION}`,
44
+ requireLocalizeMeaning: `$localize tagged messages should contain a meaning. See more at ${STYLE_GUIDE_LINK_METADATA_FOR_TRANSLATION}`,
45
+ requireLocalizeCustomId: `$localize tagged messages should contain a custom id{{patternMessage}}. See more at ${STYLE_GUIDE_LINK_METADATA_FOR_TRANSLATION}`,
46
+ },
47
+ },
48
+ defaultOptions: [DEFAULT_OPTIONS],
49
+ create(context, [{ requireDescription, requireMeaning, requireCustomId }]) {
50
+ return {
51
+ TaggedTemplateExpression(taggedTemplateExpression) {
52
+ if ((requireDescription || requireMeaning || requireCustomId) &&
53
+ utils_1.ASTUtils.isIdentifier(taggedTemplateExpression.tag)) {
54
+ const identifierName = taggedTemplateExpression.tag.name;
55
+ const templateElement = taggedTemplateExpression.quasi.quasis[0];
56
+ if (identifierName === '$localize' && !!templateElement) {
57
+ const metadata = parseMetadata(templateElement.value.raw.trim());
58
+ if (requireDescription && !metadata.description) {
59
+ context.report({
60
+ loc: templateElement.loc,
61
+ messageId: 'requireLocalizeDescription',
62
+ });
63
+ }
64
+ if (requireMeaning && !metadata.meaning) {
65
+ context.report({
66
+ loc: templateElement.loc,
67
+ messageId: 'requireLocalizeMeaning',
68
+ });
69
+ }
70
+ if (requireCustomId &&
71
+ !(metadata.customId &&
72
+ (typeof requireCustomId === 'string'
73
+ ? RegExp(requireCustomId).test(metadata.customId)
74
+ : true))) {
75
+ context.report({
76
+ loc: templateElement.loc,
77
+ messageId: 'requireLocalizeCustomId',
78
+ data: {
79
+ patternMessage: typeof requireCustomId === 'string'
80
+ ? ` matching the pattern /${requireCustomId}/ on '${metadata.customId}'`
81
+ : '',
82
+ },
83
+ });
84
+ }
85
+ }
86
+ }
87
+ },
88
+ };
89
+ },
90
+ });
91
+ // see https://github.com/angular/angular/blob/main/packages/localize/src/utils/src/messages.ts#L247
92
+ const BLOCK_MARKER = ':';
93
+ const MEANING_SEPARATOR = '|';
94
+ const ID_SEPARATOR = '@@';
95
+ function parseMetadata(rawText) {
96
+ const output = {
97
+ rawText,
98
+ meaning: undefined,
99
+ description: undefined,
100
+ customId: undefined,
101
+ };
102
+ if (rawText.charAt(0) !== BLOCK_MARKER) {
103
+ return output;
104
+ }
105
+ const endOfTheBlock = rawText.lastIndexOf(BLOCK_MARKER);
106
+ if (endOfTheBlock === -1) {
107
+ return output;
108
+ }
109
+ const text = rawText.slice(1, endOfTheBlock);
110
+ const [meaningAndDesc, customId] = text.split(ID_SEPARATOR, 2);
111
+ let [meaning, description] = meaningAndDesc.split(MEANING_SEPARATOR, 2);
112
+ if (description === undefined) {
113
+ description = meaning;
114
+ meaning = undefined;
115
+ }
116
+ if (description === '') {
117
+ description = undefined;
118
+ }
119
+ return { rawText, meaning, description, customId };
120
+ }
121
+ exports.RULE_DOCS_EXTENSION = {
122
+ rationale: `When internationalizing Angular applications with @angular/localize, adding metadata (description, meaning, and custom IDs) to $localize tagged strings is essential for high-quality translations. The description helps translators understand the context and purpose of the text they're translating. For example, "Open" could mean "open a file" (access/view) or "open an account" (establish/create) - context matters for accurate translation. The meaning field distinguishes identical text that needs different translations in different contexts. Custom IDs provide stable references that persist across code changes, preventing retranslation when only code structure changes. Without this metadata, translators work blind, leading to poor translations, wasted effort on retranslating unchanged strings, and difficulty managing large translation projects. This rule ensures your $localize strings include helpful metadata for translation workflows.`,
123
+ };
@@ -0,0 +1,11 @@
1
+ export type Options = [];
2
+ export type MessageIds = 'runtimeLocalize';
3
+ export declare const RULE_NAME = "runtime-localize";
4
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"runtimeLocalize", [], import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
5
+ name: string;
6
+ };
7
+ export default _default;
8
+ export declare const RULE_DOCS_EXTENSION: {
9
+ rationale: string;
10
+ };
11
+ //# sourceMappingURL=runtime-localize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-localize.d.ts","sourceRoot":"","sources":["../../src/rules/runtime-localize.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,iBAAiB,CAAC;AAC3C,eAAO,MAAM,SAAS,qBAAqB,CAAC;;;;AAE5C,wBA6CG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0;
4
+ const utils_1 = require("@typescript-eslint/utils");
5
+ const create_eslint_rule_1 = require("../utils/create-eslint-rule");
6
+ exports.RULE_NAME = 'runtime-localize';
7
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
8
+ name: exports.RULE_NAME,
9
+ meta: {
10
+ type: 'suggestion',
11
+ docs: {
12
+ description: 'Ensures that $localize tagged messages can use runtime-loaded translations.',
13
+ },
14
+ schema: [],
15
+ messages: {
16
+ runtimeLocalize: `$localize could be called before translations are loaded`,
17
+ },
18
+ },
19
+ defaultOptions: [],
20
+ create(context) {
21
+ return {
22
+ TaggedTemplateExpression(taggedTemplateExpression) {
23
+ if (utils_1.ASTUtils.isIdentifier(taggedTemplateExpression.tag) &&
24
+ taggedTemplateExpression.tag.name === '$localize') {
25
+ for (const ancestor of context.sourceCode.getAncestors(taggedTemplateExpression)) {
26
+ if (ancestor.type === utils_1.AST_NODE_TYPES.FunctionDeclaration ||
27
+ ancestor.type === utils_1.AST_NODE_TYPES.FunctionExpression ||
28
+ ancestor.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression ||
29
+ (ancestor.type === utils_1.AST_NODE_TYPES.PropertyDefinition &&
30
+ !ancestor.static)) {
31
+ return;
32
+ }
33
+ }
34
+ context.report({
35
+ loc: taggedTemplateExpression.tag.loc,
36
+ messageId: 'runtimeLocalize',
37
+ });
38
+ }
39
+ },
40
+ };
41
+ },
42
+ });
43
+ exports.RULE_DOCS_EXTENSION = {
44
+ rationale: "Using $localize at the top level of a module (outside of functions or methods) means the localization happens immediately when the module loads, before runtime translation data can be loaded. This creates a timing problem - the string gets localized using whatever translations are available at module load time (typically none), and the translated value is frozen. Even if you load translations later, these top-level $localize calls won't use them. Instead, $localize should be called inside functions, methods, or non-static class properties so it executes after translations are loaded. This ensures the runtime-loaded translations are actually used. For top-level strings that need localization, initialize them in a function called after translations load, or use them within component templates where Angular handles the timing correctly.",
45
+ };
@@ -0,0 +1,16 @@
1
+ import { TSESLint } from '@typescript-eslint/utils';
2
+ export type Options = [
3
+ {
4
+ [key: string]: string[];
5
+ }
6
+ ];
7
+ export type MessageIds = 'incorrectOrder';
8
+ export declare const RULE_NAME = "sort-keys-in-type-decorator";
9
+ declare const _default: TSESLint.RuleModule<"incorrectOrder", Options, import("../utils/create-eslint-rule").RuleDocs, TSESLint.RuleListener> & {
10
+ name: string;
11
+ };
12
+ export default _default;
13
+ export declare const RULE_DOCS_EXTENSION: {
14
+ rationale: string;
15
+ };
16
+ //# sourceMappingURL=sort-keys-in-type-decorator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sort-keys-in-type-decorator.d.ts","sourceRoot":"","sources":["../../src/rules/sort-keys-in-type-decorator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAY,MAAM,0BAA0B,CAAC;AAG9D,MAAM,MAAM,OAAO,GAAG;IACpB;QACE,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;KACzB;CACF,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,gBAAgB,CAAC;AA2D1C,eAAO,MAAM,SAAS,gCAAgC,CAAC;;;;AAEvD,wBA+IG;AAsFH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0;
4
+ const utils_1 = require("@angular-eslint/utils");
5
+ const create_eslint_rule_1 = require("../utils/create-eslint-rule");
6
+ const DEFAULT_ORDER = {
7
+ // https://angular.dev/api/core/Component
8
+ Component: [
9
+ 'selector',
10
+ 'imports',
11
+ 'standalone',
12
+ 'templateUrl',
13
+ 'template',
14
+ 'styleUrl',
15
+ 'styleUrls',
16
+ 'styles',
17
+ 'providers',
18
+ 'changeDetection',
19
+ 'encapsulation',
20
+ 'viewProviders',
21
+ 'host',
22
+ 'hostDirectives',
23
+ 'inputs',
24
+ 'outputs',
25
+ 'animations',
26
+ 'schemas',
27
+ 'exportAs',
28
+ 'queries',
29
+ 'preserveWhitespaces',
30
+ 'jit',
31
+ // Deprecated properties according to https://angular.dev/api/core/Component
32
+ 'moduleId',
33
+ 'interpolation',
34
+ ],
35
+ // https://angular.dev/api/core/Directive
36
+ Directive: [
37
+ 'selector',
38
+ 'standalone',
39
+ 'providers',
40
+ 'host',
41
+ 'hostDirectives',
42
+ 'inputs',
43
+ 'outputs',
44
+ 'exportAs',
45
+ 'queries',
46
+ 'jit',
47
+ ],
48
+ // https://angular.dev/api/core/NgModule
49
+ NgModule: [
50
+ 'id', // rarely used but good to have first if set
51
+ 'imports',
52
+ 'declarations',
53
+ 'providers',
54
+ 'exports',
55
+ 'bootstrap',
56
+ 'schemas',
57
+ 'jit',
58
+ ],
59
+ // https://angular.dev/api/core/Pipe
60
+ Pipe: ['name', 'standalone', 'pure'],
61
+ };
62
+ exports.RULE_NAME = 'sort-keys-in-type-decorator';
63
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
64
+ name: exports.RULE_NAME,
65
+ meta: {
66
+ type: 'suggestion',
67
+ docs: {
68
+ description: 'Ensures that keys in type decorators (Component, Directive, NgModule, Pipe) are sorted in a consistent order',
69
+ },
70
+ fixable: 'code',
71
+ schema: [
72
+ {
73
+ type: 'object',
74
+ properties: {
75
+ Component: {
76
+ type: 'array',
77
+ items: {
78
+ type: 'string',
79
+ },
80
+ },
81
+ Directive: {
82
+ type: 'array',
83
+ items: {
84
+ type: 'string',
85
+ },
86
+ },
87
+ NgModule: {
88
+ type: 'array',
89
+ items: {
90
+ type: 'string',
91
+ },
92
+ },
93
+ Pipe: {
94
+ type: 'array',
95
+ items: {
96
+ type: 'string',
97
+ },
98
+ },
99
+ },
100
+ additionalProperties: false,
101
+ },
102
+ ],
103
+ messages: {
104
+ incorrectOrder: 'Keys in @{{decorator}} decorator should be ordered: {{expectedOrder}}',
105
+ },
106
+ },
107
+ defaultOptions: [DEFAULT_ORDER],
108
+ create(context, [orderConfig]) {
109
+ function checkContext(node, decoratorName) {
110
+ const expectedOrder = orderConfig[decoratorName];
111
+ if (!expectedOrder) {
112
+ return;
113
+ }
114
+ const argument = utils_1.ASTUtils.getDecoratorArgument(node);
115
+ if (!argument) {
116
+ return;
117
+ }
118
+ const properties = utils_1.ASTUtils.getDecoratorProperties(node);
119
+ if (properties.length <= 1) {
120
+ return;
121
+ }
122
+ const firstConfiguredIndex = properties.findIndex(({ key }) => expectedOrder.includes(key.name));
123
+ const lastNonConfiguredIndex = properties.findIndex(({ key }) => !expectedOrder.includes(key.name));
124
+ if (firstConfiguredIndex !== -1 &&
125
+ lastNonConfiguredIndex !== -1 &&
126
+ lastNonConfiguredIndex < firstConfiguredIndex) {
127
+ createInvalidSortRuleForDecorator(context, decoratorName, expectedOrder, properties, properties[lastNonConfiguredIndex]);
128
+ return;
129
+ }
130
+ const configuredProperties = properties.filter(({ key }) => expectedOrder.includes(key.name));
131
+ if (configuredProperties.length) {
132
+ const actualConfiguredOrder = configuredProperties.map(({ key }) => key.name);
133
+ const expectedConfiguredOrder = expectedOrder.filter((key) => actualConfiguredOrder.includes(key));
134
+ if (actualConfiguredOrder.length &&
135
+ JSON.stringify(actualConfiguredOrder) !==
136
+ JSON.stringify(expectedConfiguredOrder)) {
137
+ const firstOutOfOrderIndex = actualConfiguredOrder.findIndex((key, index) => key !== expectedConfiguredOrder[index]);
138
+ const outOfOrderProperty = configuredProperties[firstOutOfOrderIndex];
139
+ createInvalidSortRuleForDecorator(context, decoratorName, expectedOrder, properties, outOfOrderProperty);
140
+ }
141
+ }
142
+ }
143
+ return {
144
+ [utils_1.Selectors.COMPONENT_CLASS_DECORATOR](node) {
145
+ checkContext(node, utils_1.ASTUtils.AngularClassDecorators.Component);
146
+ },
147
+ [utils_1.Selectors.DIRECTIVE_CLASS_DECORATOR](node) {
148
+ checkContext(node, utils_1.ASTUtils.AngularClassDecorators.Directive);
149
+ },
150
+ [utils_1.Selectors.INJECTABLE_CLASS_DECORATOR](node) {
151
+ checkContext(node, utils_1.ASTUtils.AngularClassDecorators.Injectable);
152
+ },
153
+ [utils_1.Selectors.MODULE_CLASS_DECORATOR](node) {
154
+ checkContext(node, utils_1.ASTUtils.AngularClassDecorators.NgModule);
155
+ },
156
+ [utils_1.Selectors.PIPE_CLASS_DECORATOR](node) {
157
+ checkContext(node, utils_1.ASTUtils.AngularClassDecorators.Pipe);
158
+ },
159
+ };
160
+ },
161
+ });
162
+ function createInvalidSortRuleForDecorator(context, decoratorName, expectedOrder, properties, node) {
163
+ const presentProps = properties.map((prop) => prop.key.name);
164
+ const relevantExpectedOrder = expectedOrder.filter((propName) => presentProps.includes(propName));
165
+ const data = {
166
+ decorator: decoratorName,
167
+ expectedOrder: relevantExpectedOrder.join(', '),
168
+ };
169
+ reportAndFix(context, node, 'incorrectOrder', data, properties, expectedOrder, node.parent);
170
+ }
171
+ function reportAndFix(context, node, messageId, data, properties, expectedOrder, objectExpression) {
172
+ const sourceCode = context.sourceCode;
173
+ context.report({
174
+ node,
175
+ messageId,
176
+ data,
177
+ fix(fixer) {
178
+ const indentation = utils_1.CommentUtils.getObjectIndentation(sourceCode, objectExpression);
179
+ const propNames = properties.map((p) => p.key.name);
180
+ const configuredProps = expectedOrder.filter((name) => propNames.includes(name));
181
+ const unconfiguredProps = propNames.filter((name) => !expectedOrder.includes(name));
182
+ const filteredOrder = [...configuredProps, ...unconfiguredProps];
183
+ const propInfoMap = utils_1.CommentUtils.extractPropertyComments(sourceCode, properties, objectExpression, indentation);
184
+ const sortedText = utils_1.CommentUtils.buildSortedPropertiesWithComments(filteredOrder, propInfoMap, indentation);
185
+ return fixer.replaceText(objectExpression, `{\n${sortedText}\n${indentation.slice(0, -2)}}`);
186
+ },
187
+ });
188
+ }
189
+ exports.RULE_DOCS_EXTENSION = {
190
+ rationale: 'Maintaining a consistent order for properties in Angular decorators (@Component, @Directive, @NgModule, @Pipe) makes code more predictable and easier to scan. When all components in a codebase follow the same property order, developers can quickly locate specific metadata without searching. For example, if selector always comes first and providers always comes before changeDetection, you develop muscle memory for where to look. This is especially helpful in large components with many properties. The recommended default order groups related properties logically: identification (selector, name) first, then dependencies (imports, providers), then templates/styles, then configuration options. Consistent ordering also makes code reviews easier, reduces merge conflicts when multiple developers edit decorators, and creates a professional, well-organized codebase.',
191
+ };