@hypersolid-dev/eslint-config 3.0.0 → 5.0.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 (57) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +37 -6
  3. package/dist/base.js +52 -7
  4. package/dist/plugins/hypersolid.d.ts +3 -0
  5. package/dist/plugins/hypersolid.js +38 -0
  6. package/dist/plugins/props-destructuring.js +2 -1
  7. package/dist/plugins/react-native-a11y/index.js +4 -2
  8. package/dist/plugins/react-native-a11y/rules/accessibility-hint.js +4 -2
  9. package/dist/plugins/react-native-a11y/rules/accessibility-label.js +8 -4
  10. package/dist/plugins/react-native-a11y/rules/accessibility-role.js +4 -2
  11. package/dist/plugins/react-native-a11y/rules/gate-announce-for-accessibility.js +10 -6
  12. package/dist/plugins/react-native-a11y/rules/input-purpose.js +8 -4
  13. package/dist/plugins/react-native-a11y/rules/no-unnamed-interactive.js +4 -2
  14. package/dist/plugins/react-native-a11y/rules/require-invert-colors-opt-out.js +6 -4
  15. package/dist/plugins/react-native-a11y/rules/require-modal-a11y-on-sheets.js +5 -3
  16. package/dist/plugins/react-native-a11y/utils/get-amount-of-text-children.js +8 -4
  17. package/dist/plugins/react-native-a11y/utils/get-element-name.js +1 -1
  18. package/dist/plugins/rules/boolean-guard.d.ts +30 -0
  19. package/dist/plugins/rules/boolean-guard.js +143 -0
  20. package/dist/plugins/rules/comment-style.d.ts +26 -0
  21. package/dist/plugins/rules/comment-style.js +158 -0
  22. package/dist/plugins/rules/effect-takes-callback.d.ts +20 -0
  23. package/dist/plugins/rules/effect-takes-callback.js +106 -0
  24. package/dist/plugins/rules/jsdoc-for-props.d.ts +18 -0
  25. package/dist/plugins/rules/jsdoc-for-props.js +57 -0
  26. package/dist/plugins/rules/no-callback-payload.d.ts +21 -0
  27. package/dist/plugins/rules/no-callback-payload.js +214 -0
  28. package/dist/plugins/rules/no-markup-variable.d.ts +22 -0
  29. package/dist/plugins/rules/no-markup-variable.js +117 -0
  30. package/dist/plugins/rules/no-unnecessary-use-callback.d.ts +24 -0
  31. package/dist/plugins/rules/no-unnecessary-use-callback.js +117 -0
  32. package/dist/plugins/rules/regex-pattern-const.d.ts +19 -0
  33. package/dist/plugins/rules/regex-pattern-const.js +82 -0
  34. package/dist/plugins/rules/test-id-default.d.ts +4 -0
  35. package/dist/plugins/rules/test-id-default.js +186 -0
  36. package/dist/plugins/rules/test-id-format.d.ts +4 -0
  37. package/dist/plugins/rules/test-id-format.js +122 -0
  38. package/dist/plugins/rules/test-id-from-props.d.ts +4 -0
  39. package/dist/plugins/rules/test-id-from-props.js +94 -0
  40. package/dist/plugins/rules/undefined-check.d.ts +5 -0
  41. package/dist/plugins/rules/undefined-check.js +64 -0
  42. package/dist/plugins/rules/webview-allow-list.d.ts +5 -0
  43. package/dist/plugins/rules/webview-allow-list.js +109 -0
  44. package/dist/plugins/utils/comments.d.ts +15 -0
  45. package/dist/plugins/utils/comments.js +15 -0
  46. package/dist/plugins/utils/component-name.d.ts +11 -0
  47. package/dist/plugins/utils/component-name.js +32 -0
  48. package/dist/plugins/utils/create-rule.d.ts +5 -0
  49. package/dist/plugins/utils/create-rule.js +4 -0
  50. package/dist/plugins/utils/expressions.d.ts +52 -0
  51. package/dist/plugins/utils/expressions.js +274 -0
  52. package/dist/plugins/utils/types.d.ts +20 -0
  53. package/dist/plugins/utils/types.js +89 -0
  54. package/dist/react-native.js +95 -11
  55. package/dist/shared.d.ts +19 -0
  56. package/dist/shared.js +72 -1
  57. package/package.json +3 -2
package/dist/shared.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import eslintReact from '@eslint-react/eslint-plugin';
2
2
  import reactHooks from 'eslint-plugin-react-hooks';
3
3
  import unicorn from 'eslint-plugin-unicorn';
4
- import hypersolid from './plugins/props-destructuring.js';
4
+ import hypersolid from './plugins/hypersolid.js';
5
5
  export const CODE_FILES = ['**/*.{js,mjs,cjs,jsx,ts,mts,cts,tsx}'];
6
6
  // Tests, stories, mocks and test setup: each preset relaxes a few rules here that test doubles break by design.
7
7
  export const TEST_FILES = [
@@ -18,6 +18,39 @@ export const NUMBER_GLOBAL_RESTRICTIONS = ['isFinite', 'isNaN'].map((name) => ({
18
18
  name,
19
19
  message: `Use Number.${name}; the global coerces its argument.`,
20
20
  }));
21
+ /** `no-restricted-syntax` entries per preset; a later config replaces the options, so each preset spreads the ones before it. */
22
+ export const BASE_RESTRICTED_SYNTAX = [
23
+ {
24
+ selector: "CallExpression[callee.property.name='catch'] > :matches(ArrowFunctionExpression, FunctionExpression).arguments:matches([body.type='Identifier'][body.name='undefined'], [body.type='Literal'][body.value=null], [body.type='BlockStatement'][body.body.length=0], [body.type='UnaryExpression'][body.operator='void'])",
25
+ message: 'An empty catch hides the failure. Log it, recover from it, or drop the catch when the promise cannot reject.',
26
+ },
27
+ {
28
+ selector: ":matches(ExportSpecifier[local.name='default'], ImportSpecifier[imported.name='default'])",
29
+ message: 'Import a default as a default (`import name from "…"`), then `export { name };` if it has to be re-exported.',
30
+ },
31
+ ];
32
+ export const REACT_RESTRICTED_SYNTAX = [
33
+ ...BASE_RESTRICTED_SYNTAX,
34
+ {
35
+ selector: 'ExpressionStatement > Literal[value=/^use no (memo|forget)$/]',
36
+ message: 'Leave this component in the React Compiler; opting a subtree out is a performance decision that needs a reviewed reason.',
37
+ },
38
+ {
39
+ selector: ":matches(JSXExpressionContainer, JSXSpreadAttribute) CallExpression[callee.type='Identifier'][callee.name=/^use([A-Z0-9]|$)/]:not(Property[key.name=/^[A-Z]/] > ArrowFunctionExpression CallExpression)",
40
+ message: 'Call this hook at the top of the component and pass its result: `const fade = useAnimatedStyle(…)`, then `style={fade}`.',
41
+ },
42
+ {
43
+ selector: "MemberExpression[object.name='React'][property.name=/^use/]",
44
+ message: "Import React hooks from 'react' (`import { useEffect } from 'react'`) instead of `React.use*`.",
45
+ },
46
+ ];
47
+ /** `@typescript-eslint/naming-convention` entries per preset, for the same reason. Destructured names come from elsewhere (`const { user_id } = payload`). */
48
+ export const NAMING_CONVENTION = [
49
+ { selector: 'variable', modifiers: ['destructured'], format: null },
50
+ { selector: 'variable', format: ['camelCase', 'PascalCase', 'UPPER_CASE'] },
51
+ { selector: 'function', format: ['camelCase', 'PascalCase'] },
52
+ { selector: 'typeLike', format: ['PascalCase'] },
53
+ ];
21
54
  // Every enabled rule is an error: a warning is either worth fixing or not worth reporting.
22
55
  export function raiseWarnings(configs) {
23
56
  return configs.map((config) => {
@@ -85,12 +118,50 @@ export function reactConfigs() {
85
118
  '@hypersolid-dev/props-destructuring': 'error',
86
119
  // A boolean reads as a claim; hook and mock names are exempt. Bad: `const visible = true` (use `isVisible`).
87
120
  'unicorn/consistent-boolean-name': ['error', REACT_BOOLEAN_NAME_OPTIONS],
121
+ // Base's empty catch and default specifiers, plus compiler opt-outs, hooks inside JSX and `React.useX`. Bad: `style={useAnimatedStyle(…)}`, `React.useState(0)`.
122
+ 'no-restricted-syntax': ['error', ...REACT_RESTRICTED_SYNTAX],
123
+ // Base's naming, plus: a boolean prop is a state, not a command. Bad: `type Props = { showBadge: boolean }` (use `hasBadge`).
124
+ '@typescript-eslint/naming-convention': [
125
+ 'error',
126
+ ...NAMING_CONVENTION,
127
+ { selector: 'typeProperty', types: ['boolean'], format: null, custom: { regex: '^(show|display|hide)[A-Z]', match: false } },
128
+ ],
129
+ // A fragment around one child renders nothing extra. Bad: `<><Row /></>`.
130
+ '@eslint-react/jsx-no-useless-fragment': 'error',
131
+ // A literal default prop is a new object each render and breaks memoization. Bad: `({ items = [] }: Props)`.
132
+ '@eslint-react/no-unstable-default-props': 'error',
133
+ // State synced from props in an effect renders one frame stale. Bad: `useEffect(() => setFullName(first + last), [first, last])`.
134
+ 'react-hooks/no-deriving-state-in-effects': 'error',
135
+ // Off: memo-dependencies and exhaustive-effect-dependencies check the same arrays with the compiler's analysis, so one missing dependency would be reported twice.
136
+ 'react-hooks/exhaustive-deps': 'off',
137
+ // The compiler's dependency check for memos: missing and unused deps. Bad: `useMemo(() => a + b, [a])`.
138
+ 'react-hooks/memo-dependencies': 'error',
139
+ // The same check for effects; plugin 7.1.1 ships it inert, so `environment` turns it on. Bad: `useEffect(() => log(id), [])`.
140
+ 'react-hooks/exhaustive-effect-dependencies': ['error', { environment: { validateExhaustiveEffectDependencies: 'all' } }],
141
+ // `{count && <Badge />}` renders `0`, which crashes React Native outside `<Text>`. Bad: `{items.length && <List />}`.
142
+ '@hypersolid-dev/boolean-guard': 'error',
143
+ // Styled JSX built in a component body is a component without a name. Bad: `const header = <View style={s.header}>…</View>`.
144
+ '@hypersolid-dev/no-markup-variable': 'error',
145
+ // A `use*Effect` hook takes its callback first, like `useEffect`. Bad: `useFocusEffect(deps, () => …)`.
146
+ '@hypersolid-dev/effect-takes-callback': 'error',
147
+ // A `useCallback` with empty deps that captures nothing is a module-level function. Bad: `useCallback(() => track('open'), [])`.
148
+ '@hypersolid-dev/no-unnecessary-use-callback': 'error',
149
+ // A component forwards the event; the caller builds the payload. Bad: `onPress={() => onSelect(item.id)}` inside `Row`.
150
+ '@hypersolid-dev/no-callback-payload': 'error',
151
+ // A comment on a type member is JSDoc, the form editors show. Bad: `// The title\ntitle: string` in a props type.
152
+ '@hypersolid-dev/jsdoc-for-props': 'error',
88
153
  },
89
154
  },
90
155
  {
91
156
  name: '@hypersolid-dev/react/tests',
92
157
  files: TEST_FILES,
93
158
  rules: {
159
+ // Off: tests build markup and callbacks inline by design. Bad: `const tree = <Row />` in a test reported.
160
+ '@hypersolid-dev/boolean-guard': 'off',
161
+ '@hypersolid-dev/no-markup-variable': 'off',
162
+ '@hypersolid-dev/effect-takes-callback': 'off',
163
+ '@hypersolid-dev/no-unnecessary-use-callback': 'off',
164
+ '@hypersolid-dev/no-callback-payload': 'off',
94
165
  // Off: a mocked hook keeps the `use` name of the hook it replaces. Bad: `const useUser = () => fakeUser` in a mock.
95
166
  '@eslint-react/no-unnecessary-use-prefix': 'off',
96
167
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypersolid-dev/eslint-config",
3
- "version": "3.0.0",
3
+ "version": "5.0.0",
4
4
  "description": "Shared ESLint flat config for Hypersolid TypeScript projects",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -73,10 +73,11 @@
73
73
  "@typescript-eslint/types": "^8.72.1",
74
74
  "@typescript-eslint/utils": "^8.72.1",
75
75
  "eslint-import-resolver-typescript": "^4.4.5",
76
+ "eslint-plugin-expo": "^1.2.1",
76
77
  "eslint-plugin-import-x": "^4.17.1",
77
78
  "eslint-plugin-jsx-a11y": "^6.10.2",
78
79
  "eslint-plugin-no-secrets": "^2.3.3",
79
- "eslint-plugin-react-hooks": "^7.1.1",
80
+ "eslint-plugin-react-hooks": "7.1.1",
80
81
  "eslint-plugin-unicorn": "^79.0.0",
81
82
  "globals": "^17.13.0",
82
83
  "typescript-eslint": "^8.72.1"