@angular-eslint/eslint-plugin-template 21.1.1-alpha.3 → 21.1.1-alpha.5

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,6 +119,7 @@ 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>;
@@ -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,
@@ -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
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angular-eslint/eslint-plugin-template",
3
- "version": "21.1.1-alpha.3",
3
+ "version": "21.1.1-alpha.5",
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.3",
24
- "@angular-eslint/utils": "21.1.1-alpha.3"
23
+ "@angular-eslint/bundled-angular-compiler": "21.1.1-alpha.5",
24
+ "@angular-eslint/utils": "21.1.1-alpha.5"
25
25
  },
26
26
  "devDependencies": {
27
27
  "@types/aria-query": "5.0.4",
28
- "@angular-eslint/test-utils": "21.1.1-alpha.3"
28
+ "@angular-eslint/test-utils": "21.1.1-alpha.5"
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.3"
35
+ "@angular-eslint/template-parser": "21.1.1-alpha.5"
36
36
  },
37
37
  "gitHead": "e2006e5e9c99e5a943d1a999e0efa5247d29ec24"
38
38
  }