@tuomashatakka/eslint-config 2.6.2 → 3.1.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 (45) hide show
  1. package/.github/workflows/ci.yml +23 -0
  2. package/.github/workflows/publish.yml +45 -0
  3. package/AGENTS.md +29 -0
  4. package/bun.lock +60 -102
  5. package/eslint.config.mjs +1 -0
  6. package/index.mjs +7 -21
  7. package/package.json +11 -19
  8. package/plugins/no-inline-types/index.mjs +11 -0
  9. package/plugins/no-inline-types/rules/no-inline-multiline-types.mjs +181 -0
  10. package/plugins/omit/index.mjs +8 -0
  11. package/plugins/omit/rules/omit-unnecessary-parens-brackets.mjs +329 -0
  12. package/plugins/omit/utils.mjs +91 -0
  13. package/plugins/react-strict/index.mjs +19 -0
  14. package/plugins/react-strict/rules/jsx-prop-layout.mjs +100 -0
  15. package/plugins/react-strict/rules/no-complex-jsx-map.mjs +66 -0
  16. package/plugins/react-strict/rules/no-jsx-value-calculations.mjs +99 -0
  17. package/plugins/react-strict/rules/no-nested-divs.mjs +59 -0
  18. package/plugins/react-strict/rules/no-style-prop.mjs +43 -0
  19. package/plugins/react-strict/rules/prefer-no-use-effect.mjs +26 -0
  20. package/plugins/whitespaced/index.mjs +15 -0
  21. package/plugins/whitespaced/rules/aligned-assignments.mjs +385 -0
  22. package/plugins/whitespaced/rules/block-padding.mjs +289 -0
  23. package/plugins/whitespaced/rules/class-property-grouping.mjs +370 -0
  24. package/plugins/whitespaced/rules/consistent-line-spacing.mjs +266 -0
  25. package/plugins/whitespaced/rules/multiline-format.mjs +533 -0
  26. package/rules.mjs +101 -95
  27. package/test/fixtures/basic-javascript.js +5 -4
  28. package/test/fixtures/complex-patterns.ts +9 -7
  29. package/test/fixtures/edge-cases.js +12 -7
  30. package/test/fixtures/jsx-formatting.jsx +5 -4
  31. package/test/fixtures/omit-parens.invalid.ts +12 -0
  32. package/test/fixtures/omit-parens.valid.ts +13 -0
  33. package/test/fixtures/react-component.tsx +7 -6
  34. package/test/fixtures/react-strict.invalid.tsx +31 -0
  35. package/test/fixtures/react-strict.valid.tsx +76 -0
  36. package/test/fixtures/whitespaced-docstring.invalid.ts +10 -0
  37. package/test/fixtures/whitespaced-docstring.valid.ts +16 -0
  38. package/test/fixtures/whitespaced-members.invalid.ts +22 -0
  39. package/test/fixtures/whitespaced-members.valid.ts +13 -0
  40. package/test/fixtures/whitespaced-multiline.invalid.ts +8 -0
  41. package/test/fixtures/whitespaced-multiline.valid.ts +15 -0
  42. package/test/fixtures/whitespaced-types.valid.ts +5 -0
  43. package/test/fixtures/whitespaced.valid.ts +45 -0
  44. package/test/format-cases.mjs +13 -14
  45. package/test/test-runner.mjs +128 -47
package/rules.mjs CHANGED
@@ -4,8 +4,8 @@
4
4
  */
5
5
 
6
6
  export const rules = {
7
- 'multiline-comment-style': [ 'warn', 'separate-lines', { checkJSDoc: false }],
8
- '@stylistic/lines-around-comment': [ 'warn', {
7
+ '@stylistic/multiline-comment-style': [ 'warn', 'separate-lines', { checkJSDoc: false }],
8
+ '@stylistic/lines-around-comment': [ 'warn', {
9
9
  beforeBlockComment: true,
10
10
  beforeLineComment: false,
11
11
  afterLineComment: false,
@@ -35,106 +35,106 @@ export const rules = {
35
35
  'no-undef': [ 0 ],
36
36
  'use-isnan': [ 'error' ],
37
37
  'no-obj-calls': [ 'error' ],
38
- 'no-new-symbol': [ 'error' ],
38
+ 'no-new-native-nonconstructor': [ 'error' ],
39
39
  'no-func-assign': [ 'error' ],
40
40
  'no-class-assign': [ 'error' ],
41
41
  'no-array-constructor': [ 'error' ],
42
42
  'omit/omit-unnecessary-parens-brackets': [ 'warn' ],
43
43
  'no-inline-types/no-inline-multiline-types': [ 'warn' ],
44
- 'whitespaced/consistent-line-spacing': [ 'off', {
45
- beforeClass: 2,
46
- afterClass: 2,
47
- beforeImports: 0,
48
- afterImports: 2,
49
- beforeFunction: 2,
50
- afterFunction: 2,
51
- ignoreTopLevelCode: false,
52
- skipImportGroups: true
44
+ 'whitespaced/aligned-assignments': [ 'warn', { alignMemberAssignments: false, alignTypes: true }],
45
+ '@stylistic/function-call-spacing': [ 'warn', 'never' ],
46
+ '@stylistic/computed-property-spacing': [ 'warn', 'never' ],
47
+ '@stylistic/brace-style': [ 'warn', 'stroustrup', { allowSingleLine: false }],
48
+ '@stylistic/comma-dangle': [ 'warn', 'only-multiline' ],
49
+ '@stylistic/comma-spacing': [ 'warn', { before: false, after: true }],
50
+ '@stylistic/comma-style': [ 'warn', 'last' ],
51
+ '@stylistic/key-spacing': [ 'warn', { beforeColon: false, afterColon: true, align: 'value' }],
52
+ '@stylistic/keyword-spacing': [ 'warn', { before: true, after: true }],
53
+ '@stylistic/object-property-newline': [ 'warn', { allowAllPropertiesOnSameLine: true }],
54
+ '@stylistic/max-len': [ 0, 400 ],
55
+ '@stylistic/no-tabs': [ 'warn' ],
56
+ '@stylistic/no-extra-parens': [ 'warn', 'all' ],
57
+ '@stylistic/no-multiple-empty-lines': [ 'warn', { max: 2, maxEOF: 0, maxBOF: 0 }],
58
+ '@stylistic/no-trailing-spaces': [ 'warn', { skipBlankLines: false, ignoreComments: false }],
59
+ '@stylistic/no-confusing-arrow': [ 0 ],
60
+ '@stylistic/no-whitespace-before-property': [ 'warn' ],
61
+ '@stylistic/space-in-parens': [ 'warn', 'never' ],
62
+ '@stylistic/space-infix-ops': [ 'warn', { int32Hint: false }],
63
+ '@stylistic/space-unary-ops': [ 'warn', { words: true, nonwords: false }],
64
+ '@stylistic/switch-colon-spacing': [ 'warn', { after: true, before: false }],
65
+ '@stylistic/template-curly-spacing': [ 'warn', 'never' ],
66
+ '@stylistic/wrap-iife': [ 'warn', 'inside' ],
67
+ '@stylistic/wrap-regex': [ 'warn' ],
68
+ '@stylistic/object-curly-spacing': [ 'warn', 'always', { arraysInObjects: false, objectsInObjects: false }],
69
+ '@stylistic/array-bracket-spacing': [ 'warn', 'always', { arraysInArrays: false, objectsInArrays: false }],
70
+ '@stylistic/new-parens': [ 'warn', 'always' ],
71
+ '@stylistic/operator-linebreak': [ 'warn', 'after', { overrides: { '?': 'before', ':': 'before' }}],
72
+ '@stylistic/quote-props': [ 'warn', 'consistent-as-needed', { keywords: false, unnecessary: true, numbers: false }],
73
+ '@stylistic/indent': [ 'warn', 2, { SwitchCase: 1, CallExpression: { arguments: 'first' }, offsetTernaryExpressions: false, flatTernaryExpressions: true }],
74
+ '@stylistic/arrow-spacing': [ 'warn' ],
75
+ '@stylistic/newline-per-chained-call': [ 'warn', { ignoreChainWithDepth: 2 }],
76
+ '@stylistic/lines-between-class-members': [ 'warn', 'always', { exceptAfterSingleLine: true }],
77
+ '@stylistic/one-var-declaration-per-line': [ 'warn', 'always' ],
78
+ '@stylistic/arrow-parens': [ 'warn', 'as-needed' ],
79
+ '@stylistic/dot-location': [ 'warn', 'property' ],
80
+ '@stylistic/eol-last': [ 'warn', 'always' ],
81
+ '@stylistic/multiline-ternary': [ 'warn', 'always-multiline' ],
82
+ '@stylistic/no-floating-decimal': [ 'warn' ],
83
+ '@stylistic/no-extra-semi': [ 'warn' ],
84
+ '@stylistic/semi-spacing': [ 'warn', { before: false, after: true }],
85
+ '@stylistic/semi-style': [ 'warn', 'last' ],
86
+ '@stylistic/semi': [ 'warn', 'never' ],
87
+ '@stylistic/space-before-function-paren': [ 'warn', { anonymous: 'always', named: 'always', asyncArrow: 'always' }],
88
+ '@stylistic/template-tag-spacing': [ 'warn', 'always' ],
89
+ '@stylistic/yield-star-spacing': [ 'warn', 'after' ],
90
+ '@stylistic/quotes': [ 'warn', 'single', { avoidEscape: true, allowTemplateLiterals: 'always' }],
91
+ '@stylistic/jsx-newline': [ 'warn', { prevent: true, allowMultilines: true }],
92
+ '@stylistic/jsx-equals-spacing': [ 'warn', 'never' ],
93
+ '@stylistic/jsx-max-props-per-line': [ 'warn', { maximum: 1, when: 'multiline' }],
94
+ '@stylistic/jsx-self-closing-comp': [ 'warn', { component: true, html: true }],
95
+ '@stylistic/jsx-one-expression-per-line': [ 'warn', { allow: 'non-jsx' }],
96
+ '@stylistic/jsx-tag-spacing': [ 'warn', { closingSlash: 'never', beforeSelfClosing: 'always', beforeClosing: 'never', afterOpening: 'never' }],
97
+ '@stylistic/jsx-closing-bracket-location': [ 'warn', 'after-props' ],
98
+ '@stylistic/jsx-closing-tag-location': [ 'warn', 'line-aligned' ],
99
+ '@stylistic/jsx-first-prop-new-line': [ 'warn', 'multiline' ],
100
+ '@stylistic/jsx-function-call-newline': [ 'warn', 'always' ],
101
+ '@stylistic/jsx-indent-props': [ 'warn', { indentMode: 2, ignoreTernaryOperator: true }],
102
+ '@stylistic/jsx-curly-spacing': [ 'warn', { when: 'always', spacing: { objectLiterals: 'never' }}],
103
+ 'react/no-unescaped-entities': [ 'error', { forbid: [ '>', '}' ]}],
104
+ 'react/jsx-uses-vars': [ 'error' ],
105
+ 'react/jsx-uses-react': [ 'error' ],
106
+ 'react/style-prop-object': [ 'error', { allow: []}],
107
+ 'react/prefer-stateless-function': [ 'error', { ignorePureComponents: false }],
108
+ 'react/no-invalid-html-attribute': [ 'error', []],
109
+ 'react/hook-use-state': [ 'warn', { allowDestructuredState: true }],
110
+ 'react/jsx-no-useless-fragment': [ 'warn', { allowExpressions: true }],
111
+ 'react/jsx-no-target-blank': [ 'warn', { enforceDynamicLinks: 'always' }],
112
+ 'react/jsx-no-constructed-context-values': [ 'warn' ],
113
+ 'react/jsx-no-script-url': [ 'warn' ],
114
+ 'react/jsx-no-comment-textnodes': [ 'warn' ],
115
+ 'react/jsx-no-duplicate-props': [ 'warn' ],
116
+ 'react/jsx-no-undef': [ 'warn' ],
117
+ 'react/jsx-pascal-case': [ 'warn' ],
118
+ 'react/jsx-curly-brace-presence': [ 'warn', { props: 'never', children: 'never' }],
119
+ 'react/display-name': [ 'warn', { checkContextObjects: true }],
120
+ 'react/no-unstable-nested-components': [ 'warn', { allowAsProps: true }],
121
+ 'react/function-component-definition': [ 'warn', { namedComponents: 'arrow-function', unnamedComponents: 'arrow-function' }],
122
+ 'react/jsx-no-leaked-render': [ 'warn', { validStrategies: [ 'ternary', 'coerce' ]}],
123
+ 'react/jsx-sort-props': [ 'warn', {
124
+ callbacksLast: true,
125
+ shorthandFirst: true,
126
+ reservedFirst: true,
127
+ multiline: 'last',
128
+ noSortAlphabetically: true,
53
129
  }],
54
- '@stylistic/function-call-spacing': [ 'warn', 'never' ],
55
- '@stylistic/computed-property-spacing': [ 'warn', 'never' ],
56
- '@stylistic/brace-style': [ 'warn', 'stroustrup', { allowSingleLine: false }],
57
- '@stylistic/comma-dangle': [ 'warn', 'only-multiline' ],
58
- '@stylistic/comma-spacing': [ 'warn', { before: false, after: true }],
59
- '@stylistic/comma-style': [ 'warn', 'last' ],
60
- '@stylistic/key-spacing': [ 'warn', { beforeColon: false, afterColon: true, align: 'value' }],
61
- '@stylistic/keyword-spacing': [ 'warn', { before: true, after: true }],
62
- '@stylistic/object-property-newline': [ 'warn', { allowAllPropertiesOnSameLine: true }],
63
- '@stylistic/max-len': [ 0, 400 ],
64
- '@stylistic/no-tabs': [ 'warn' ],
65
- '@stylistic/no-extra-parens': [ 'warn', 'all' ],
66
- '@stylistic/no-multiple-empty-lines': [ 'warn', { max: 2, maxEOF: 0, maxBOF: 0 }],
67
- '@stylistic/no-trailing-spaces': [ 'warn', { skipBlankLines: false, ignoreComments: false }],
68
- '@stylistic/no-confusing-arrow': [ 0 ],
69
- '@stylistic/no-whitespace-before-property': [ 'warn' ],
70
- '@stylistic/space-in-parens': [ 'warn', 'never' ],
71
- '@stylistic/space-infix-ops': [ 'warn', { int32Hint: false }],
72
- '@stylistic/space-unary-ops': [ 'warn', { words: true, nonwords: false }],
73
- '@stylistic/switch-colon-spacing': [ 'warn', { after: true, before: false }],
74
- '@stylistic/template-curly-spacing': [ 'warn', 'never' ],
75
- '@stylistic/wrap-iife': [ 'warn', 'inside' ],
76
- '@stylistic/wrap-regex': [ 'warn' ],
77
- '@stylistic/object-curly-spacing': [ 'warn', 'always', { arraysInObjects: false, objectsInObjects: false }],
78
- '@stylistic/array-bracket-spacing': [ 'warn', 'always', { arraysInArrays: false, objectsInArrays: false }],
79
- '@stylistic/new-parens': [ 'warn', 'always' ],
80
- '@stylistic/operator-linebreak': [ 'warn', 'after', { overrides: { '?': 'before', ':': 'before' }}],
81
- '@stylistic/quote-props': [ 'warn', 'consistent-as-needed', { keywords: false, unnecessary: true, numbers: false }],
82
- '@stylistic/indent': [ 'warn', 2, { SwitchCase: 1, CallExpression: { arguments: 'first' }, offsetTernaryExpressions: false, flatTernaryExpressions: true }],
83
- '@stylistic/arrow-spacing': [ 'warn' ],
84
- '@stylistic/newline-per-chained-call': [ 'warn', { ignoreChainWithDepth: 2 }],
85
- '@stylistic/lines-between-class-members': [ 'warn', 'always', { exceptAfterSingleLine: true }],
86
- '@stylistic/one-var-declaration-per-line': [ 'warn', 'always' ],
87
- '@stylistic/arrow-parens': [ 'warn', 'as-needed' ],
88
- '@stylistic/dot-location': [ 'warn', 'property' ],
89
- '@stylistic/eol-last': [ 'warn', 'always' ],
90
- '@stylistic/multiline-ternary': [ 'warn', 'always-multiline' ],
91
- '@stylistic/no-floating-decimal': [ 'warn' ],
92
- '@stylistic/no-extra-semi': [ 'warn' ],
93
- '@stylistic/semi-spacing': [ 'warn', { before: false, after: true }],
94
- '@stylistic/semi-style': [ 'warn', 'last' ],
95
- '@stylistic/semi': [ 'warn', 'never' ],
96
- '@stylistic/space-before-function-paren': [ 'warn', { anonymous: 'always', named: 'always', asyncArrow: 'always' }],
97
- '@stylistic/template-tag-spacing': [ 'warn', 'always' ],
98
- '@stylistic/yield-star-spacing': [ 'warn', 'after' ],
99
- '@stylistic/quotes': [ 'warn', 'single', { avoidEscape: true, allowTemplateLiterals: 'always' }],
100
- // JSX/React - Migrated to @stylistic plugin namespace (consistent)
101
- '@stylistic/jsx-newline': [ 'warn', { prevent: true, allowMultilines: true }],
102
- '@stylistic/jsx-props-no-multi-spaces': [ 'warn' ],
103
- '@stylistic/jsx-equals-spacing': [ 'warn', 'never' ],
104
- '@stylistic/jsx-max-props-per-line': [ 'warn', { maximum: 1, when: 'multiline' }],
105
- '@stylistic/jsx-self-closing-comp': [ 'warn', { component: true, html: true }],
106
- '@stylistic/jsx-one-expression-per-line': [ 'warn', { allow: 'non-jsx' }],
107
- '@stylistic/jsx-tag-spacing': [ 'warn', { closingSlash: 'never', beforeSelfClosing: 'always', beforeClosing: 'never', afterOpening: 'never' }],
108
- '@stylistic/jsx-closing-bracket-location': [ 'warn', 'after-props' ],
109
- '@stylistic/jsx-closing-tag-location': [ 'warn', 'line-aligned' ],
110
- '@stylistic/jsx-first-prop-new-line': [ 'warn', 'multiline' ],
111
- '@stylistic/jsx-function-call-newline': [ 'warn', 'always' ],
112
- '@stylistic/jsx-indent-props': [ 'warn', { indentMode: 2, ignoreTernaryOperator: true }],
113
- '@stylistic/jsx-curly-spacing': [ 'warn', { when: 'always', spacing: { objectLiterals: 'never' }}],
114
- 'react/no-unescaped-entities': [ 'error', { forbid: [ '>', '}' ]}],
115
- 'react/jsx-uses-vars': [ 'error' ],
116
- 'react/jsx-uses-react': [ 'error' ],
117
- 'react/style-prop-object': [ 'error', { allow: []}],
118
- 'react/prefer-stateless-function': [ 'error', { ignorePureComponents: false }],
119
- 'react/no-invalid-html-attribute': [ 'error', []],
120
- 'react/hook-use-state': [ 'warn', { allowDestructuredState: true }],
121
- 'react/jsx-no-useless-fragment': [ 'warn', { allowExpressions: true }],
122
- 'react/jsx-no-target-blank': [ 'warn', { enforceDynamicLinks: 'always' }],
123
- 'react/jsx-no-constructed-context-values': [ 'warn' ],
124
- 'react/jsx-no-script-url': [ 'warn' ],
125
- 'react/jsx-no-comment-textnodes': [ 'warn' ],
126
- 'react/jsx-no-duplicate-props': [ 'warn' ],
127
- 'react/jsx-no-undef': [ 'warn' ],
128
- 'react/jsx-pascal-case': [ 'warn' ],
129
- 'react/jsx-curly-brace-presence': [ 'warn', { props: 'never', children: 'never' }],
130
- 'react/display-name': [ 'warn', { checkContextObjects: true }],
131
- 'react-hooks/exhaustive-deps': 0,
132
- '@stylistic/no-multi-spaces': [ 'warn', {
130
+ 'react-hooks/exhaustive-deps': 0,
131
+ '@stylistic/no-multi-spaces': [ 'warn', {
133
132
  exceptions: {
134
- Property: true,
135
- TSTypeAnnotation: true,
136
- VariableDeclarator: true,
137
- ObjectExpression: false,
133
+ Property: true,
134
+ TSTypeAnnotation: true,
135
+ VariableDeclarator: true,
136
+ AssignmentExpression: true,
137
+ ObjectExpression: false,
138
138
  },
139
139
  }],
140
140
  '@stylistic/spaced-comment': [ 'warn', 'always', { markers: [ '/' ]}],
@@ -157,6 +157,12 @@ export const rules = {
157
157
  { blankLine: 'always', prev: [ 'block-like', 'block' ], next: [ 'multiline-expression', 'function', 'block-like', 'block' ]},
158
158
  { blankLine: 'any', prev: [ 'multiline-expression', 'function', 'block-like', 'block' ], next: [ 'multiline-expression', 'function', 'block-like', 'block' ]},
159
159
  ],
160
+ 'react-strict/no-style-prop': [ 'warn' ],
161
+ 'react-strict/no-nested-divs': [ 'warn' ],
162
+ 'react-strict/no-complex-jsx-map': [ 'warn' ],
163
+ 'react-strict/prefer-no-use-effect': [ 'warn' ],
164
+ 'react-strict/no-jsx-value-calculations': [ 'warn' ],
165
+ 'react-strict/jsx-prop-layout': [ 'warn' ],
160
166
  }
161
167
 
162
168
  export default rules
@@ -7,8 +7,8 @@ const testObject = {
7
7
  name: 'test',
8
8
  value: 42,
9
9
  nested: {
10
- property: 'value'
11
- }
10
+ property: 'value',
11
+ },
12
12
  }
13
13
 
14
14
 
@@ -27,8 +27,8 @@ const arrowFunction = (x, y) => {
27
27
 
28
28
 
29
29
  const asyncFunction = async data => {
30
- const response = await fetch('/api/data')
31
- const json = await response.json()
30
+ const response = await fetch('/api/data')
31
+ const json = await response.json()
32
32
 
33
33
  return json
34
34
  }
@@ -47,4 +47,5 @@ class TestClass {
47
47
 
48
48
  export default basicFunction
49
49
 
50
+
50
51
  export { TestClass, arrowFunction }
@@ -20,19 +20,19 @@ const complexObject = {
20
20
  nested: {
21
21
  deeply: {
22
22
  buried: {
23
- value: 'found'
24
- }
25
- }
23
+ value: 'found',
24
+ },
25
+ },
26
26
  },
27
27
  array: [
28
28
  { id: 1, name: 'first' },
29
29
  { id: 2, name: 'second' },
30
- { id: 3, name: 'third' }
30
+ { id: 3, name: 'third' },
31
31
  ],
32
32
  methods: {
33
33
  process: (data: unknown[]) => data.filter(Boolean),
34
- validate: (input: string) => input.length > 0
35
- }
34
+ validate: (input: string) => input.length > 0,
35
+ },
36
36
  }
37
37
 
38
38
 
@@ -112,9 +112,11 @@ const destructuringExample = ({
112
112
  displayName: name,
113
113
  years: age,
114
114
  place: city && country ? `${city}, ${country}` : 'Unknown',
115
- metadata: rest
115
+ metadata: rest,
116
116
  })
117
117
 
118
+
118
119
  export type { ComplexType, GenericInterface }
119
120
 
121
+
120
122
  export { GenericClass, complexFunction, destructuringExample, ternaryChain }
@@ -14,12 +14,18 @@ const complexTemplate = `
14
14
  const regexPatterns = {
15
15
  email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
16
16
  phone: /^\+?[\d\s-()]+$/,
17
- url: /^https?:\/\/.+$/
17
+ url: /^https?:\/\/.+$/,
18
18
  }
19
19
 
20
20
 
21
21
  // Chained method calls
22
- const chainedOperations = [ 1, 2, 3, 4, 5 ]
22
+ const chainedOperations = [
23
+ 1,
24
+ 2,
25
+ 3,
26
+ 4,
27
+ 5,
28
+ ]
23
29
  .filter(x => x % 2 === 0)
24
30
  .map(x => x * 2)
25
31
  .reduce((acc, val) => acc + val, 0)
@@ -37,7 +43,7 @@ const dynamicKeys = {
37
43
  yield 2
38
44
  yield 3
39
45
  },
40
- [Symbol.toStringTag]: 'CustomObject'
46
+ [Symbol.toStringTag]: 'CustomObject',
41
47
  }
42
48
 
43
49
 
@@ -47,7 +53,7 @@ const asyncErrorHandling = async () => {
47
53
  const results = await Promise.all([
48
54
  fetch('/api/users'),
49
55
  fetch('/api/posts'),
50
- fetch('/api/comments')
56
+ fetch('/api/comments'),
51
57
  ])
52
58
 
53
59
 
@@ -78,7 +84,7 @@ const arrowFunctionPatterns = {
78
84
  const result = await processValue(x)
79
85
  return result
80
86
  },
81
- destructured: ({ name, age }) => `${name} is ${age} years old`
87
+ destructured: ({ name, age }) => `${name} is ${age} years old`,
82
88
  }
83
89
 
84
90
 
@@ -103,8 +109,6 @@ const complexSwitch = (type, data) => {
103
109
  }
104
110
 
105
111
 
106
- // Export patterns
107
-
108
112
  export {
109
113
  complexTemplate,
110
114
  regexPatterns,
@@ -116,4 +120,5 @@ export {
116
120
  complexSwitch
117
121
  }
118
122
 
123
+
119
124
  export default complexCondition
@@ -15,9 +15,9 @@ const JSXFormattingTest = ({ items, onSelect, className }) => {
15
15
 
16
16
  {/* Multi-line JSX with props */}
17
17
  <button
18
- onClick={ () => handleClick('test') }
19
18
  disabled={ !items.length }
20
- className="primary-button">
19
+ className="primary-button"
20
+ onClick={ () => handleClick('test') }>
21
21
  Click me
22
22
  </button>
23
23
 
@@ -31,8 +31,7 @@ const JSXFormattingTest = ({ items, onSelect, className }) => {
31
31
  onClick={ () => handleClick(item.id) }>
32
32
  <span>{item.name}</span>
33
33
 
34
- {item.description &&
35
- <small>{item.description}</small>
34
+ {item.description ? <small>{item.description}</small> : null
36
35
  }
37
36
  </li>
38
37
  )}
@@ -132,6 +131,8 @@ const DataProvider = ({ children, data }) => {
132
131
  return children(enhancedData)
133
132
  }
134
133
 
134
+
135
135
  export default JSXFormattingTest
136
136
 
137
+
137
138
  export { withLoading, DataProvider }
@@ -0,0 +1,12 @@
1
+ // expect-warning: omit/omit-unnecessary-parens-brackets
2
+ const foo = ('bar')
3
+
4
+ // expect-warning: omit/omit-unnecessary-parens-brackets
5
+ function getValue () {
6
+ return ('value')
7
+ }
8
+
9
+ // expect-warning: omit/omit-unnecessary-parens-brackets
10
+ const arr = ([1, 2, 3])
11
+
12
+ export { foo, getValue, arr }
@@ -0,0 +1,13 @@
1
+ const foo = 'bar'
2
+
3
+ function getValue () {
4
+ return 'value'
5
+ }
6
+
7
+ const obj = {
8
+ key: 'value',
9
+ }
10
+
11
+ const arr = [ 1, 2, 3 ]
12
+
13
+ export { foo, getValue, obj, arr }
@@ -18,7 +18,7 @@ interface State {
18
18
  const TestComponent: React.FC<Props> = ({ title, count = 0, onUpdate }) => {
19
19
  const [ state, setState ] = useState<State>({
20
20
  isLoading: false,
21
- data: []
21
+ data: [],
22
22
  })
23
23
 
24
24
 
@@ -27,13 +27,13 @@ const TestComponent: React.FC<Props> = ({ title, count = 0, onUpdate }) => {
27
27
  setState(prev => ({ ...prev, isLoading: true }))
28
28
 
29
29
  try {
30
- const response = await fetch('/api/data')
31
- const data = await response.json()
30
+ const response = await fetch('/api/data')
31
+ const data = await response.json()
32
32
 
33
33
  setState(prev => ({
34
34
  ...prev,
35
35
  isLoading: false,
36
- data
36
+ data,
37
37
  }))
38
38
  }
39
39
  catch (error) {
@@ -69,8 +69,8 @@ const TestComponent: React.FC<Props> = ({ title, count = 0, onUpdate }) => {
69
69
 
70
70
  <div className="content">
71
71
  <button
72
- onClick={ () => handleClick(count + 1) }
73
- disabled={ state.isLoading }>
72
+ disabled={ state.isLoading }
73
+ onClick={ () => handleClick(count + 1) }>
74
74
  Click me ({count})
75
75
  </button>
76
76
 
@@ -81,4 +81,5 @@ const TestComponent: React.FC<Props> = ({ title, count = 0, onUpdate }) => {
81
81
  </div>
82
82
  }
83
83
 
84
+
84
85
  export default TestComponent
@@ -0,0 +1,31 @@
1
+
2
+ import React, { useState, useEffect } from 'react'
3
+
4
+ // expect-warning: react-strict/prefer-no-use-effect
5
+ const BadComponent = () => {
6
+ const [ data, setData ] = useState([])
7
+ useEffect(() => {
8
+ fetch('/api').then(r => r.json())
9
+ .then(setData)
10
+ }, [])
11
+
12
+ return <div>
13
+ {/* expect-warning: react-strict/no-nested-divs */}
14
+ <div>
15
+ <p>Nested div is bad</p>
16
+ </div>
17
+
18
+ {/* expect-warning: react-strict/no-style-prop */}
19
+ <span style={{ color: 'red' }}>styled inline</span>
20
+
21
+ {/* expect-warning: react-strict/no-complex-jsx-map */}
22
+ {data.map((item: string) => {
23
+ if (item === 'special')
24
+ return <strong key={ item }>Special</strong>
25
+
26
+ return <span key={ item }>{item}</span>
27
+ })}
28
+ </div>
29
+ }
30
+
31
+ export default BadComponent
@@ -0,0 +1,76 @@
1
+ import React, { useState, useCallback, useMemo } from 'react'
2
+
3
+
4
+ interface UserCardProps {
5
+ id: string
6
+ name: string
7
+ email: string
8
+ isActive: boolean
9
+ onSelect: (id: string) => void
10
+ }
11
+
12
+
13
+ const UserCard: React.FC<UserCardProps> = ({ id, name, email, isActive, onSelect }) => {
14
+ const handleClick = useCallback(() => {
15
+ onSelect(id)
16
+ }, [ id, onSelect ])
17
+
18
+ const displayName = useMemo(() =>
19
+ isActive ? name : `${name} (inactive)`, [ isActive, name ])
20
+
21
+ return <article className="user-card">
22
+ <header>
23
+ <h3>{displayName}</h3>
24
+ </header>
25
+
26
+ <p>{email}</p>
27
+
28
+ <button
29
+ type="button"
30
+ onClick={ handleClick }>
31
+ Select
32
+ </button>
33
+ </article>
34
+ }
35
+
36
+
37
+ interface UserListProps {
38
+ users: UserCardProps[]
39
+ onSelect: (id: string) => void
40
+ }
41
+
42
+
43
+ const UserListItem: React.FC<UserCardProps> = props =>
44
+ <UserCard { ...props } />
45
+
46
+
47
+ const UserList: React.FC<UserListProps> = ({ users, onSelect }) => {
48
+ const [ filter, setFilter ] = useState('')
49
+
50
+ const filtered = useMemo(() =>
51
+ users.filter(u =>
52
+ u.name.toLowerCase().includes(filter.toLowerCase())), [ users, filter ])
53
+
54
+ const handleFilterChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
55
+ setFilter(e.target.value)
56
+ }, [])
57
+
58
+ return <section className="user-list">
59
+ <input
60
+ type="text"
61
+ placeholder="Filter users..."
62
+ value={ filter }
63
+ onChange={ handleFilterChange } />
64
+
65
+ <ul>
66
+ {filtered.map(user =>
67
+ <UserListItem
68
+ key={ user.id }
69
+ { ...user }
70
+ onSelect={ onSelect } />)}
71
+ </ul>
72
+ </section>
73
+ }
74
+
75
+
76
+ export default UserList
@@ -0,0 +1,10 @@
1
+ import path from 'path'
2
+
3
+
4
+ // Only 1 blank line before this docstring — should be 2.
5
+ function getFixturesDir (): string {
6
+ return path.join('/test', 'fixtures')
7
+ }
8
+
9
+
10
+ export { getFixturesDir }
@@ -0,0 +1,16 @@
1
+ import path from 'path'
2
+
3
+
4
+ // Returns the fixtures directory path.
5
+ function getFixturesDir (): string {
6
+ return path.join('/test', 'fixtures')
7
+ }
8
+
9
+
10
+ // Returns the config file path.
11
+ function getConfigPath (): string {
12
+ return path.join('/test', '../index.mjs')
13
+ }
14
+
15
+
16
+ export { getFixturesDir, getConfigPath }
@@ -0,0 +1,22 @@
1
+ import path from 'path'
2
+
3
+
4
+ type ResultsType = { passed: number; failed: number; errors: string[] }
5
+
6
+
7
+ class TestRunner {
8
+ fixturesDir: string
9
+ configPath: string
10
+
11
+ results: ResultsType
12
+
13
+ constructor () {
14
+ this.fixturesDir = path.join('/test', 'fixtures')
15
+ this.configPath = path.join('/test', '../index.mjs')
16
+ this.results = {
17
+ passed: 0,
18
+ failed: 0,
19
+ errors: [],
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,13 @@
1
+ import path from 'path'
2
+
3
+
4
+ class TestRunner {
5
+ fixturesDir: string
6
+
7
+ configPath: string
8
+
9
+ constructor () {
10
+ this.fixturesDir = path.join('/test', 'fixtures')
11
+ this.configPath = path.join('/test', '../index.mjs')
12
+ }
13
+ }
@@ -0,0 +1,8 @@
1
+ const names = [
2
+ 'alpha',
3
+ 'bravo',
4
+ 'charlie',
5
+ ]
6
+
7
+
8
+ export { names }