@angular-eslint/eslint-plugin-template 21.1.1-alpha.1 → 21.1.1-alpha.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -66,6 +66,7 @@ Please see https://github.com/angular-eslint/angular-eslint for full usage instr
66
66
  | [`prefer-at-else`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-at-else.md) | Prefer using `@else` instead of a second `@if` with the opposite condition to reduce code and make it easier to read. | | :wrench: | | |
67
67
  | [`prefer-at-empty`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-at-empty.md) | Prefer using `@empty` with `@for` loops instead of a separate `@if` or `@else` block to reduce code and make it easier to read. | | :wrench: | | |
68
68
  | [`prefer-built-in-pipes`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-built-in-pipes.md) | Encourages the use of Angular built-in pipes (e.g. lowercase, uppercase, titlecase) instead of certain JavaScript methods in Angular templates. | | | | |
69
+ | [`prefer-class-binding`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-class-binding.md) | Suggests using [class] bindings over ngClass where applicable | | | | |
69
70
  | [`prefer-contextual-for-variables`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-contextual-for-variables.md) | Ensures that contextual variables are used in @for blocks where possible instead of aliasing them. | | :wrench: | | |
70
71
  | [`prefer-control-flow`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-control-flow.md) | Ensures that the built-in control flow is used. | :white_check_mark: | | | |
71
72
  | [`prefer-ngsrc`](https://github.com/angular-eslint/angular-eslint/blob/main/packages/eslint-plugin-template/docs/rules/prefer-ngsrc.md) | Ensures ngSrc is used instead of src for img elements | | | :bulb: | |
@@ -29,6 +29,7 @@
29
29
  "@angular-eslint/template/prefer-at-else": "error",
30
30
  "@angular-eslint/template/prefer-at-empty": "error",
31
31
  "@angular-eslint/template/prefer-built-in-pipes": "error",
32
+ "@angular-eslint/template/prefer-class-binding": "error",
32
33
  "@angular-eslint/template/prefer-contextual-for-variables": "error",
33
34
  "@angular-eslint/template/prefer-control-flow": "error",
34
35
  "@angular-eslint/template/prefer-ngsrc": "error",
package/dist/index.d.ts CHANGED
@@ -31,6 +31,7 @@ declare const _default: {
31
31
  "@angular-eslint/template/prefer-at-else": string;
32
32
  "@angular-eslint/template/prefer-at-empty": string;
33
33
  "@angular-eslint/template/prefer-built-in-pipes": string;
34
+ "@angular-eslint/template/prefer-class-binding": string;
34
35
  "@angular-eslint/template/prefer-contextual-for-variables": string;
35
36
  "@angular-eslint/template/prefer-control-flow": string;
36
37
  "@angular-eslint/template/prefer-ngsrc": string;
@@ -118,11 +119,12 @@ declare const _default: {
118
119
  "no-positive-tabindex": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/no-positive-tabindex").MessageIds, [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
119
120
  "prefer-at-else": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferAtElse", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
120
121
  "prefer-at-empty": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferAtEmpty", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
122
+ "prefer-class-binding": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferClassBinding", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
121
123
  "prefer-contextual-for-variables": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/prefer-contextual-for-variables").MessageIds, import("./rules/prefer-contextual-for-variables").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
122
124
  "prefer-control-flow": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferControlFlow", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
123
125
  "prefer-ngsrc": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/prefer-ngsrc").MessageIds, [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
124
126
  "prefer-self-closing-tags": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferSelfClosingTags", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
125
- "prefer-static-string-properties": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferStaticStringProperties", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
127
+ "prefer-static-string-properties": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferStaticStringProperties", import("./rules/prefer-static-string-properties").Options, import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
126
128
  "prefer-template-literal": import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferTemplateLiteral", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
127
129
  "role-has-required-aria": import("@typescript-eslint/utils/ts-eslint").RuleModule<import("./rules/role-has-required-aria").MessageIds, [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
128
130
  "table-scope": import("@typescript-eslint/utils/ts-eslint").RuleModule<"tableScope", [], import("./utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2GA,kBA+CE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8GA,kBAgDE"}
package/dist/index.js CHANGED
@@ -68,6 +68,7 @@ const no_positive_tabindex_1 = __importStar(require("./rules/no-positive-tabinde
68
68
  const prefer_ngsrc_1 = __importStar(require("./rules/prefer-ngsrc"));
69
69
  const prefer_at_else_1 = __importStar(require("./rules/prefer-at-else"));
70
70
  const prefer_at_empty_1 = __importStar(require("./rules/prefer-at-empty"));
71
+ const prefer_class_binding_1 = __importStar(require("./rules/prefer-class-binding"));
71
72
  const prefer_contextual_for_variables_1 = __importStar(require("./rules/prefer-contextual-for-variables"));
72
73
  const prefer_control_flow_1 = __importStar(require("./rules/prefer-control-flow"));
73
74
  const prefer_self_closing_tags_1 = __importStar(require("./rules/prefer-self-closing-tags"));
@@ -113,6 +114,7 @@ module.exports = {
113
114
  [no_positive_tabindex_1.RULE_NAME]: no_positive_tabindex_1.default,
114
115
  [prefer_at_else_1.RULE_NAME]: prefer_at_else_1.default,
115
116
  [prefer_at_empty_1.RULE_NAME]: prefer_at_empty_1.default,
117
+ [prefer_class_binding_1.RULE_NAME]: prefer_class_binding_1.default,
116
118
  [prefer_contextual_for_variables_1.RULE_NAME]: prefer_contextual_for_variables_1.default,
117
119
  [prefer_control_flow_1.RULE_NAME]: prefer_control_flow_1.default,
118
120
  [prefer_ngsrc_1.RULE_NAME]: prefer_ngsrc_1.default,
@@ -1 +1 @@
1
- {"version":3,"file":"no-autofocus.d.ts","sourceRoot":"","sources":["../../src/rules/no-autofocus.ts"],"names":[],"mappings":"AASA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,aAAa,CAAC;AACvC,eAAO,MAAM,SAAS,iBAAiB,CAAC;;AAExC,wBAsCG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
1
+ {"version":3,"file":"no-autofocus.d.ts","sourceRoot":"","sources":["../../src/rules/no-autofocus.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,aAAa,CAAC;AACvC,eAAO,MAAM,SAAS,iBAAiB,CAAC;;AAMxC,wBA6CG;AAeH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -24,20 +24,36 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
24
24
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
25
25
  const elementNamePattern = (0, to_pattern_1.toPattern)([...(0, get_dom_elements_1.getDomElements)()]);
26
26
  return {
27
- [`Element[name=${elementNamePattern}] > :matches(BoundAttribute, TextAttribute)[name="autofocus"]`]({ sourceSpan, }) {
28
- const loc = parserServices.convertNodeSourceSpanToLoc(sourceSpan);
27
+ [`Element[name=${elementNamePattern}] > :matches(BoundAttribute, TextAttribute)[name="autofocus"]`](node) {
28
+ // Allow autofocus on dialog elements and their descendants
29
+ // per MDN accessibility guidelines:
30
+ // https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/dialog
31
+ if (isInDialogElement(node)) {
32
+ return;
33
+ }
34
+ const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
29
35
  context.report({
30
36
  loc,
31
37
  messageId: 'noAutofocus',
32
38
  fix: (fixer) => fixer.removeRange([
33
- sourceSpan.start.offset - 1,
34
- sourceSpan.end.offset,
39
+ node.sourceSpan.start.offset - 1,
40
+ node.sourceSpan.end.offset,
35
41
  ]),
36
42
  });
37
43
  },
38
44
  };
39
45
  },
40
46
  });
47
+ function isInDialogElement(attribute) {
48
+ let current = attribute.parent;
49
+ while (current) {
50
+ if (current.name?.toLowerCase() === 'dialog') {
51
+ return true;
52
+ }
53
+ current = current.parent;
54
+ }
55
+ return false;
56
+ }
41
57
  exports.RULE_DOCS_EXTENSION = {
42
- rationale: "The autofocus attribute automatically moves focus to an element when the page loads, which can be disorienting and problematic for accessibility. For screen reader users, autofocus disrupts their normal navigation flow and can cause them to miss important page content. For users with motor disabilities, unexpected focus changes can be confusing. For users with cognitive disabilities, the auto-focused element might grab attention before they've had a chance to understand the page structure. Additionally, autofocus can interfere with browser features like scroll restoration. If focus management is needed, implement it through Angular lifecycle hooks with proper context awareness rather than using the autofocus attribute.",
58
+ rationale: "The autofocus attribute automatically moves focus to an element when the page loads, which can be disorienting and problematic for accessibility. For screen reader users, autofocus disrupts their normal navigation flow and can cause them to miss important page content. For users with motor disabilities, unexpected focus changes can be confusing. For users with cognitive disabilities, the auto-focused element might grab attention before they've had a chance to understand the page structure. Additionally, autofocus can interfere with browser features like scroll restoration. If focus management is needed, implement it through Angular lifecycle hooks with proper context awareness rather than using the autofocus attribute.\n\nHowever, there is an important exception: Using autofocus on elements within <dialog> elements (or on the dialog itself) is actually recommended for accessibility. According to MDN, the autofocus attribute should be added to the element the user is expected to interact with immediately upon opening a modal dialog. If no other element involves more immediate interaction, it is recommended to add autofocus to the close button inside the dialog, or the dialog itself. This rule automatically allows autofocus when used within dialog elements.",
43
59
  };
@@ -0,0 +1,8 @@
1
+ export type MessageIds = 'preferClassBinding';
2
+ export declare const RULE_NAME = "prefer-class-binding";
3
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferClassBinding", [], import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
4
+ export default _default;
5
+ export declare const RULE_DOCS_EXTENSION: {
6
+ rationale: string;
7
+ };
8
+ //# sourceMappingURL=prefer-class-binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prefer-class-binding.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-class-binding.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,UAAU,GAAG,oBAAoB,CAAC;AAC9C,eAAO,MAAM,SAAS,yBAAyB,CAAC;;AAEhD,wBAmCG;AAoDH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0;
4
+ const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
5
+ const utils_1 = require("@angular-eslint/utils");
6
+ const create_eslint_rule_1 = require("../utils/create-eslint-rule");
7
+ exports.RULE_NAME = 'prefer-class-binding';
8
+ exports.default = (0, create_eslint_rule_1.createESLintRule)({
9
+ name: exports.RULE_NAME,
10
+ meta: {
11
+ type: 'suggestion',
12
+ docs: {
13
+ description: 'Suggests using [class] bindings over ngClass where applicable',
14
+ },
15
+ schema: [],
16
+ messages: {
17
+ preferClassBinding: 'Consider using [class] bindings instead of [ngClass] where applicable.',
18
+ },
19
+ },
20
+ defaultOptions: [],
21
+ create(context) {
22
+ const parserServices = (0, utils_1.getTemplateParserServices)(context);
23
+ return {
24
+ 'BoundAttribute[name="ngClass"]'(node) {
25
+ // Skip if ngClass is necessary (e.g., uses space-separated class names)
26
+ if (requiresNgClass(node)) {
27
+ return;
28
+ }
29
+ const loc = parserServices.convertNodeSourceSpanToLoc(node.sourceSpan);
30
+ context.report({
31
+ messageId: 'preferClassBinding',
32
+ loc,
33
+ });
34
+ },
35
+ };
36
+ },
37
+ });
38
+ let parser = null;
39
+ /**
40
+ * Instantiate the `Parser` class lazily only when this rule is applied.
41
+ */
42
+ function getParser() {
43
+ return parser || (parser = new bundled_angular_compiler_1.Parser(new bundled_angular_compiler_1.Lexer()));
44
+ }
45
+ /**
46
+ * Checks if a string contains space-separated class names
47
+ */
48
+ function hasSpaceSeparatedClasses(str) {
49
+ return /\s/.test(str.trim());
50
+ }
51
+ /**
52
+ * Checks if the ngClass binding uses features that class bindings don't support:
53
+ * - Object keys with space-separated class names
54
+ */
55
+ function requiresNgClass(node) {
56
+ // Check if we have a value with source code
57
+ if (!node.value?.source || !node.valueSpan) {
58
+ return false;
59
+ }
60
+ // Parse the binding to get the AST
61
+ const parsedAst = getParser().parseBinding(node.value.source, node.valueSpan, 0).ast;
62
+ if (parsedAst instanceof bundled_angular_compiler_1.LiteralMap) {
63
+ for (const astKey of parsedAst.keys) {
64
+ const className = astKey.key;
65
+ if (typeof className === 'string' &&
66
+ hasSpaceSeparatedClasses(className)) {
67
+ return true;
68
+ }
69
+ }
70
+ }
71
+ return false;
72
+ }
73
+ exports.RULE_DOCS_EXTENSION = {
74
+ rationale: 'For simple cases, [class] bindings offer a more straightforward syntax with better performance than ngClass. However, ngClass should still be used when you need: (1) space-separated class names in a single key, or (2) mutations on objects, as class bindings do not support these use cases (the reference must change for class bindings to detect updates). See https://angular.dev/guide/templates/binding#css-class-and-style-property-bindings for more information. This rule helps identify potential simplification opportunities but should be applied judiciously based on your specific needs.',
75
+ };
@@ -1,7 +1,11 @@
1
- export type Options = [];
1
+ export type Options = [
2
+ {
3
+ readonly ignore?: readonly string[];
4
+ }
5
+ ];
2
6
  export type MessageIds = 'preferStaticStringProperties';
3
7
  export declare const RULE_NAME = "prefer-static-string-properties";
4
- declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferStaticStringProperties", [], import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
8
+ declare const _default: import("@typescript-eslint/utils/ts-eslint").RuleModule<"preferStaticStringProperties", Options, import("../utils/create-eslint-rule").RuleDocs, import("@typescript-eslint/utils/ts-eslint").RuleListener>;
5
9
  export default _default;
6
10
  export declare const RULE_DOCS_EXTENSION: {
7
11
  rationale: string;
@@ -1 +1 @@
1
- {"version":3,"file":"prefer-static-string-properties.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-static-string-properties.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,OAAO,GAAG,EAAE,CAAC;AACzB,MAAM,MAAM,UAAU,GAAG,8BAA8B,CAAC;AACxD,eAAO,MAAM,SAAS,oCAAoC,CAAC;;AAE3D,wBA0DG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
1
+ {"version":3,"file":"prefer-static-string-properties.d.ts","sourceRoot":"","sources":["../../src/rules/prefer-static-string-properties.ts"],"names":[],"mappings":"AAiBA,MAAM,MAAM,OAAO,GAAG;IACpB;QACE,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;KACrC;CACF,CAAC;AACF,MAAM,MAAM,UAAU,GAAG,8BAA8B,CAAC;AACxD,eAAO,MAAM,SAAS,oCAAoC,CAAC;;AAE3D,wBAiFG;AAEH,eAAO,MAAM,mBAAmB;;CAG/B,CAAC"}
@@ -4,6 +4,14 @@ exports.RULE_DOCS_EXTENSION = exports.RULE_NAME = void 0;
4
4
  const bundled_angular_compiler_1 = require("@angular-eslint/bundled-angular-compiler");
5
5
  const utils_1 = require("@angular-eslint/utils");
6
6
  const create_eslint_rule_1 = require("../utils/create-eslint-rule");
7
+ // Properties that are property-only DOM APIs and should not be converted to attributes
8
+ // because they behave differently (e.g., textContent doesn't HTML-encode when used as a property)
9
+ const PROPERTY_ONLY_DOM_APIS = [
10
+ 'innerHTML',
11
+ 'innerText',
12
+ 'outerHTML',
13
+ 'textContent',
14
+ ];
7
15
  exports.RULE_NAME = 'prefer-static-string-properties';
8
16
  exports.default = (0, create_eslint_rule_1.createESLintRule)({
9
17
  name: exports.RULE_NAME,
@@ -13,14 +21,30 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
13
21
  description: 'Ensures that static string values use property assignment instead of property binding.',
14
22
  },
15
23
  fixable: 'code',
16
- schema: [],
24
+ schema: [
25
+ {
26
+ type: 'object',
27
+ properties: {
28
+ ignore: {
29
+ type: 'array',
30
+ items: {
31
+ type: 'string',
32
+ },
33
+ description: 'An array of property names that should be ignored by this rule',
34
+ uniqueItems: true,
35
+ },
36
+ },
37
+ additionalProperties: false,
38
+ },
39
+ ],
17
40
  messages: {
18
41
  preferStaticStringProperties: 'Using a property is more efficient than binding a static string.',
19
42
  },
20
43
  },
21
- defaultOptions: [],
22
- create(context) {
44
+ defaultOptions: [{}],
45
+ create(context, [{ ignore = [] }]) {
23
46
  const parserServices = (0, utils_1.getTemplateParserServices)(context);
47
+ const ignoredProperties = new Set([...PROPERTY_ONLY_DOM_APIS, ...ignore]);
24
48
  return {
25
49
  ['BoundAttribute.inputs']({ name, sourceSpan, keySpan, value, }) {
26
50
  // Exclude @xxx (Animation) and xx.color
@@ -28,6 +52,10 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
28
52
  const isBindingProperty = keySpan?.details &&
29
53
  !keySpan.details.includes('@') &&
30
54
  !keySpan.details.includes('.');
55
+ // Skip if this property is in the ignore list or is a property-only DOM API
56
+ if (ignoredProperties.has(name)) {
57
+ return;
58
+ }
31
59
  if (isBindingProperty &&
32
60
  value instanceof bundled_angular_compiler_1.ASTWithSource &&
33
61
  value.ast instanceof bundled_angular_compiler_1.LiteralPrimitive &&
@@ -49,5 +77,5 @@ exports.default = (0, create_eslint_rule_1.createESLintRule)({
49
77
  },
50
78
  });
51
79
  exports.RULE_DOCS_EXTENSION = {
52
- rationale: 'When binding a static string literal to a property, using attribute syntax (property="value") is more efficient and simpler than property binding syntax ([property]="\'value\'"). Property binding with a static string creates unnecessary overhead because Angular evaluates it as an expression during change detection, even though the value never changes. Attribute syntax makes it immediately clear that the value is static and will never be updated. For example, [alt]="\'Profile image\'" should be alt="Profile image". This rule helps identify performance opportunities and makes templates more readable by distinguishing truly dynamic bindings from static values. The rule excludes animation bindings (@xxx), style/class sub-properties (style.color), and structural directives (*ngIf) where property binding syntax is required.',
80
+ rationale: 'When binding a static string literal to a property, using attribute syntax (property="value") is more efficient and simpler than property binding syntax ([property]="\'value\'"). Property binding with a static string creates unnecessary overhead because Angular evaluates it as an expression during change detection, even though the value never changes. Attribute syntax makes it immediately clear that the value is static and will never be updated. For example, [alt]="\'Profile image\'" should be alt="Profile image". This rule helps identify performance opportunities and makes templates more readable by distinguishing truly dynamic bindings from static values. The rule excludes animation bindings (@xxx), style/class sub-properties (style.color), and structural directives (*ngIf) where property binding syntax is required.\n\nThe rule automatically excludes certain property-only DOM APIs (innerHTML, innerText, outerHTML, textContent) because these properties behave differently than their attribute counterparts. For example, textContent as a property does not HTML-encode its value, while textContent as an attribute does. Converting [textContent]="\'<I>\'" to textContent="<I>" would change the behavior and potentially break functionality. You can also configure additional properties to ignore using the ignore option.',
53
81
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-eslint/eslint-plugin-template",
3
- "version": "21.1.1-alpha.1",
3
+ "version": "21.1.1-alpha.10",
4
4
  "description": "ESLint plugin for Angular Templates",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -20,19 +20,19 @@
20
20
  "dependencies": {
21
21
  "aria-query": "5.3.2",
22
22
  "axobject-query": "4.1.0",
23
- "@angular-eslint/bundled-angular-compiler": "21.1.1-alpha.1",
24
- "@angular-eslint/utils": "21.1.1-alpha.1"
23
+ "@angular-eslint/bundled-angular-compiler": "21.1.1-alpha.10",
24
+ "@angular-eslint/utils": "21.1.1-alpha.10"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/aria-query": "5.0.4",
28
- "@angular-eslint/test-utils": "21.1.1-alpha.1"
28
+ "@angular-eslint/test-utils": "21.1.1-alpha.10"
29
29
  },
30
30
  "peerDependencies": {
31
31
  "@typescript-eslint/types": "^7.11.0 || ^8.0.0",
32
32
  "@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
33
33
  "eslint": "^8.57.0 || ^9.0.0",
34
34
  "typescript": "*",
35
- "@angular-eslint/template-parser": "21.1.1-alpha.1"
35
+ "@angular-eslint/template-parser": "21.1.1-alpha.10"
36
36
  },
37
37
  "gitHead": "e2006e5e9c99e5a943d1a999e0efa5247d29ec24"
38
38
  }