@st1ggy/linter-config 7.2.2 → 7.3.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.
@@ -19,6 +19,7 @@ node scripts/inventory.mjs
19
19
  | Unicorn | `config.unicorn.js` | eslint-plugin-unicorn. |
20
20
  | SonarJS | `config.sonarjs.js` | eslint-plugin-sonarjs. |
21
21
  | React | `config.react.js` | React/JSX + hooks. |
22
+ | SolidJS | `config.solid.js` | `eslint-plugin-solid` recommended for JS/JSX, TypeScript preset for TS/TSX, and JSX formatting. |
22
23
  | Next.js | `config.next.js` | `@next/eslint-plugin-next`. |
23
24
  | Svelte | `svelte-stack.js` | `eslint-plugin-svelte` flat recommended + TS parser options. |
24
25
  | Astro | `astro-stack.js` | `eslint-plugin-astro` recommended. |
@@ -27,6 +28,7 @@ Inventory rule counts (sample files in this repository):
27
28
 
28
29
  - `eslint-common`: effective rules on `src/examples/example.ts`
29
30
  - `eslint-react`: includes React layer on the same sample
31
+ - `eslint-solid`: effective rules on `src/examples/example.solid.tsx`
30
32
  - `eslint-next`: Next layer on the same sample
31
33
  - `eslint-svelte`: effective rules on `src/examples/example.svelte`
32
34
  - `eslint-astro`: effective rules on `src/examples/example.astro`
package/index.d.ts CHANGED
@@ -9,6 +9,7 @@ declare module '@st1ggy/linter-config' {
9
9
 
10
10
  export const eslintCommon: Linter.Config[]
11
11
  export const eslintReact: Linter.Config[]
12
+ export const eslintSolid: Linter.Config[]
12
13
  export const eslintNext: Linter.Config[]
13
14
  export const eslintSvelte: Linter.Config[]
14
15
  export const eslintAstro: Linter.Config[]
@@ -36,6 +37,14 @@ declare module '@st1ggy/linter-config/eslint-react' {
36
37
  export default config
37
38
  }
38
39
 
40
+ declare module '@st1ggy/linter-config/eslint-solid' {
41
+ import type { Linter } from 'eslint'
42
+
43
+ const config: Linter.Config[]
44
+
45
+ export default config
46
+ }
47
+
39
48
  declare module '@st1ggy/linter-config/eslint-svelte' {
40
49
  import type { Linter } from 'eslint'
41
50
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@st1ggy/linter-config",
3
- "version": "7.2.2",
3
+ "version": "7.3.0",
4
4
  "description": "ESLint, Prettier, and Stylelint shareable configs (sources under src/).",
5
5
  "private": false,
6
6
  "repository": {
@@ -73,6 +73,11 @@
73
73
  "import": "./src/eslint/eslint.config.react.js",
74
74
  "default": "./src/eslint/eslint.config.react.js"
75
75
  },
76
+ "./eslint-solid": {
77
+ "types": "./index.d.ts",
78
+ "import": "./src/eslint/eslint.config.solid.js",
79
+ "default": "./src/eslint/eslint.config.solid.js"
80
+ },
76
81
  "./eslint-svelte": {
77
82
  "types": "./index.d.ts",
78
83
  "import": "./src/eslint/eslint-svelte.config.js",
@@ -140,6 +145,7 @@
140
145
  "eslint-plugin-jsx-a11y": "^6.10.2",
141
146
  "eslint-plugin-react": "^7.37.5",
142
147
  "eslint-plugin-react-hooks": "^7.1.1",
148
+ "eslint-plugin-solid": "^0.18.0",
143
149
  "eslint-plugin-svelte": "^3.22.0",
144
150
  "next": "^16.3.0",
145
151
  "prettier": "^3.9.6",
@@ -165,6 +171,9 @@
165
171
  "eslint-plugin-react-hooks": {
166
172
  "optional": true
167
173
  },
174
+ "eslint-plugin-solid": {
175
+ "optional": true
176
+ },
168
177
  "eslint-plugin-svelte": {
169
178
  "optional": true
170
179
  },
@@ -202,9 +211,11 @@
202
211
  "eslint-plugin-jsx-a11y": "^6.10.2",
203
212
  "eslint-plugin-react": "^7.37.5",
204
213
  "eslint-plugin-react-hooks": "^7.1.1",
214
+ "eslint-plugin-solid": "^0.18.0",
205
215
  "eslint-plugin-svelte": "^3.22.0",
206
216
  "prettier-plugin-astro": "^0.14.1",
207
217
  "prettier-plugin-svelte": "^4.1.1",
218
+ "solid-js": "^1.9.15",
208
219
  "svelte": "^5.56.8"
209
220
  }
210
221
  }
package/scripts/README.md CHANGED
@@ -19,6 +19,7 @@ Direct CLI (paths from repo root — same entry as published **`@st1ggy/linter-c
19
19
 
20
20
  ```bash
21
21
  node ./scripts/linter-init.mjs init --react
22
+ node ./scripts/linter-init.mjs init --solid --dir ./my-solid-app
22
23
  node ./scripts/linter-init.mjs migrate --svelte --dir ./my-app
23
24
  node ./scripts/linter-init.mjs init --astro
24
25
  ```
@@ -30,6 +31,7 @@ The CLI is published under the name **`@st1ggy/linter-config`** (see [`package.j
30
31
  ```bash
31
32
  npx @st1ggy/linter-config init --common
32
33
  npx @st1ggy/linter-config init --next
34
+ npx @st1ggy/linter-config init --solid
33
35
  npm exec @st1ggy/linter-config -- migrate --svelte
34
36
  ```
35
37
 
@@ -39,8 +41,10 @@ npm exec @st1ggy/linter-config -- migrate --svelte
39
41
  npx --yes @st1ggy/linter-config init --astro
40
42
  ```
41
43
 
42
- - **Stack (optional, default common):** `--common` | `--react` | `--next` | `--svelte` | `--astro` (at most one).
44
+ - **Stack (optional, default common):** `--common` | `--react` | `--solid` | `--next` | `--svelte` | `--astro` (at most one).
43
45
  - **`--skip-install`:** only write wrapper files; do not run npm/pnpm/yarn/bun.
44
46
  - Every CLI command opens an interactive wizard. `init` creates missing wrappers; `migrate` can remove selected legacy configs and replaces wrappers.
45
47
 
46
48
  Shell shortcuts in this folder (`init-common.sh`, etc.) call **`node …/linter-init.mjs`** (same script as the published **`@st1ggy/linter-config`** CLI) with the matching stack; pass **`--skip-install`** through when needed.
49
+
50
+ For SolidJS, `init-solid.sh` selects `eslint-solid`, `prettier-common`, and `stylelint-scss`, and installs `eslint-plugin-solid` alongside `@st1ggy/linter-config`.
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ # See init-common.sh — same as npx @st1ggy/linter-config, stack --solid.
3
+ set -euo pipefail
4
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5
+ exec node "${SCRIPT_DIR}/linter-init.mjs" init --solid "$@"
@@ -26,6 +26,11 @@ const flatConfigPresets = [
26
26
  module: 'src/eslint/eslint.config.react.js',
27
27
  sampleFile: 'src/examples/example.ts',
28
28
  },
29
+ {
30
+ id: 'eslint-solid',
31
+ module: 'src/eslint/eslint.config.solid.js',
32
+ sampleFile: 'src/examples/example.solid.tsx',
33
+ },
29
34
  {
30
35
  id: 'eslint-next',
31
36
  module: 'src/eslint/eslint.config.next.js',
@@ -6,7 +6,7 @@ import path from 'node:path'
6
6
 
7
7
  export const PACKAGE = '@st1ggy/linter-config'
8
8
 
9
- export const STACK_KEYS = ['common', 'react', 'next', 'svelte', 'astro']
9
+ export const STACK_KEYS = ['common', 'react', 'solid', 'next', 'svelte', 'astro']
10
10
 
11
11
  export const STACKS = {
12
12
  common: {
@@ -21,6 +21,12 @@ export const STACKS = {
21
21
  stylelint: 'stylelint-scss',
22
22
  packages: ['eslint-plugin-react', 'eslint-plugin-react-hooks'],
23
23
  },
24
+ solid: {
25
+ eslint: 'eslint-solid',
26
+ prettier: 'prettier-common',
27
+ stylelint: 'stylelint-scss',
28
+ packages: ['eslint-plugin-solid'],
29
+ },
24
30
  next: {
25
31
  eslint: 'eslint-next',
26
32
  prettier: 'prettier-common',
@@ -50,6 +56,10 @@ export const STACK_CHOICES = [
50
56
  value: 'react',
51
57
  name: 'react — React + hooks on top of common',
52
58
  },
59
+ {
60
+ value: 'solid',
61
+ name: 'solid — SolidJS + reactivity rules on top of common',
62
+ },
53
63
  {
54
64
  value: 'next',
55
65
  name: 'next — Next.js App Router on top of react',
@@ -260,7 +270,7 @@ Unless --skip-install: if package.json exists, runs the detected package manager
260
270
  ${PACKAGE} and the selected stack's integration plugins.
261
271
 
262
272
  Stack (at most one; default: common):
263
- --common | --react | --next | --svelte | --astro
273
+ --common | --react | --solid | --next | --svelte | --astro
264
274
 
265
275
  Options:
266
276
  --dir, -d Target directory (default: current working directory).
@@ -271,6 +281,7 @@ Examples (after: npm i -D ${PACKAGE}):
271
281
  npx ${PACKAGE}
272
282
  npx ${PACKAGE} init
273
283
  npx ${PACKAGE} init --react
284
+ npx ${PACKAGE} init --solid
274
285
  npx ${PACKAGE} migrate --svelte --dir ./apps/web
275
286
  npx ${PACKAGE} init --astro
276
287
  npm exec ${PACKAGE} -- init --common
@@ -34,6 +34,7 @@ function parseArgv(argv) {
34
34
  'biome',
35
35
  'common',
36
36
  'react',
37
+ 'solid',
37
38
  'next',
38
39
  'svelte',
39
40
  'astro',
@@ -59,6 +60,7 @@ function argvToValues(argv) {
59
60
  biome: Boolean(argv.biome),
60
61
  common: Boolean(argv.common),
61
62
  react: Boolean(argv.react),
63
+ solid: Boolean(argv.solid),
62
64
  next: Boolean(argv.next),
63
65
  svelte: Boolean(argv.svelte),
64
66
  astro: Boolean(argv.astro),
@@ -0,0 +1,134 @@
1
+ /* eslint-disable unicorn/no-incorrect-template-string-interpolation -- Fixtures contain literal JSX expressions. */
2
+
3
+ import { ESLint } from 'eslint'
4
+ import assert from 'node:assert/strict'
5
+ import { mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs'
6
+ import { tmpdir } from 'node:os'
7
+ import path from 'node:path'
8
+ import test from 'node:test'
9
+
10
+ import solidConfig from '@st1ggy/linter-config/eslint-solid'
11
+
12
+ import prettierConfig from '../src/prettier/prettier.config.common.js'
13
+
14
+ import { resolveStackKey, run, stackPackages } from './linter-init-core.mjs'
15
+
16
+ function createProject(context) {
17
+ const prefix = path.join(tmpdir(), 'linter-solid-')
18
+ const directory = realpathSync(mkdtempSync(prefix))
19
+
20
+ context.after(() => rmSync(directory, { recursive: true, force: true }))
21
+ writeFileSync(
22
+ path.join(directory, 'tsconfig.json'),
23
+ JSON.stringify({ compilerOptions: { allowJs: true, jsx: 'preserve', noEmit: true }, include: ['*.jsx', '*.tsx'] }),
24
+ )
25
+ writeFileSync(path.join(directory, '.prettierrc.json'), JSON.stringify(prettierConfig))
26
+
27
+ return directory
28
+ }
29
+
30
+ async function lintSource(context, extension, source) {
31
+ const directory = createProject(context)
32
+ const filePath = path.join(directory, `component.${extension}`)
33
+
34
+ writeFileSync(filePath, source)
35
+ const eslint = new ESLint({
36
+ cwd: directory,
37
+ overrideConfigFile: true,
38
+ overrideConfig: [...solidConfig, { languageOptions: { parserOptions: { tsconfigRootDir: directory } } }],
39
+ })
40
+ const [result] = await eslint.lintFiles([filePath])
41
+
42
+ assert.equal(result.fatalErrorCount, 0, JSON.stringify(result.messages))
43
+
44
+ return result.messages
45
+ }
46
+
47
+ test('Solid preset is available through both public exports', async () => {
48
+ const { eslintSolid } = await import('@st1ggy/linter-config')
49
+
50
+ assert.equal(eslintSolid, solidConfig)
51
+ })
52
+
53
+ for (const extension of ['jsx', 'tsx']) {
54
+ const annotation = extension === 'tsx' ? ': { name: string }' : ''
55
+
56
+ test(`Solid preset accepts reactive props in ${extension}`, async (context) => {
57
+ const messages = await lintSource(
58
+ context,
59
+ extension,
60
+ `export const Greeting = (props${annotation}) => <p>{props.name}</p>\n`,
61
+ )
62
+
63
+ assert.deepEqual(messages, [])
64
+ })
65
+
66
+ test(`Solid preset reports destructured props in ${extension}`, async (context) => {
67
+ const messages = await lintSource(
68
+ context,
69
+ extension,
70
+ `export const Greeting = ({ name }${annotation}) => <p>{name}</p>\n`,
71
+ )
72
+
73
+ assert.ok(messages.some((message) => message.ruleId === 'solid/no-destructure' && message.severity === 2))
74
+ })
75
+
76
+ test(`Solid preset reports lost reactivity in ${extension}`, async (context) => {
77
+ const messages = await lintSource(
78
+ context,
79
+ extension,
80
+ `export const Greeting = (props${annotation}) => {
81
+ const name = props.name
82
+
83
+ return <p>{name}</p>
84
+ }
85
+ `,
86
+ )
87
+
88
+ assert.ok(messages.some((message) => message.ruleId === 'solid/reactivity' && message.severity === 1))
89
+ })
90
+
91
+ test(`Solid preset recognizes components used in ${extension}`, async (context) => {
92
+ const messages = await lintSource(
93
+ context,
94
+ extension,
95
+ 'const Greeting = () => <p>Hello</p>\n\nexport const App = () => <Greeting />\n',
96
+ )
97
+
98
+ assert.deepEqual(messages, [])
99
+ })
100
+ }
101
+
102
+ test('Solid stack generates wrappers, preserves them on init and replaces them on migrate', (context) => {
103
+ const directory = createProject(context)
104
+ const expected = {
105
+ 'eslint.config.js': "export { default } from '@st1ggy/linter-config/eslint-solid';\n",
106
+ 'prettier.config.js': "export { default } from '@st1ggy/linter-config/prettier-common';\n",
107
+ 'stylelint.config.js': "export { default } from '@st1ggy/linter-config/stylelint-scss';\n",
108
+ }
109
+
110
+ run('init', directory, 'solid', { quiet: true })
111
+
112
+ for (const [file, content] of Object.entries(expected)) {
113
+ assert.equal(readFileSync(path.join(directory, file), 'utf8'), content)
114
+ writeFileSync(path.join(directory, file), '// existing config\n')
115
+ }
116
+
117
+ run('init', directory, 'solid', { quiet: true })
118
+
119
+ for (const file of Object.keys(expected)) {
120
+ assert.equal(readFileSync(path.join(directory, file), 'utf8'), '// existing config\n')
121
+ }
122
+
123
+ run('migrate', directory, 'solid', { quiet: true })
124
+
125
+ for (const [file, content] of Object.entries(expected)) {
126
+ assert.equal(readFileSync(path.join(directory, file), 'utf8'), content)
127
+ }
128
+ })
129
+
130
+ test('Solid stack selects its integration plugin and rejects conflicting flags', () => {
131
+ assert.equal(resolveStackKey({ solid: true }), 'solid')
132
+ assert.deepEqual(stackPackages('solid'), ['@st1ggy/linter-config', 'eslint-plugin-solid'])
133
+ assert.throws(() => resolveStackKey({ solid: true, react: true }), /Pick at most one stack flag/)
134
+ })
@@ -0,0 +1,36 @@
1
+ import { defineConfig } from 'eslint/config'
2
+ import solidPlugin from 'eslint-plugin-solid'
3
+
4
+ export default defineConfig([
5
+ {
6
+ ...solidPlugin.configs['flat/recommended'],
7
+ files: ['**/*.{js,jsx,mjs,cjs}'],
8
+ },
9
+ {
10
+ ...solidPlugin.configs['flat/typescript'],
11
+ files: ['**/*.{ts,tsx,mts,cts}'],
12
+ },
13
+ {
14
+ files: ['**/*.{jsx,tsx}'],
15
+ rules: {
16
+ 'solid/self-closing-comp': 'error',
17
+ '@stylistic/jsx-closing-bracket-location': 'error',
18
+ '@stylistic/jsx-closing-tag-location': 'error',
19
+ '@stylistic/jsx-curly-brace-presence': ['error', { props: 'never', children: 'never' }],
20
+ '@stylistic/jsx-curly-newline': ['error', { multiline: 'consistent', singleline: 'forbid' }],
21
+ '@stylistic/jsx-curly-spacing': [
22
+ 'error',
23
+ {
24
+ when: 'never',
25
+ attributes: { allowMultiline: false },
26
+ children: false,
27
+ },
28
+ ],
29
+ '@stylistic/jsx-equals-spacing': ['error', 'never'],
30
+ '@stylistic/jsx-first-prop-new-line': ['error', 'multiline'],
31
+ '@stylistic/jsx-max-props-per-line': ['error', { when: 'multiline' }],
32
+ '@stylistic/no-multi-spaces': 'error',
33
+ '@stylistic/jsx-tag-spacing': 'error',
34
+ },
35
+ },
36
+ ])
@@ -0,0 +1,4 @@
1
+ import solidConfig from './configs/config.solid.js'
2
+ import commonConfig from './eslint.config.common.js'
3
+
4
+ export default [...commonConfig, ...solidConfig]
@@ -0,0 +1,3 @@
1
+ /** @jsxImportSource solid-js */
2
+
3
+ export const Greeting = (props: { name: string }) => <p>{props.name}</p>
package/src/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export { default as eslintCommon } from './eslint/eslint.config.common.js'
2
2
  export { default as eslintReact } from './eslint/eslint.config.react.js'
3
+ export { default as eslintSolid } from './eslint/eslint.config.solid.js'
3
4
  export { default as eslintNext } from './eslint/eslint.config.next.js'
4
5
  export { default as eslintSvelte } from './eslint/eslint-svelte.config.js'
5
6
  export { default as eslintAstro } from './eslint/eslint.config.astro.js'