@taiga-ui/eslint-plugin-experience-next 0.470.0 → 0.471.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.
package/README.md CHANGED
@@ -43,6 +43,7 @@ export default [
43
43
  | class-property-naming | Enforce custom naming for class properties based on their type | | 🔧 | |
44
44
  | decorator-key-sort | Sorts the keys of the object passed to the `@Component/@Injectable/@NgModule/@Pipe` decorator | ✅ | 🔧 | |
45
45
  | flat-exports | Spread nested arrays when exporting Angular entity collections | | 🔧 | |
46
+ | host-attributes-sort | Sort Angular host metadata attributes using configurable attribute groups | ✅ | 🔧 | |
46
47
  | injection-token-description | They are required to provide a description for `InjectionToken` | ✅ | | |
47
48
  | no-deep-imports | Disables deep imports of Taiga UI packages | ✅ | 🔧 | |
48
49
  | no-deep-imports-to-indexed-packages | Disallow deep imports from packages that expose an index.ts next to ng-package.json or package.json | ✅ | 🔧 | |
@@ -180,6 +181,105 @@ export const TuiInput = [...TuiTextfield, TuiInputDirective] as const;
180
181
 
181
182
  ---
182
183
 
184
+ ## host-attributes-sort
185
+
186
+ <sup>`✅ Recommended`</sup> <sup>`Fixable`</sup>
187
+
188
+ Sorts Angular `host` metadata entries in `@Component` and `@Directive` using configurable attribute groups, matching the
189
+ same grouping model used for template attributes in Prettier. The recommended config enables the rule with a default
190
+ group order that places `id` before plain attributes, `class`, animation bindings, inputs, two-way bindings, and
191
+ outputs.
192
+
193
+ ```ts
194
+ // ❌ error
195
+ @Component({
196
+ host: {
197
+ '(click)': 'handleClick()',
198
+ '[value]': 'value()',
199
+ class: 'cmp',
200
+ id: 'cmp-id',
201
+ },
202
+ })
203
+
204
+ // ✅ after autofix
205
+ @Component({
206
+ host: {
207
+ id: 'cmp-id',
208
+ class: 'cmp',
209
+ '[value]': 'value()',
210
+ '(click)': 'handleClick()',
211
+ },
212
+ })
213
+ ```
214
+
215
+ The rule understands the same preset names as `prettier-plugin-organize-attributes`. You can use aggregate presets such
216
+ as `$ANGULAR`, `$HTML`, and `$CODE_GUIDE`, or compose atomic presets such as `$CLASS`, `$ID`, `$ARIA`, `$ANGULAR_INPUT`,
217
+ `$ANGULAR_TWO_WAY_BINDING`, and `$ANGULAR_OUTPUT`.
218
+
219
+ ```json
220
+ {
221
+ "@taiga-ui/experience-next/host-attributes-sort": [
222
+ "error",
223
+ {
224
+ "attributeGroups": ["$ANGULAR"]
225
+ }
226
+ ]
227
+ }
228
+ ```
229
+
230
+ Use `$ANGULAR` when `host` should follow the familiar Angular template-style order:
231
+ `class -> id -> #ref -> *directive -> @animation -> [@animation] -> [(model)] -> [input] -> (output)`.
232
+
233
+ ```json
234
+ {
235
+ "@taiga-ui/experience-next/host-attributes-sort": [
236
+ "error",
237
+ {
238
+ "attributeGroups": ["$HTML"]
239
+ }
240
+ ]
241
+ }
242
+ ```
243
+
244
+ Use `$HTML` when only `class` and `id` should be pulled to the front, and everything else can stay in the trailing
245
+ default group.
246
+
247
+ ```json
248
+ {
249
+ "@taiga-ui/experience-next/host-attributes-sort": [
250
+ "error",
251
+ {
252
+ "attributeGroups": ["$CODE_GUIDE"]
253
+ }
254
+ ]
255
+ }
256
+ ```
257
+
258
+ Use `$CODE_GUIDE` for a wider HTML-oriented order: `class`, `id`, `name`, `data-*`, `src`, `for`, `type`, `href`,
259
+ `value`, `title`, `alt`, `role`, `aria-*`.
260
+
261
+ ```json
262
+ {
263
+ "@taiga-ui/experience-next/host-attributes-sort": [
264
+ "error",
265
+ {
266
+ "attributeGroups": ["$ID", "$DEFAULT", "$ARIA", "$ANGULAR_OUTPUT"]
267
+ }
268
+ ]
269
+ }
270
+ ```
271
+
272
+ Use atomic presets when you want a custom order instead of one of the bundled aliases.
273
+
274
+ | Option | Type | Description |
275
+ | --------------------- | --------------------------- | ----------------------------------------------------------------- |
276
+ | `attributeGroups` | `string[]` | Group order. Supports the same preset tokens as Prettier plugins. |
277
+ | `attributeIgnoreCase` | `boolean` | Ignore case when matching custom regexp groups. |
278
+ | `attributeSort` | `'ASC' \| 'DESC' \| 'NONE'` | Sort order inside each matched group. |
279
+ | `decorators` | `string[]` | Decorator names whose `host` metadata should be checked. |
280
+
281
+ ---
282
+
183
283
  ## injection-token-description
184
284
 
185
285
  <sup>`✅ Recommended`</sup>
package/index.d.ts CHANGED
@@ -18,6 +18,14 @@ declare const plugin: {
18
18
  'flat-exports': import("@typescript-eslint/utils/ts-eslint").RuleModule<"spreadArrays", [], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
19
19
  name: string;
20
20
  };
21
+ 'host-attributes-sort': import("@typescript-eslint/utils/ts-eslint").RuleModule<"incorrectOrder", [{
22
+ attributeGroups?: string[];
23
+ attributeIgnoreCase?: boolean;
24
+ attributeSort?: "ASC" | "DESC" | "NONE";
25
+ decorators?: string[];
26
+ }], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
27
+ name: string;
28
+ };
21
29
  'html-logical-properties': import("eslint").Rule.RuleModule;
22
30
  'injection-token-description': import("@typescript-eslint/utils/ts-eslint").RuleModule<"invalid-injection-token-description", readonly unknown[], unknown, import("@typescript-eslint/utils/ts-eslint").RuleListener> & {
23
31
  name: string;
package/index.esm.js CHANGED
@@ -896,6 +896,7 @@ var recommended = defineConfig([
896
896
  Pipe: ['standalone', 'name', 'pure'],
897
897
  },
898
898
  ],
899
+ '@taiga-ui/experience-next/host-attributes-sort': 'error',
899
900
  '@taiga-ui/experience-next/injection-token-description': 'error',
900
901
  '@taiga-ui/experience-next/no-deep-imports': [
901
902
  'error',
@@ -1334,8 +1335,8 @@ function intersect(a, b) {
1334
1335
  return a.some((type) => origin.has(type));
1335
1336
  }
1336
1337
 
1337
- const createRule$g = ESLintUtils.RuleCreator((name) => name);
1338
- var classPropertyNaming = createRule$g({
1338
+ const createRule$h = ESLintUtils.RuleCreator((name) => name);
1339
+ var classPropertyNaming = createRule$h({
1339
1340
  create(context, [configs]) {
1340
1341
  const parserServices = ESLintUtils.getParserServices(context);
1341
1342
  const typeChecker = parserServices.program.getTypeChecker();
@@ -1504,9 +1505,9 @@ function isExternalPureTuple(typeChecker, type) {
1504
1505
  return typeArgs.every((item) => isClassType(item));
1505
1506
  }
1506
1507
 
1507
- const createRule$f = ESLintUtils.RuleCreator((name) => name);
1508
+ const createRule$g = ESLintUtils.RuleCreator((name) => name);
1508
1509
  const MESSAGE_ID$7 = 'spreadArrays';
1509
- var flatExports = createRule$f({
1510
+ var flatExports = createRule$g({
1510
1511
  create(context) {
1511
1512
  const parserServices = ESLintUtils.getParserServices(context);
1512
1513
  const typeChecker = parserServices.program.getTypeChecker();
@@ -1630,6 +1631,317 @@ var flatExports = createRule$f({
1630
1631
  name: 'flat-exports',
1631
1632
  });
1632
1633
 
1634
+ function isObject(node) {
1635
+ return node?.type === AST_NODE_TYPES$1.ObjectExpression;
1636
+ }
1637
+
1638
+ /**
1639
+ * Extracts the metadata object from a class decorator such as
1640
+ * `@Component()`, `@Directive()`, `@NgModule()`, or `@Pipe()`.
1641
+ *
1642
+ * Returns the first argument of the decorator call *if and only if*
1643
+ * it is an `ObjectExpression`.
1644
+ *
1645
+ * @example
1646
+ * // Given:
1647
+ * @Component({
1648
+ * selector: 'x',
1649
+ * imports: [A, B],
1650
+ * })
1651
+ * class MyCmp {}
1652
+ *
1653
+ * // In the AST for @Component(...)
1654
+ * getDecoratorMetadata(decorator, allowed) →
1655
+ * ObjectExpression({ selector: ..., imports: ... })
1656
+ *
1657
+ * @param decorator - The decorator node attached to a class declaration.
1658
+ * @param allowedNames - A set of decorator names to consider
1659
+ * (e.g., Component, Directive, NgModule, Pipe).
1660
+ *
1661
+ * @returns The metadata `ObjectExpression` if present and valid,
1662
+ * otherwise `null`.
1663
+ */
1664
+ function getDecoratorMetadata(decorator, allowedNames) {
1665
+ const expr = decorator.expression;
1666
+ if (expr.type !== AST_NODE_TYPES$1.CallExpression) {
1667
+ return null;
1668
+ }
1669
+ const callee = expr.callee;
1670
+ if (callee.type !== AST_NODE_TYPES$1.Identifier) {
1671
+ return null;
1672
+ }
1673
+ if (!allowedNames.has(callee.name)) {
1674
+ return null;
1675
+ }
1676
+ const arg = expr.arguments[0];
1677
+ return isObject(arg) ? arg : null;
1678
+ }
1679
+
1680
+ function sameOrder(a, b) {
1681
+ return a.length === b.length && a.every((value, index) => value === b[index]);
1682
+ }
1683
+
1684
+ const DEFAULT_GROUP = '$DEFAULT';
1685
+ const DEFAULT_ATTRIBUTE_GROUPS = [
1686
+ '$ANGULAR_STRUCTURAL_DIRECTIVE',
1687
+ '$ANGULAR_ELEMENT_REF',
1688
+ '$ID',
1689
+ '$DEFAULT',
1690
+ '$CLASS',
1691
+ '$ANGULAR_ANIMATION',
1692
+ '$ANGULAR_ANIMATION_INPUT',
1693
+ '$ANGULAR_INPUT',
1694
+ '$ANGULAR_TWO_WAY_BINDING',
1695
+ '$ANGULAR_OUTPUT',
1696
+ ];
1697
+ const DEFAULT_DECORATORS$1 = ['Component', 'Directive'];
1698
+ const PRESETS = {
1699
+ $ALT: /^alt$/,
1700
+ $ANGULAR: [
1701
+ '$CLASS',
1702
+ '$ID',
1703
+ '$ANGULAR_ELEMENT_REF',
1704
+ '$ANGULAR_STRUCTURAL_DIRECTIVE',
1705
+ '$ANGULAR_ANIMATION',
1706
+ '$ANGULAR_ANIMATION_INPUT',
1707
+ '$ANGULAR_TWO_WAY_BINDING',
1708
+ '$ANGULAR_INPUT',
1709
+ '$ANGULAR_OUTPUT',
1710
+ ],
1711
+ $ANGULAR_ANIMATION: /^@/,
1712
+ $ANGULAR_ANIMATION_INPUT: /^\[@/,
1713
+ $ANGULAR_ELEMENT_REF: /^#/,
1714
+ $ANGULAR_INPUT: /^\[[^(@]/,
1715
+ $ANGULAR_OUTPUT: /^\(/,
1716
+ $ANGULAR_STRUCTURAL_DIRECTIVE: /^\*/,
1717
+ $ANGULAR_TWO_WAY_BINDING: /^\[\(/,
1718
+ $ARIA: /^aria-/,
1719
+ $CLASS: /^class$/,
1720
+ $CODE_GUIDE: [
1721
+ '$CLASS',
1722
+ '$ID',
1723
+ '$NAME',
1724
+ '$DATA',
1725
+ '$SRC',
1726
+ '$FOR',
1727
+ '$TYPE',
1728
+ '$HREF',
1729
+ '$VALUE',
1730
+ '$TITLE',
1731
+ '$ALT',
1732
+ '$ROLE',
1733
+ '$ARIA',
1734
+ ],
1735
+ $DATA: /^data-/,
1736
+ $FOR: /^for$/,
1737
+ $HREF: /^href$/,
1738
+ $HTML: ['$CLASS', '$ID'],
1739
+ $ID: /^id$/,
1740
+ $NAME: /^name$/,
1741
+ $ROLE: /^role$/,
1742
+ $SRC: /^src$/,
1743
+ $TITLE: /^title$/,
1744
+ $TYPE: /^type$/,
1745
+ $VALUE: /^value$/,
1746
+ $VUE: ['$CLASS', '$ID', '$VUE_ATTRIBUTE'],
1747
+ $VUE_ATTRIBUTE: /^v-/,
1748
+ };
1749
+ const createRule$f = ESLintUtils.RuleCreator((name) => name);
1750
+ const rule$i = createRule$f({
1751
+ create(context, [options]) {
1752
+ const sourceCode = context.sourceCode;
1753
+ const settings = {
1754
+ attributeGroups: [...DEFAULT_ATTRIBUTE_GROUPS],
1755
+ attributeIgnoreCase: true,
1756
+ attributeSort: 'ASC',
1757
+ decorators: [...DEFAULT_DECORATORS$1],
1758
+ ...options,
1759
+ };
1760
+ const allowedDecorators = new Set(settings.decorators);
1761
+ return {
1762
+ ClassDeclaration(node) {
1763
+ for (const decorator of node?.decorators ?? []) {
1764
+ const metadata = getDecoratorMetadata(decorator, allowedDecorators);
1765
+ if (!metadata) {
1766
+ continue;
1767
+ }
1768
+ const hostObject = getHostObject(metadata);
1769
+ if (!hostObject) {
1770
+ continue;
1771
+ }
1772
+ const properties = getHostAttributeProperties(hostObject);
1773
+ if (!properties || properties.length <= 1) {
1774
+ continue;
1775
+ }
1776
+ const sortedProperties = organizeProperties(properties, settings);
1777
+ const currentOrder = properties.map(({ name }) => name);
1778
+ const expectedOrder = sortedProperties.map(({ name }) => name);
1779
+ if (sameOrder(currentOrder, expectedOrder)) {
1780
+ continue;
1781
+ }
1782
+ const report = {
1783
+ data: { expected: expectedOrder.join(', ') },
1784
+ messageId: 'incorrectOrder',
1785
+ node: hostObject,
1786
+ };
1787
+ if (sourceCode.getCommentsInside(hostObject).length > 0) {
1788
+ context.report(report);
1789
+ continue;
1790
+ }
1791
+ context.report({
1792
+ ...report,
1793
+ fix: (fixer) => fixer.replaceTextRange(hostObject.range, `{${sortedProperties
1794
+ .map(({ node: property }) => sourceCode.getText(property))
1795
+ .join(', ')}}`),
1796
+ });
1797
+ }
1798
+ },
1799
+ };
1800
+ },
1801
+ meta: {
1802
+ defaultOptions: [
1803
+ {
1804
+ attributeGroups: [...DEFAULT_ATTRIBUTE_GROUPS],
1805
+ attributeIgnoreCase: true,
1806
+ attributeSort: 'ASC',
1807
+ decorators: [...DEFAULT_DECORATORS$1],
1808
+ },
1809
+ ],
1810
+ docs: {
1811
+ description: 'Sort Angular host metadata attributes using configurable attribute groups.',
1812
+ },
1813
+ fixable: 'code',
1814
+ messages: { incorrectOrder: 'Host attributes should be sorted as [{{expected}}]' },
1815
+ schema: [
1816
+ {
1817
+ additionalProperties: false,
1818
+ properties: {
1819
+ attributeGroups: {
1820
+ items: { type: 'string' },
1821
+ type: 'array',
1822
+ },
1823
+ attributeIgnoreCase: { type: 'boolean' },
1824
+ attributeSort: {
1825
+ enum: ['ASC', 'DESC', 'NONE'],
1826
+ type: 'string',
1827
+ },
1828
+ decorators: {
1829
+ items: { type: 'string' },
1830
+ type: 'array',
1831
+ },
1832
+ },
1833
+ type: 'object',
1834
+ },
1835
+ ],
1836
+ type: 'problem',
1837
+ },
1838
+ name: 'host-attributes-sort',
1839
+ });
1840
+ function getHostObject(metadata) {
1841
+ for (const property of metadata.properties) {
1842
+ if (property.type !== AST_NODE_TYPES$1.Property ||
1843
+ property.kind !== 'init' ||
1844
+ property.computed ||
1845
+ property.method) {
1846
+ continue;
1847
+ }
1848
+ if (getStaticPropertyName(property.key) !== 'host') {
1849
+ continue;
1850
+ }
1851
+ return property.value.type === AST_NODE_TYPES$1.ObjectExpression
1852
+ ? property.value
1853
+ : null;
1854
+ }
1855
+ return null;
1856
+ }
1857
+ function getHostAttributeProperties(hostObject) {
1858
+ const properties = [];
1859
+ for (const property of hostObject.properties) {
1860
+ if (property.type !== AST_NODE_TYPES$1.Property ||
1861
+ property.kind !== 'init' ||
1862
+ property.computed ||
1863
+ property.method) {
1864
+ return null;
1865
+ }
1866
+ const name = getStaticPropertyName(property.key);
1867
+ if (name === null) {
1868
+ return null;
1869
+ }
1870
+ properties.push({ name, node: property });
1871
+ }
1872
+ return properties;
1873
+ }
1874
+ function getStaticPropertyName(key) {
1875
+ if (key.type === AST_NODE_TYPES$1.Identifier) {
1876
+ return key.name;
1877
+ }
1878
+ if (key.type === AST_NODE_TYPES$1.Literal &&
1879
+ (typeof key.value === 'string' || typeof key.value === 'number')) {
1880
+ return String(key.value);
1881
+ }
1882
+ if (key.type === AST_NODE_TYPES$1.TemplateLiteral &&
1883
+ key.expressions.length === 0 &&
1884
+ key.quasis.length === 1) {
1885
+ return key.quasis[0]?.value.cooked ?? null;
1886
+ }
1887
+ return null;
1888
+ }
1889
+ function organizeProperties(properties, options) {
1890
+ const groups = getGroups(options.attributeGroups.length > 0 ? options.attributeGroups : ['$ANGULAR'], options.attributeIgnoreCase);
1891
+ const defaultGroup = ensureDefaultGroup(groups);
1892
+ for (const property of properties) {
1893
+ const targetGroup = groups.find(({ regexp }) => regexp?.test(property.name)) ?? defaultGroup;
1894
+ targetGroup.values.push(property);
1895
+ }
1896
+ if (options.attributeSort !== 'NONE') {
1897
+ for (const group of groups) {
1898
+ group.values.sort((left, right) => left.name.localeCompare(right.name));
1899
+ if (options.attributeSort === 'DESC') {
1900
+ group.values.reverse();
1901
+ }
1902
+ }
1903
+ }
1904
+ return groups.flatMap(({ values }) => values);
1905
+ }
1906
+ function getGroups(queries, ignoreCase) {
1907
+ return queries.flatMap((query) => getGroup(query, ignoreCase));
1908
+ }
1909
+ function getGroup(query, ignoreCase) {
1910
+ if (query === DEFAULT_GROUP) {
1911
+ return [createDefaultGroup()];
1912
+ }
1913
+ const preset = PRESETS[query];
1914
+ if (!preset) {
1915
+ return [
1916
+ {
1917
+ query,
1918
+ regexp: new RegExp(query, ignoreCase ? 'i' : ''),
1919
+ values: [],
1920
+ },
1921
+ ];
1922
+ }
1923
+ if (Array.isArray(preset)) {
1924
+ return preset.flatMap((item) => getGroup(item, ignoreCase));
1925
+ }
1926
+ return [{ query, regexp: preset, values: [] }];
1927
+ }
1928
+ function ensureDefaultGroup(groups) {
1929
+ const existing = groups.find(({ unknown }) => unknown);
1930
+ if (existing) {
1931
+ return existing;
1932
+ }
1933
+ const fallback = createDefaultGroup();
1934
+ groups.push(fallback);
1935
+ return fallback;
1936
+ }
1937
+ function createDefaultGroup() {
1938
+ return {
1939
+ query: DEFAULT_GROUP,
1940
+ unknown: true,
1941
+ values: [],
1942
+ };
1943
+ }
1944
+
1633
1945
  const DIRECTIONAL_TO_LOGICAL = {
1634
1946
  'border-bottom': 'border-block-end',
1635
1947
  'border-left': 'border-inline-start',
@@ -5315,52 +5627,6 @@ const rule$1 = createRule$2({
5315
5627
  name: 'short-tui-imports',
5316
5628
  });
5317
5629
 
5318
- function isObject(node) {
5319
- return node?.type === AST_NODE_TYPES$1.ObjectExpression;
5320
- }
5321
-
5322
- /**
5323
- * Extracts the metadata object from a class decorator such as
5324
- * `@Component()`, `@Directive()`, `@NgModule()`, or `@Pipe()`.
5325
- *
5326
- * Returns the first argument of the decorator call *if and only if*
5327
- * it is an `ObjectExpression`.
5328
- *
5329
- * @example
5330
- * // Given:
5331
- * @Component({
5332
- * selector: 'x',
5333
- * imports: [A, B],
5334
- * })
5335
- * class MyCmp {}
5336
- *
5337
- * // In the AST for @Component(...)
5338
- * getDecoratorMetadata(decorator, allowed) →
5339
- * ObjectExpression({ selector: ..., imports: ... })
5340
- *
5341
- * @param decorator - The decorator node attached to a class declaration.
5342
- * @param allowedNames - A set of decorator names to consider
5343
- * (e.g., Component, Directive, NgModule, Pipe).
5344
- *
5345
- * @returns The metadata `ObjectExpression` if present and valid,
5346
- * otherwise `null`.
5347
- */
5348
- function getDecoratorMetadata(decorator, allowedNames) {
5349
- const expr = decorator.expression;
5350
- if (expr.type !== AST_NODE_TYPES$1.CallExpression) {
5351
- return null;
5352
- }
5353
- const callee = expr.callee;
5354
- if (callee.type !== AST_NODE_TYPES$1.Identifier) {
5355
- return null;
5356
- }
5357
- if (!allowedNames.has(callee.name)) {
5358
- return null;
5359
- }
5360
- const arg = expr.arguments[0];
5361
- return isObject(arg) ? arg : null;
5362
- }
5363
-
5364
5630
  function getImportsArray(meta) {
5365
5631
  const property = meta.properties.find((literal) => literal.type === AST_NODE_TYPES$1.Property &&
5366
5632
  literal.key.type === AST_NODE_TYPES$1.Identifier &&
@@ -5436,10 +5702,6 @@ function getSortedNames(elements, source) {
5436
5702
  return [...sortedRegular, ...sortedSpreads].map((n) => nameOf(n, source));
5437
5703
  }
5438
5704
 
5439
- function sameOrder(a, b) {
5440
- return a.length === b.length && a.every((value, index) => value === b[index]);
5441
- }
5442
-
5443
5705
  const createRule$1 = ESLintUtils.RuleCreator((name) => name);
5444
5706
  var standaloneImportsSort = createRule$1({
5445
5707
  create(context, [options]) {
@@ -5609,6 +5871,7 @@ const plugin = {
5609
5871
  'class-property-naming': classPropertyNaming,
5610
5872
  'decorator-key-sort': config$3,
5611
5873
  'flat-exports': flatExports,
5874
+ 'host-attributes-sort': rule$i,
5612
5875
  'html-logical-properties': config$2,
5613
5876
  'injection-token-description': rule$h,
5614
5877
  'no-deep-imports': rule$g,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taiga-ui/eslint-plugin-experience-next",
3
- "version": "0.470.0",
3
+ "version": "0.471.0",
4
4
  "description": "An ESLint plugin to enforce a consistent code styles across taiga-ui projects",
5
5
  "repository": {
6
6
  "type": "git",
@@ -0,0 +1,14 @@
1
+ import { ESLintUtils } from '@typescript-eslint/utils';
2
+ type SortOrder = 'ASC' | 'DESC' | 'NONE';
3
+ type Options = [
4
+ {
5
+ attributeGroups?: string[];
6
+ attributeIgnoreCase?: boolean;
7
+ attributeSort?: SortOrder;
8
+ decorators?: string[];
9
+ }
10
+ ];
11
+ export declare const rule: ESLintUtils.RuleModule<"incorrectOrder", Options, unknown, ESLintUtils.RuleListener> & {
12
+ name: string;
13
+ };
14
+ export default rule;