@ashstack/lint 0.1.1 → 0.2.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 (102) hide show
  1. package/README.md +1 -1
  2. package/dist/core/index.js +1 -222
  3. package/dist/core/rules/core/components-tsx-only.js +1 -37
  4. package/dist/core/rules/core/hoist-intl.js +1 -32
  5. package/dist/core/rules/core/index.js +1 -18
  6. package/dist/core/rules/core/no-comments.js +1 -194
  7. package/dist/core/rules/core/no-naming-convention.js +1 -143
  8. package/dist/core/rules/core/use-design-system.js +1 -147
  9. package/dist/core/rules/zod/index.js +1 -12
  10. package/dist/core/rules/zod/prefer-enum.js +1 -44
  11. package/dist/index.js +1 -3
  12. package/dist/lib/ast.js +1 -162
  13. package/dist/lib/detect.js +1 -42
  14. package/dist/lib/merge.js +1 -18
  15. package/dist/lib/module.js +1 -49
  16. package/dist/lib/types.js +1 -1
  17. package/dist/react/index.js +1 -126
  18. package/dist/react/rules/i18n/index.js +1 -16
  19. package/dist/react/rules/i18n/no-bare-attrs.js +1 -38
  20. package/dist/react/rules/i18n/no-bare-text.js +1 -26
  21. package/dist/react/rules/i18n/no-bare-toast.js +1 -38
  22. package/dist/react/rules/query/index.js +1 -20
  23. package/dist/react/rules/query/next-page-param-undefined.js +1 -26
  24. package/dist/react/rules/query/no-deprecated-filters.js +1 -63
  25. package/dist/react/rules/query/no-fetch-in-query-fn.js +1 -22
  26. package/dist/react/rules/query/no-inline-keys.js +1 -107
  27. package/dist/react/rules/query/require-destructured-hooks.js +1 -41
  28. package/dist/react/rules/query/shared.js +1 -10
  29. package/dist/react/rules/zustand/index.js +1 -12
  30. package/dist/react/rules/zustand/require-selector.js +1 -62
  31. package/dist/react-native/index.js +1 -113
  32. package/dist/react-native/rules/keyboard/index.js +1 -18
  33. package/dist/react-native/rules/legend-list/index.js +1 -51
  34. package/dist/react-native/rules/legend-list/no-flex-in-content-container.js +1 -77
  35. package/dist/react-native/rules/legend-list/no-index-key-extractor.js +1 -26
  36. package/dist/react-native/rules/legend-list/no-inline-data.js +1 -25
  37. package/dist/react-native/rules/legend-list/no-inline-extra-data.js +1 -21
  38. package/dist/react-native/rules/legend-list/no-inline-render-item-props.js +1 -35
  39. package/dist/react-native/rules/legend-list/no-mixed-children.js +1 -24
  40. package/dist/react-native/rules/legend-list/no-remount-key.js +1 -18
  41. package/dist/react-native/rules/legend-list/no-scrollview-map.js +1 -27
  42. package/dist/react-native/rules/legend-list/no-unsupported-props.js +1 -29
  43. package/dist/react-native/rules/legend-list/required-props.js +1 -23
  44. package/dist/react-native/rules/legend-list/shared.js +1 -25
  45. package/dist/react-native/rules/legend-list/typed-items-need-item-type.js +1 -49
  46. package/dist/react-native/rules/legend-state/index.js +1 -35
  47. package/dist/react-native/rules/legend-state/naming.js +1 -26
  48. package/dist/react-native/rules/legend-state/no-assignment.js +1 -31
  49. package/dist/react-native/rules/legend-state/no-nested-observable.js +1 -23
  50. package/dist/react-native/rules/legend-state/no-object-selector.js +1 -47
  51. package/dist/react-native/rules/legend-state/no-observable-in-component.js +1 -46
  52. package/dist/react-native/rules/legend-state/no-peek-in-selector.js +1 -67
  53. package/dist/react-native/rules/legend-state/no-react-mirror.js +1 -33
  54. package/dist/react-native/rules/legend-state/no-untracked-get-in-jsx.js +1 -52
  55. package/dist/react-native/rules/legend-state/shared.js +1 -23
  56. package/dist/react-native/rules/react-native/hoist-stateless-function.js +1 -59
  57. package/dist/react-native/rules/react-native/index.js +1 -50
  58. package/dist/react-native/rules/react-native/no-conditional-style-array.js +1 -23
  59. package/dist/react-native/rules/react-native/no-dynamic-import.js +1 -18
  60. package/dist/react-native/rules/react-native/no-keyboard-will-events.js +1 -18
  61. package/dist/react-native/rules/react-native/no-leaked-render.js +1 -43
  62. package/dist/react-native/rules/react-native/no-manual-memo.js +1 -87
  63. package/dist/react-native/rules/react-native/no-redundant-view-nesting.js +1 -36
  64. package/dist/react-native/rules/react-native/no-rn-image-network-source.js +1 -58
  65. package/dist/react-native/rules/react-native/no-rn-namespace-import.js +1 -38
  66. package/dist/react-native/rules/react-native/no-scroll-position-state.js +1 -26
  67. package/dist/react-native/rules/react-native/no-unlabeled-icon-pressable.js +1 -62
  68. package/dist/react-native/rules/reanimated/animated-reaction-safety.js +1 -110
  69. package/dist/react-native/rules/reanimated/animated-style-needs-animated-component.js +1 -72
  70. package/dist/react-native/rules/reanimated/animated-updater-purity.js +1 -38
  71. package/dist/react-native/rules/reanimated/gpu-properties-only.js +1 -80
  72. package/dist/react-native/rules/reanimated/hoist-layout-animation-builder.js +1 -43
  73. package/dist/react-native/rules/reanimated/index.js +1 -51
  74. package/dist/react-native/rules/reanimated/interpolate-needs-clamp.js +1 -41
  75. package/dist/react-native/rules/reanimated/no-react-state-from-continuous-worklet.js +1 -86
  76. package/dist/react-native/rules/reanimated/no-shared-value-dot-value.js +1 -112
  77. package/dist/react-native/rules/reanimated/prefer-lazy-shared-value-initializer.js +1 -24
  78. package/dist/react-native/rules/reanimated/schedule-on-rn-scope.js +1 -21
  79. package/dist/react-native/rules/reanimated/shared-value-usage.js +1 -105
  80. package/dist/react-native/rules/reanimated/shared.js +1 -13
  81. package/dist/react-native/rules/skia/canvas-opaque.js +1 -49
  82. package/dist/react-native/rules/skia/index.js +1 -21
  83. package/dist/react-native/rules/turbo-image/index.js +1 -14
  84. package/dist/react-native/rules/turbo-image/require-cache-policy.js +1 -21
  85. package/dist/react-native/rules/turbo-image/require-resize.js +1 -22
  86. package/dist/react-native/rules/unistyles/animated-theme.js +1 -37
  87. package/dist/react-native/rules/unistyles/content-container.js +1 -73
  88. package/dist/react-native/rules/unistyles/in-sheet.js +1 -116
  89. package/dist/react-native/rules/unistyles/index.js +1 -58
  90. package/dist/react-native/rules/unistyles/insets.js +1 -92
  91. package/dist/react-native/rules/unistyles/no-hardcoded-color.js +1 -30
  92. package/dist/react-native/rules/unistyles/no-hardcoded-spacing.js +1 -78
  93. package/dist/react-native/rules/unistyles/no-margin.js +1 -19
  94. package/dist/react-native/rules/unistyles/no-style-spread.js +1 -41
  95. package/dist/react-native/rules/unistyles/no-unused-styles.js +1 -99
  96. package/dist/react-native/rules/unistyles/rtl-style-call.js +1 -42
  97. package/dist/react-native/rules/unistyles/shared.js +1 -70
  98. package/dist/react-native/rules/unistyles/theme-screen-component.js +1 -22
  99. package/dist/react-native/rules/unistyles/theme-style-attr.js +1 -18
  100. package/dist/react-native/stylesheet.js +1 -44
  101. package/package.json +3 -4
  102. package/RULES.md +0 -2881
package/README.md CHANGED
@@ -53,7 +53,7 @@ Off by default, since they need a team decision first: `@ashstack/core/no-commen
53
53
 
54
54
  ## Rule reference
55
55
 
56
- [RULES.md](./RULES.md) covers every rule, with options and examples. It ships in this package, generated from the rules and CI-checked.
56
+ [RULES.md](https://github.com/AlshehriAli0/ashStack/blob/main/packages/lint/RULES.md) covers every rule, with options and examples. It is generated from the rules and CI-checked.
57
57
 
58
58
  ## License
59
59
 
@@ -1,222 +1 @@
1
- import { composeModules } from "../lib/module.js";
2
- import coreModule from "./rules/core/index.js";
3
- import zodModule from "./rules/zod/index.js";
4
- export const coreModules = [coreModule, zodModule];
5
- const BASE_RULES = {
6
- "array-callback-return": "error",
7
- "constructor-super": "error",
8
- "default-param-last": "error",
9
- eqeqeq: ["error", "always", { null: "ignore" }],
10
- "for-direction": "error",
11
- "getter-return": "error",
12
- "func-style": ["error", "expression"],
13
- "no-array-constructor": "error",
14
- "no-async-promise-executor": "error",
15
- "no-case-declarations": "error",
16
- "no-class-assign": "error",
17
- "no-compare-neg-zero": "error",
18
- "no-cond-assign": "error",
19
- "no-const-assign": "error",
20
- "no-constant-condition": "error",
21
- "no-control-regex": "error",
22
- "no-debugger": "error",
23
- "no-dupe-class-members": "error",
24
- "no-dupe-else-if": "error",
25
- "no-dupe-keys": "error",
26
- "no-duplicate-case": "error",
27
- "no-empty": "error",
28
- "no-empty-character-class": "error",
29
- "no-empty-pattern": "error",
30
- "no-ex-assign": "error",
31
- "no-extra-boolean-cast": "error",
32
- "no-fallthrough": "error",
33
- "no-func-assign": "error",
34
- "no-global-assign": "error",
35
- "no-import-assign": "error",
36
- "no-irregular-whitespace": "error",
37
- "no-loss-of-precision": "error",
38
- "no-misleading-character-class": "error",
39
- "no-negated-condition": "error",
40
- "no-nested-ternary": "error",
41
- "no-new-native-nonconstructor": "error",
42
- "no-nonoctal-decimal-escape": "error",
43
- "no-obj-calls": "error",
44
- "no-param-reassign": "error",
45
- "no-prototype-builtins": "error",
46
- "no-redeclare": "off",
47
- "no-regex-spaces": "error",
48
- "no-self-assign": "error",
49
- "no-setter-return": "error",
50
- "no-shadow-restricted-names": "error",
51
- "no-sparse-arrays": "error",
52
- "no-this-before-super": "error",
53
- "no-undef": "off",
54
- "no-unreachable": "error",
55
- "no-unsafe-finally": "error",
56
- "no-unsafe-negation": "error",
57
- "no-unsafe-optional-chaining": "error",
58
- "no-unused-labels": "error",
59
- "no-unused-private-class-members": "error",
60
- "no-useless-catch": "error",
61
- "no-useless-constructor": "error",
62
- "no-useless-escape": "error",
63
- "no-var": "error",
64
- "no-with": "error",
65
- "object-shorthand": ["error", "methods"],
66
- "prefer-rest-params": "error",
67
- "require-yield": "error",
68
- "use-isnan": "error",
69
- "valid-typeof": "error",
70
- "no-unused-vars": [
71
- "error",
72
- {
73
- args: "after-used",
74
- argsIgnorePattern: "^_",
75
- caughtErrors: "all",
76
- caughtErrorsIgnorePattern: "^_",
77
- varsIgnorePattern: "^_",
78
- destructuredArrayIgnorePattern: "^_",
79
- ignoreRestSiblings: true,
80
- fix: { imports: "safe-fix" },
81
- },
82
- ],
83
- "no-use-before-define": "off",
84
- complexity: ["error", { max: 12 }],
85
- "max-depth": ["error", 3],
86
- "max-params": ["error", 4],
87
- "max-lines": ["error", { max: 300, skipBlankLines: true, skipComments: true }],
88
- "max-lines-per-function": ["error", { max: 250, skipBlankLines: true, skipComments: true }],
89
- "eslint/no-await-in-loop": "error",
90
- "eslint/no-console": "off",
91
- "eslint/no-constant-binary-expression": "off",
92
- "eslint/no-control-regex": "error",
93
- "eslint/no-else-return": "error",
94
- "eslint/no-empty": "error",
95
- "eslint/no-empty-function": "error",
96
- "eslint/no-new": "off",
97
- "eslint/no-self-compare": "error",
98
- "eslint/no-shadow": "error",
99
- "eslint/no-template-curly-in-string": "error",
100
- "eslint/no-unassigned-vars": "off",
101
- "eslint/no-underscore-dangle": "off",
102
- "eslint/no-unmodified-loop-condition": "off",
103
- "eslint/no-unused-expressions": "off",
104
- "eslint/no-useless-concat": "off",
105
- "eslint/preserve-caught-error": "off",
106
- "eslint/prefer-arrow-callback": "error",
107
- "eslint/prefer-const": "error",
108
- "eslint/prefer-template": "error",
109
- "typescript/consistent-type-imports": "error",
110
- "typescript/dot-notation": "error",
111
- "typescript/no-confusing-void-expression": "error",
112
- "typescript/no-empty-object-type": ["error", { allowInterfaces: "with-single-extends" }],
113
- "typescript/no-explicit-any": "error",
114
- "typescript/no-extra-non-null-assertion": "error",
115
- "typescript/no-extraneous-class": "error",
116
- "typescript/no-floating-promises": "error",
117
- "typescript/no-import-type-side-effects": "error",
118
- "typescript/no-misused-new": "error",
119
- "typescript/no-misused-promises": ["error", { checksVoidReturn: false }],
120
- "typescript/no-namespace": "error",
121
- "typescript/no-non-null-asserted-optional-chain": "error",
122
- "typescript/no-non-null-assertion": "error",
123
- "typescript/no-unnecessary-boolean-literal-compare": "error",
124
- "typescript/no-unnecessary-condition": "error",
125
- "typescript/no-unnecessary-type-assertion": "error",
126
- "typescript/no-unnecessary-type-constraint": "error",
127
- "typescript/no-unsafe-declaration-merging": "error",
128
- "typescript/no-unsafe-function-type": "error",
129
- "typescript/no-unsafe-type-assertion": "error",
130
- "typescript/no-wrapper-object-types": "error",
131
- "typescript/prefer-as-const": "error",
132
- "typescript/prefer-namespace-keyword": "error",
133
- "typescript/prefer-nullish-coalescing": "error",
134
- "typescript/require-await": "error",
135
- "typescript/switch-exhaustiveness-check": ["error", { considerDefaultExhaustiveForUnions: true }],
136
- "import/no-commonjs": "off",
137
- "import/no-cycle": "error",
138
- "import/no-duplicates": "error",
139
- "import/no-named-as-default": "off",
140
- "import/no-named-as-default-member": "off",
141
- "import/no-self-import": "off",
142
- "import/no-unassigned-import": "off",
143
- "unicorn/consistent-function-scoping": "off",
144
- "unicorn/no-array-reverse": "off",
145
- "unicorn/no-array-sort": "off",
146
- "unicorn/no-empty-file": "off",
147
- "unicorn/no-new-array": "off",
148
- "unicorn/no-useless-fallback-in-spread": "off",
149
- "unicorn/no-useless-spread": "off",
150
- "unicorn/prefer-add-event-listener": "off",
151
- "unicorn/prefer-array-flat-map": "error",
152
- "unicorn/prefer-date-now": "error",
153
- "unicorn/prefer-logical-operator-over-ternary": "error",
154
- "unicorn/prefer-node-protocol": "error",
155
- "unicorn/prefer-optional-catch-binding": "error",
156
- "unicorn/require-module-specifiers": "off",
157
- "unicorn/throw-new-error": "error",
158
- "promise/always-return": "off",
159
- "promise/no-callback-in-promise": "off",
160
- "promise/no-multiple-resolved": "off",
161
- "promise/no-promise-in-callback": "off",
162
- "oxc/no-async-endpoint-handlers": "off",
163
- };
164
- /** Test code is a different kind of code: long tables of cases, and assertions that know more than the type does. */
165
- export const TEST_FILES = [
166
- "**/*.test.ts",
167
- "**/*.test.tsx",
168
- "**/*.spec.ts",
169
- "**/*.spec.tsx",
170
- "**/__tests__/**",
171
- "**/tests/**",
172
- ];
173
- /**
174
- * Base entry — strict TypeScript rules for any project (backend, CLI, library).
175
- * Library modules (zod) auto-enable when the consumer depends on the library;
176
- * pass an explicit boolean to force either way.
177
- *
178
- * Some of the enabled `typescript/` rules are type-aware (`no-floating-promises`,
179
- * `no-unnecessary-condition`, …): they only report when oxlint runs with
180
- * `--type-aware` and the `oxlint-tsgolint` peer installed.
181
- */
182
- const core = (options = {}) => {
183
- const composed = composeModules(coreModules, options);
184
- return {
185
- plugins: ["eslint", "typescript", "import", "unicorn", "promise", "oxc"],
186
- jsPlugins: composed.jsPlugins,
187
- env: { builtin: true, es2024: true },
188
- categories: {
189
- correctness: "error",
190
- suspicious: "error",
191
- perf: "error",
192
- pedantic: "off",
193
- style: "off",
194
- restriction: "off",
195
- nursery: "off",
196
- },
197
- rules: { ...BASE_RULES, ...composed.rules },
198
- overrides: [
199
- {
200
- files: ["**/*.tsx"],
201
- rules: { "func-style": "off" },
202
- },
203
- {
204
- files: ["**/*.d.ts"],
205
- rules: { "typescript/no-explicit-any": "off" },
206
- },
207
- {
208
- files: TEST_FILES,
209
- rules: {
210
- "max-lines": "off",
211
- "max-lines-per-function": "off",
212
- "typescript/await-thenable": "off",
213
- "typescript/no-non-null-assertion": "off",
214
- "typescript/no-unsafe-type-assertion": "off",
215
- "typescript/require-await": "off",
216
- "typescript/unbound-method": "off",
217
- },
218
- },
219
- ],
220
- };
221
- };
222
- export default core;
1
+ import{composeModules as o}from"../lib/module.js";import n from"./rules/core/index.js";import s from"./rules/zod/index.js";export const coreModules=[n,s];const t={"array-callback-return":"error","constructor-super":"error","default-param-last":"error",eqeqeq:["error","always",{null:"ignore"}],"for-direction":"error","getter-return":"error","func-style":["error","expression"],"no-array-constructor":"error","no-async-promise-executor":"error","no-case-declarations":"error","no-class-assign":"error","no-compare-neg-zero":"error","no-cond-assign":"error","no-const-assign":"error","no-constant-condition":"error","no-control-regex":"error","no-debugger":"error","no-dupe-class-members":"error","no-dupe-else-if":"error","no-dupe-keys":"error","no-duplicate-case":"error","no-empty":"error","no-empty-character-class":"error","no-empty-pattern":"error","no-ex-assign":"error","no-extra-boolean-cast":"error","no-fallthrough":"error","no-func-assign":"error","no-global-assign":"error","no-import-assign":"error","no-irregular-whitespace":"error","no-loss-of-precision":"error","no-misleading-character-class":"error","no-negated-condition":"error","no-nested-ternary":"error","no-new-native-nonconstructor":"error","no-nonoctal-decimal-escape":"error","no-obj-calls":"error","no-param-reassign":"error","no-prototype-builtins":"error","no-redeclare":"off","no-regex-spaces":"error","no-self-assign":"error","no-setter-return":"error","no-shadow-restricted-names":"error","no-sparse-arrays":"error","no-this-before-super":"error","no-undef":"off","no-unreachable":"error","no-unsafe-finally":"error","no-unsafe-negation":"error","no-unsafe-optional-chaining":"error","no-unused-labels":"error","no-unused-private-class-members":"error","no-useless-catch":"error","no-useless-constructor":"error","no-useless-escape":"error","no-var":"error","no-with":"error","object-shorthand":["error","methods"],"prefer-rest-params":"error","require-yield":"error","use-isnan":"error","valid-typeof":"error","no-unused-vars":["error",{args:"after-used",argsIgnorePattern:"^_",caughtErrors:"all",caughtErrorsIgnorePattern:"^_",varsIgnorePattern:"^_",destructuredArrayIgnorePattern:"^_",ignoreRestSiblings:!0,fix:{imports:"safe-fix"}}],"no-use-before-define":"off",complexity:["error",{max:12}],"max-depth":["error",3],"max-params":["error",4],"max-lines":["error",{max:300,skipBlankLines:!0,skipComments:!0}],"max-lines-per-function":["error",{max:250,skipBlankLines:!0,skipComments:!0}],"eslint/no-await-in-loop":"error","eslint/no-console":"off","eslint/no-constant-binary-expression":"off","eslint/no-control-regex":"error","eslint/no-else-return":"error","eslint/no-empty":"error","eslint/no-empty-function":"error","eslint/no-new":"off","eslint/no-self-compare":"error","eslint/no-shadow":"error","eslint/no-template-curly-in-string":"error","eslint/no-unassigned-vars":"off","eslint/no-underscore-dangle":"off","eslint/no-unmodified-loop-condition":"off","eslint/no-unused-expressions":"off","eslint/no-useless-concat":"off","eslint/preserve-caught-error":"off","eslint/prefer-arrow-callback":"error","eslint/prefer-const":"error","eslint/prefer-template":"error","typescript/consistent-type-imports":"error","typescript/dot-notation":"error","typescript/no-confusing-void-expression":"error","typescript/no-empty-object-type":["error",{allowInterfaces:"with-single-extends"}],"typescript/no-explicit-any":"error","typescript/no-extra-non-null-assertion":"error","typescript/no-extraneous-class":"error","typescript/no-floating-promises":"error","typescript/no-import-type-side-effects":"error","typescript/no-misused-new":"error","typescript/no-misused-promises":["error",{checksVoidReturn:!1}],"typescript/no-namespace":"error","typescript/no-non-null-asserted-optional-chain":"error","typescript/no-non-null-assertion":"error","typescript/no-unnecessary-boolean-literal-compare":"error","typescript/no-unnecessary-condition":"error","typescript/no-unnecessary-type-assertion":"error","typescript/no-unnecessary-type-constraint":"error","typescript/no-unsafe-declaration-merging":"error","typescript/no-unsafe-function-type":"error","typescript/no-unsafe-type-assertion":"error","typescript/no-wrapper-object-types":"error","typescript/prefer-as-const":"error","typescript/prefer-namespace-keyword":"error","typescript/prefer-nullish-coalescing":"error","typescript/require-await":"error","typescript/switch-exhaustiveness-check":["error",{considerDefaultExhaustiveForUnions:!0}],"import/no-commonjs":"off","import/no-cycle":"error","import/no-duplicates":"error","import/no-named-as-default":"off","import/no-named-as-default-member":"off","import/no-self-import":"off","import/no-unassigned-import":"off","unicorn/consistent-function-scoping":"off","unicorn/no-array-reverse":"off","unicorn/no-array-sort":"off","unicorn/no-empty-file":"off","unicorn/no-new-array":"off","unicorn/no-useless-fallback-in-spread":"off","unicorn/no-useless-spread":"off","unicorn/prefer-add-event-listener":"off","unicorn/prefer-array-flat-map":"error","unicorn/prefer-date-now":"error","unicorn/prefer-logical-operator-over-ternary":"error","unicorn/prefer-node-protocol":"error","unicorn/prefer-optional-catch-binding":"error","unicorn/require-module-specifiers":"off","unicorn/throw-new-error":"error","promise/always-return":"off","promise/no-callback-in-promise":"off","promise/no-multiple-resolved":"off","promise/no-promise-in-callback":"off","oxc/no-async-endpoint-handlers":"off"};export const TEST_FILES=["**/*.test.ts","**/*.test.tsx","**/*.spec.ts","**/*.spec.tsx","**/__tests__/**","**/tests/**"];const i=(e={})=>{const r=o(coreModules,e);return{plugins:["eslint","typescript","import","unicorn","promise","oxc"],jsPlugins:r.jsPlugins,env:{builtin:!0,es2024:!0},categories:{correctness:"error",suspicious:"error",perf:"error",pedantic:"off",style:"off",restriction:"off",nursery:"off"},rules:{...t,...r.rules},overrides:[{files:["**/*.tsx"],rules:{"func-style":"off"}},{files:["**/*.d.ts"],rules:{"typescript/no-explicit-any":"off"}},{files:TEST_FILES,rules:{"max-lines":"off","max-lines-per-function":"off","typescript/await-thenable":"off","typescript/no-non-null-assertion":"off","typescript/no-unsafe-type-assertion":"off","typescript/require-await":"off","typescript/unbound-method":"off"}}]}};export default i;
@@ -1,37 +1 @@
1
- import { optionsOf } from "../../../lib/ast.js";
2
- const COMPONENTS_TSX_ONLY = "Move this file to `src/utils` (helpers, pure logic), `src/hooks` (a hook), or `src/api/<feature>/` (data access). `components/` holds only files that render JSX, plus a barrel.";
3
- const DEFAULT_DIR = "src/components";
4
- const isBarrel = (body) => body.every(statement => statement.type === "ImportDeclaration" ||
5
- statement.type === "ExportAllDeclaration" ||
6
- (statement.type === "ExportNamedDeclaration" && statement.declaration == null));
7
- export const componentsTsxOnly = {
8
- meta: {
9
- type: "problem",
10
- docs: {
11
- description: "Require every file under the components directory to render JSX or be a re-export barrel. `dir` says which directory, defaulting to `src/components`.",
12
- },
13
- schema: [{ type: "object", properties: { dir: { type: "string", minLength: 1 } }, additionalProperties: false }],
14
- defaultOff: true,
15
- },
16
- createOnce(context) {
17
- let rendersJsx = false;
18
- return {
19
- before() {
20
- rendersJsx = false;
21
- const { dir = DEFAULT_DIR } = optionsOf(context, {});
22
- return context.filename.replaceAll("\\", "/").includes(`/${dir}/`);
23
- },
24
- JSXElement() {
25
- rendersJsx = true;
26
- },
27
- JSXFragment() {
28
- rendersJsx = true;
29
- },
30
- "Program:exit"(node) {
31
- if (rendersJsx || isBarrel(node.body))
32
- return;
33
- context.report({ node, message: COMPONENTS_TSX_ONLY });
34
- },
35
- };
36
- },
37
- };
1
+ import{optionsOf as t}from"../../../lib/ast.js";const s="Move this file to `src/utils` (helpers, pure logic), `src/hooks` (a hook), or `src/api/<feature>/` (data access). `components/` holds only files that render JSX, plus a barrel.",l="src/components",n=(r)=>r.every((e)=>e.type==="ImportDeclaration"||e.type==="ExportAllDeclaration"||e.type==="ExportNamedDeclaration"&&e.declaration==null);export const componentsTsxOnly={meta:{type:"problem",docs:{description:"Require every file under the components directory to render JSX or be a re-export barrel. `dir` says which directory, defaulting to `src/components`."},schema:[{type:"object",properties:{dir:{type:"string",minLength:1}},additionalProperties:!1}],defaultOff:!0},createOnce(r){let e=!1;return{before(){e=!1;const{dir:o=l}=t(r,{});return r.filename.replaceAll("\\","/").includes(`/${o}/`)},JSXElement(){e=!0},JSXFragment(){e=!0},"Program:exit"(o){if(e||n(o.body))return;r.report({node:o,message:s})}}}};
@@ -1,32 +1 @@
1
- import { ancestors, calleeName, hasAncestor, isFunction, problem, subtreeHas } from "../../../lib/ast.js";
2
- const HOIST_INTL = "Move this `Intl` formatter to module scope when the locale and options are static, or wrap it in `useMemo` keyed on the locale — constructing one per render is expensive.";
3
- const MEMO_HOOKS = new Set(["useMemo", "useCallback"]);
4
- const rendersJsx = (node) => subtreeHas(node, current => current.type === "JSXElement" || current.type === "JSXFragment");
5
- /** Any enclosing function that renders JSX, not just the innermost one: a callback of a component is rebuilt too. */
6
- const insideAComponent = (node) => {
7
- for (const current of ancestors(node)) {
8
- if (isFunction(current) && rendersJsx(current))
9
- return true;
10
- }
11
- return false;
12
- };
13
- export const hoistIntl = problem("Disallow `new Intl.*` inside a function that renders JSX, unless the call already sits in `useMemo` or `useCallback`.", {
14
- createOnce(context) {
15
- return {
16
- NewExpression(node) {
17
- const { callee } = node;
18
- if (callee.type !== "MemberExpression")
19
- return;
20
- const { object } = callee;
21
- if (object.type !== "Identifier" || object.name !== "Intl")
22
- return;
23
- if (hasAncestor(node, current => current.type === "CallExpression" && MEMO_HOOKS.has(calleeName(current)))) {
24
- return;
25
- }
26
- if (!insideAComponent(node))
27
- return;
28
- context.report({ node, message: HOIST_INTL });
29
- },
30
- };
31
- },
32
- });
1
+ import{ancestors as o,calleeName as a,hasAncestor as i,isFunction as l,problem as c,subtreeHas as p}from"../../../lib/ast.js";const u="Move this `Intl` formatter to module scope when the locale and options are static, or wrap it in `useMemo` keyed on the locale — constructing one per render is expensive.",m=new Set(["useMemo","useCallback"]),f=(t)=>p(t,(e)=>e.type==="JSXElement"||e.type==="JSXFragment"),h=(t)=>{for(const e of o(t))if(l(e)&&f(e))return!0;return!1};export const hoistIntl=c("Disallow `new Intl.*` inside a function that renders JSX, unless the call already sits in `useMemo` or `useCallback`.",{createOnce(t){return{NewExpression(e){const{callee:n}=e;if(n.type!=="MemberExpression")return;const{object:s}=n;if(s.type!=="Identifier"||s.name!=="Intl")return;if(i(e,(r)=>r.type==="CallExpression"&&m.has(a(r))))return;if(!h(e))return;t.report({node:e,message:u})}}}});
@@ -1,18 +1 @@
1
- import { defineModule } from "../../../lib/module.js";
2
- import { componentsTsxOnly } from "./components-tsx-only.js";
3
- import { hoistIntl } from "./hoist-intl.js";
4
- import { noComments } from "./no-comments.js";
5
- import { noNamingConvention } from "./no-naming-convention.js";
6
- import { useDesignSystem } from "./use-design-system.js";
7
- export default defineModule({
8
- meta: { name: "@ashstack/core" },
9
- url: import.meta.url,
10
- docsWhen: "always on via `core()` and every entry above it (opt-in rules noted per rule)",
11
- rules: {
12
- "no-comments": noComments,
13
- "no-naming-convention": noNamingConvention,
14
- "use-design-system": useDesignSystem,
15
- "components-tsx-only": componentsTsxOnly,
16
- "hoist-intl": hoistIntl,
17
- },
18
- });
1
+ import{defineModule as o}from"../../../lib/module.js";import{componentsTsxOnly as n}from"./components-tsx-only.js";import{hoistIntl as e}from"./hoist-intl.js";import{noComments as t}from"./no-comments.js";import{noNamingConvention as m}from"./no-naming-convention.js";import{useDesignSystem as r}from"./use-design-system.js";export default o({meta:{name:"@ashstack/core"},url:import.meta.url,docsWhen:"always on via `core()` and every entry above it (opt-in rules noted per rule)",rules:{"no-comments":t,"no-naming-convention":m,"use-design-system":r,"components-tsx-only":n,"hoist-intl":e}});
@@ -1,194 +1 @@
1
- import { optionsOf } from "../../../lib/ast.js";
2
- const COMMENT_DIRECTIVES = [
3
- "@ts-expect-error",
4
- "@ts-ignore",
5
- "@ts-nocheck",
6
- "@ts-check",
7
- "@jsx",
8
- "@jsxRuntime",
9
- "@jsxImportSource",
10
- "prettier-ignore",
11
- "eslint-disable",
12
- "eslint-enable",
13
- "oxlint-disable",
14
- "oxlint-enable",
15
- "oxfmt-ignore",
16
- "react-doctor-disable",
17
- "@type",
18
- "<reference",
19
- "#__PURE__",
20
- "@__PURE__",
21
- "v8 ignore",
22
- "c8 ignore",
23
- "istanbul ignore",
24
- "@vitest-environment",
25
- "EXPECT_PASS",
26
- "EXPECT_FAIL",
27
- ];
28
- const ESCAPE_HATCH = /^what:\s*(?<fact>[\s\S]+)$/i;
29
- const BLOCK_COMMENT_TYPES = new Set(["Block", "MultiLine"]);
30
- const IGNORED_COMMENT_TYPES = new Set(["Shebang", "Hashbang"]);
31
- const HATCH_MIN_FACT = 10;
32
- const HATCH_MAX_LENGTH = 120;
33
- const HATCH_DEFAULT_BUDGET = 2;
34
- const REFACTOR_MOVES = "Make the code say this, then delete the line — nearly every comment here is one rename or one refactor away from unnecessary. Reach for the move that fits: Rename the value, function or type to the plain word this sentence uses; Extract Function for the block it summarises, named after the claim it makes; Guard Clause for the nesting it walks the reader through; split the clever expression it decodes into named steps. You are done when the sentence's content is legible in an identifier, a signature or the control flow.";
35
- const HATCH_OPEN = " The `// what:` hatch carries one kind of fact: one that outlives this code — a platform bug, an upstream contract, a number measured on a device. A fact about what this code does fails that test, and the refactor is still owed.";
36
- const HATCH_CLOSED = " This project runs with the `// what:` hatch turned off, so the refactor is the only way out: there is no fact this file may state in prose.";
37
- const refactorFirst = (escapeHatch) => REFACTOR_MOVES + (escapeHatch ? HATCH_OPEN : HATCH_CLOSED);
38
- const FLOATING_JSDOC = "Attach this block to the declaration it documents, or Rename and Extract Function until the code reads without it. A `/** */` block earns its place as documentation of the symbol directly beneath it.";
39
- const HATCH_MESSAGES = {
40
- block: "Rewrite this as a single `// what: <fact>` line comment. If the fact needs a paragraph, name the pieces in code until it fits on one line.",
41
- shortFact: `Write the fact after \`what:\` (at least ${HATCH_MIN_FACT} characters), or delete the comment.`,
42
- tooLong: `Trim this \`what:\` line under ${HATCH_MAX_LENGTH} characters, moving what is left into names in the code.`,
43
- stacked: "Keep one `what:` line here — the single irreducible fact — and refactor what the others explain into named values and functions.",
44
- };
45
- const overBudget = (budget, count) => `Delete \`what:\` comments from this file until at most ${budget} remain (it has ${count}): move the logic each one annotates into a function whose name carries what the comment says.`;
46
- const DECLARATION_TYPES = new Set([
47
- "ExportNamedDeclaration",
48
- "ExportDefaultDeclaration",
49
- "ExportAllDeclaration",
50
- "FunctionDeclaration",
51
- "VariableDeclaration",
52
- "ClassDeclaration",
53
- "MethodDefinition",
54
- "PropertyDefinition",
55
- "Property",
56
- "TSInterfaceDeclaration",
57
- "TSTypeAliasDeclaration",
58
- "TSEnumDeclaration",
59
- "TSEnumMember",
60
- "TSModuleDeclaration",
61
- "TSDeclareFunction",
62
- "TSPropertySignature",
63
- "TSMethodSignature",
64
- "TSIndexSignature",
65
- "TSCallSignatureDeclaration",
66
- "TSConstructSignatureDeclaration",
67
- "TSAbstractMethodDefinition",
68
- "TSAbstractPropertyDefinition",
69
- "AccessorProperty",
70
- "ImportDeclaration",
71
- ]);
72
- const commentBody = (comment) => (BLOCK_COMMENT_TYPES.has(comment.type) ? comment.value.replace(/^[*\s]+/, "") : comment.value).trim();
73
- const isDirective = (body) => COMMENT_DIRECTIVES.some(prefix => body.startsWith(prefix));
74
- const isJsdoc = (comment) => BLOCK_COMMENT_TYPES.has(comment.type) && comment.value.startsWith("*");
75
- /** Comments this rule judges: shebangs and tooling directives are none of its business. */
76
- const reviewedComments = (context) => {
77
- const reviewed = [];
78
- for (const comment of context.sourceCode.getAllComments()) {
79
- if (IGNORED_COMMENT_TYPES.has(comment.type))
80
- continue;
81
- const body = commentBody(comment);
82
- if (isDirective(body))
83
- continue;
84
- reviewed.push({ comment, body });
85
- }
86
- return reviewed;
87
- };
88
- /** offset of the first non-whitespace character at or after `from` */
89
- const nextTokenStart = (text, from) => {
90
- let index = from;
91
- while (/\s/.test(text.charAt(index)))
92
- index += 1;
93
- return index;
94
- };
95
- const shapeViolation = (comment, body, fact) => {
96
- if (BLOCK_COMMENT_TYPES.has(comment.type))
97
- return HATCH_MESSAGES.block;
98
- if (fact.trim().length < HATCH_MIN_FACT)
99
- return HATCH_MESSAGES.shortFact;
100
- if (body.length > HATCH_MAX_LENGTH)
101
- return HATCH_MESSAGES.tooLong;
102
- return null;
103
- };
104
- const isStackedOn = (source, previous, comment) => source.slice(previous.end, comment.start).trim() === "";
105
- const hatchAllowed = (options) => options.escapeHatch !== false;
106
- export const noComments = {
107
- meta: {
108
- type: "problem",
109
- docs: {
110
- description: 'Disallow every comment that is neither a `// what: <fact>` line nor a tooling directive. The message names the refactoring that removes it: Rename, Extract Function, Guard Clause. Surviving `// what:` lines are held to one short line each, at most `budget` per file (default 2); `escapeHatch: false` removes the hatch entirely, so no prose survives at all. With `jsdoc: "allow"`, a `/** */` block documenting the declaration directly beneath it is kept, while a floating one still reports.',
111
- },
112
- schema: [
113
- {
114
- type: "object",
115
- properties: {
116
- jsdoc: { enum: ["allow", "report"] },
117
- escapeHatch: { type: "boolean" },
118
- budget: { type: "integer", minimum: 0 },
119
- },
120
- additionalProperties: false,
121
- },
122
- ],
123
- defaultOff: true,
124
- },
125
- createOnce(context) {
126
- const declarationStarts = new Set();
127
- const recordDeclaration = (node) => {
128
- declarationStarts.add(node.start);
129
- };
130
- const declarationVisitors = {};
131
- for (const type of DECLARATION_TYPES)
132
- declarationVisitors[type] = recordDeclaration;
133
- const report = (comment, message) => {
134
- context.report({ node: comment, message });
135
- };
136
- const reportProse = (reviewed, options) => {
137
- const { text } = context.sourceCode;
138
- const escapeHatch = hatchAllowed(options);
139
- for (const { comment, body } of reviewed) {
140
- if (escapeHatch && ESCAPE_HATCH.test(body))
141
- continue;
142
- if (options.jsdoc !== "allow" || !isJsdoc(comment))
143
- report(comment, refactorFirst(escapeHatch));
144
- else if (!declarationStarts.has(nextTokenStart(text, comment.end)))
145
- report(comment, FLOATING_JSDOC);
146
- }
147
- };
148
- const wellShapedHatches = (reviewed) => {
149
- const accepted = [];
150
- for (const { comment, body } of reviewed) {
151
- const fact = body.match(ESCAPE_HATCH)?.groups?.fact;
152
- if (fact === undefined)
153
- continue;
154
- const violation = shapeViolation(comment, body, fact);
155
- if (violation === null)
156
- accepted.push(comment);
157
- else
158
- report(comment, violation);
159
- }
160
- return accepted;
161
- };
162
- const reportStacked = (accepted) => {
163
- const source = context.sourceCode.getText();
164
- for (const [index, comment] of accepted.entries()) {
165
- const previous = accepted[index - 1];
166
- if (previous && isStackedOn(source, previous, comment))
167
- report(comment, HATCH_MESSAGES.stacked);
168
- }
169
- };
170
- const reportOverBudget = (accepted, options) => {
171
- const budget = options.budget ?? HATCH_DEFAULT_BUDGET;
172
- const overflow = accepted[budget];
173
- if (overflow)
174
- report(overflow, overBudget(budget, accepted.length));
175
- };
176
- return {
177
- ...declarationVisitors,
178
- before() {
179
- declarationStarts.clear();
180
- return true;
181
- },
182
- "Program:exit"() {
183
- const options = optionsOf(context, {});
184
- const reviewed = reviewedComments(context);
185
- reportProse(reviewed, options);
186
- if (!hatchAllowed(options))
187
- return;
188
- const accepted = wellShapedHatches(reviewed);
189
- reportStacked(accepted);
190
- reportOverBudget(accepted, options);
191
- },
192
- };
193
- },
194
- };
1
+ import{optionsOf as y}from"../../../lib/ast.js";const E=["@ts-expect-error","@ts-ignore","@ts-nocheck","@ts-check","@jsx","@jsxRuntime","@jsxImportSource","prettier-ignore","eslint-disable","eslint-enable","oxlint-disable","oxlint-enable","oxfmt-ignore","react-doctor-disable","@type","<reference","#__PURE__","@__PURE__","v8 ignore","c8 ignore","istanbul ignore","@vitest-environment","EXPECT_PASS","EXPECT_FAIL"],f=/^what:\s*(?<fact>[\s\S]+)$/i,u=new Set(["Block","MultiLine"]),v=new Set(["Shebang","Hashbang"]),m=10,p=120,C=2,D="Make the code say this, then delete the line — nearly every comment here is one rename or one refactor away from unnecessary. Reach for the move that fits: Rename the value, function or type to the plain word this sentence uses; Extract Function for the block it summarises, named after the claim it makes; Guard Clause for the nesting it walks the reader through; split the clever expression it decodes into named steps. You are done when the sentence's content is legible in an identifier, a signature or the control flow.",A=" The `// what:` hatch carries one kind of fact: one that outlives this code — a platform bug, an upstream contract, a number measured on a device. A fact about what this code does fails that test, and the refactor is still owed.",_=" This project runs with the `// what:` hatch turned off, so the refactor is the only way out: there is no fact this file may state in prose.",H=(e)=>D+(e?A:_),k="Attach this block to the declaration it documents, or Rename and Extract Function until the code reads without it. A `/** */` block earns its place as documentation of the symbol directly beneath it.",d={block:"Rewrite this as a single `// what: <fact>` line comment. If the fact needs a paragraph, name the pieces in code until it fits on one line.",shortFact:`Write the fact after \`what:\` (at least ${m} characters), or delete the comment.`,tooLong:`Trim this \`what:\` line under ${p} characters, moving what is left into names in the code.`,stacked:"Keep one `what:` line here — the single irreducible fact — and refactor what the others explain into named values and functions."},x=(e,o)=>`Delete \`what:\` comments from this file until at most ${e} remain (it has ${o}): move the logic each one annotates into a function whose name carries what the comment says.`,P=new Set(["ExportNamedDeclaration","ExportDefaultDeclaration","ExportAllDeclaration","FunctionDeclaration","VariableDeclaration","ClassDeclaration","MethodDefinition","PropertyDefinition","Property","TSInterfaceDeclaration","TSTypeAliasDeclaration","TSEnumDeclaration","TSEnumMember","TSModuleDeclaration","TSDeclareFunction","TSPropertySignature","TSMethodSignature","TSIndexSignature","TSCallSignatureDeclaration","TSConstructSignatureDeclaration","TSAbstractMethodDefinition","TSAbstractPropertyDefinition","AccessorProperty","ImportDeclaration"]),M=(e)=>(u.has(e.type)?e.value.replace(/^[*\s]+/,""):e.value).trim(),O=(e)=>E.some((o)=>e.startsWith(o)),F=(e)=>u.has(e.type)&&e.value.startsWith("*"),R=(e)=>{const o=[];for(const a of e.sourceCode.getAllComments()){if(v.has(a.type))continue;const c=M(a);if(O(c))continue;o.push({comment:a,body:c})}return o},I=(e,o)=>{let a=o;while(/\s/.test(e.charAt(a)))a+=1;return a},L=(e,o,a)=>{if(u.has(e.type))return d.block;if(a.trim().length<m)return d.shortFact;if(o.length>p)return d.tooLong;return null},N=(e,o,a)=>e.slice(o.end,a.start).trim()==="",g=(e)=>e.escapeHatch!==!1;export const noComments={meta:{type:"problem",docs:{description:'Disallow every comment that is neither a `// what: <fact>` line nor a tooling directive. The message names the refactoring that removes it: Rename, Extract Function, Guard Clause. Surviving `// what:` lines are held to one short line each, at most `budget` per file (default 2); `escapeHatch: false` removes the hatch entirely, so no prose survives at all. With `jsdoc: "allow"`, a `/** */` block documenting the declaration directly beneath it is kept, while a floating one still reports.'},schema:[{type:"object",properties:{jsdoc:{enum:["allow","report"]},escapeHatch:{type:"boolean"},budget:{type:"integer",minimum:0}},additionalProperties:!1}],defaultOff:!0},createOnce(e){const o=new Set,a=(t)=>{o.add(t.start)},c={};for(const t of P)c[t]=a;const l=(t,n)=>{e.report({node:t,message:n})},S=(t,n)=>{const{text:r}=e.sourceCode,s=g(n);for(const{comment:i,body:h}of t){if(s&&f.test(h))continue;if(n.jsdoc!=="allow"||!F(i))l(i,H(s));else if(!o.has(I(r,i.end)))l(i,k)}},T=(t)=>{const n=[];for(const{comment:r,body:s}of t){const i=s.match(f)?.groups?.fact;if(i===void 0)continue;const h=L(r,s,i);if(h===null)n.push(r);else l(r,h)}return n},w=(t)=>{const n=e.sourceCode.getText();for(const[r,s]of t.entries()){const i=t[r-1];if(i&&N(n,i,s))l(s,d.stacked)}},b=(t,n)=>{const r=n.budget??C,s=t[r];if(s)l(s,x(r,t.length))};return{...c,before(){o.clear();return!0},"Program:exit"(){const t=y(e,{}),n=R(e);S(n,t);if(!g(t))return;const r=T(n);w(r);b(r,t)}}}};