diff --git a/packages/eslint-plugin-template/docs/rules/attributes-order.md b/packages/eslint-plugin-template/docs/rules/attributes-order.md index adce7111a..02cb6093c 100644 --- a/packages/eslint-plugin-template/docs/rules/attributes-order.md +++ b/packages/eslint-plugin-template/docs/rules/attributes-order.md @@ -661,6 +661,94 @@ interface Options { ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ``` +
+ +--- + +
+ +#### Custom Config + +```json +{ + "rules": { + "@angular-eslint/template/attributes-order": [ + "error", + { + "alphabetical": true, + "order": [] + } + ] + } +} +``` + +
+ +#### ❌ Invalid Code + +```html +
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` + +
+ +--- + +
+ +#### Custom Config + +```json +{ + "rules": { + "@angular-eslint/template/attributes-order": [ + "error", + { + "alphabetical": true + } + ] + } +} +``` + +
+ +#### ❌ Invalid Code + +```html +
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` + +
+ +--- + +
+ +#### Default Config + +```json +{ + "rules": { + "@angular-eslint/template/attributes-order": [ + "error" + ] + } +} +``` + +
+ +#### ❌ Invalid Code + +```html +
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``` +
diff --git a/packages/eslint-plugin-template/src/rules/attributes-order.ts b/packages/eslint-plugin-template/src/rules/attributes-order.ts index cf23eaa40..100d1f854 100644 --- a/packages/eslint-plugin-template/src/rules/attributes-order.ts +++ b/packages/eslint-plugin-template/src/rules/attributes-order.ts @@ -126,7 +126,8 @@ export default createESLintRule({ }, end: { line: loc.end.line, - column: loc.end.column + 1, + column: + loc.end.column + (isValuelessStructuralDirective(attr) ? 0 : 1), }, }; default: @@ -423,6 +424,29 @@ function getMessageName(expected: ExtendedAttribute): string { } } +function isValuelessStructuralDirective(attr: ExtendedAttribute): boolean { + if (attr.orderType !== OrderType.StructuralDirective || !attr.keySpan) { + return false; + } + + const attrSpan = attr.sourceSpan; + const keySpan = attr.keySpan; + + /** + * A valueless structural directive will have the same span as its key. + * TextAttribute[value=''] is not always a reliable selector, because + * a *structuralDirective with `let var = something` will have value = '' + */ + return ( + attrSpan.start.offset === keySpan.start.offset && + attrSpan.start.line === keySpan.start.line && + attrSpan.start.col === keySpan.start.col && + attrSpan.end.offset === keySpan.end.offset && + attrSpan.end.line === keySpan.end.line && + attrSpan.end.col === keySpan.end.col + ); +} + function getStartPos(expected: ExtendedAttribute): number { switch (expected.orderType) { case OrderType.StructuralDirective: @@ -435,7 +459,10 @@ function getStartPos(expected: ExtendedAttribute): number { function getEndPos(expected: ExtendedAttribute): number { switch (expected.orderType) { case OrderType.StructuralDirective: - return expected.sourceSpan.end.offset + 1; + return ( + expected.sourceSpan.end.offset + + (isValuelessStructuralDirective(expected) ? 0 : 1) + ); default: return expected.sourceSpan.end.offset; } diff --git a/packages/eslint-plugin-template/tests/rules/attributes-order/cases.ts b/packages/eslint-plugin-template/tests/rules/attributes-order/cases.ts index 3e6dcd9a8..2a76c550f 100644 --- a/packages/eslint-plugin-template/tests/rules/attributes-order/cases.ts +++ b/packages/eslint-plugin-template/tests/rules/attributes-order/cases.ts @@ -400,4 +400,70 @@ export const invalid = [ `, }), + convertAnnotatedSourceToFailureCase({ + messageId, + description: `should work with valueless structural directive at beginning`, + annotatedSource: ` +
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + `, + options: [ + { + alphabetical: true, + order: [ + OrderType.AttributeBinding, + OrderType.TemplateReferenceVariable, + OrderType.InputBinding, + OrderType.OutputBinding, + OrderType.TwoWayBinding, + OrderType.StructuralDirective, + ], + }, + ], + data: { + expected: '`class`, `*structuralDirective`', + actual: '`*structuralDirective`, `class`', + }, + annotatedOutput: ` +
+ + `, + }), + + convertAnnotatedSourceToFailureCase({ + messageId, + description: + 'should work with valueless structural directive with no value in middle', + annotatedSource: ` +
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + `, + options: [{ alphabetical: true }], + data: { + expected: '`*structuralDirective`, `abbr`, `title`', + actual: '`title`, `*structuralDirective`, `abbr`', + }, + annotatedOutput: ` +
+ + `, + }), + + convertAnnotatedSourceToFailureCase({ + messageId, + description: + 'should work with valueless structural directive with no value at end', + annotatedSource: ` +
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + `, + data: { + expected: '`*structuralDirective`, `class`', + actual: '`class`, `*structuralDirective`', + }, + annotatedOutput: ` +
+ + `, + }), ];