@lqbach/eslint-config 0.6.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -7,9 +7,12 @@ import {
7
7
  parserTypeScript,
8
8
  parserVue,
9
9
  parserYaml,
10
+ pluginAstro,
10
11
  pluginJsonc,
12
+ pluginNext,
11
13
  pluginPerfectionist,
12
14
  pluginReact,
15
+ pluginReactHooks,
13
16
  pluginTypeScript,
14
17
  pluginUnusedImports,
15
18
  pluginVue,
@@ -45,8 +48,11 @@ export default function config(params: ConfigParams = {}): Array<ConfigObject> {
45
48
  "**/web/public",
46
49
  "**/studio/build",
47
50
  "**/studio/.sanity",
51
+
52
+ ...(params.next ? ["**/.next"] : []),
48
53
  ...(params.ignores ? params.ignores : []),
49
54
  ],
55
+ name: "lqbach/ignores",
50
56
  }
51
57
 
52
58
  //JavaScript Config
@@ -66,6 +72,7 @@ export default function config(params: ConfigParams = {}): Array<ConfigObject> {
66
72
  sourceType: "module",
67
73
  },
68
74
  },
75
+ name: "lqbach/javascript",
69
76
  plugins: {
70
77
  "unused-imports": pluginUnusedImports,
71
78
  },
@@ -86,7 +93,7 @@ export default function config(params: ConfigParams = {}): Array<ConfigObject> {
86
93
 
87
94
  // TypeScript Config
88
95
  const typescriptConfig: ConfigObject =
89
- params.typescript ?? true
96
+ (params.typescript ?? true)
90
97
  ? {
91
98
  files: ["**/*.{ts,tsx}"],
92
99
  languageOptions: {
@@ -95,6 +102,7 @@ export default function config(params: ConfigParams = {}): Array<ConfigObject> {
95
102
  sourceType: "module",
96
103
  },
97
104
  },
105
+ name: "lqbach/typescript",
98
106
  plugins: {
99
107
  "@typescript-eslint": pluginTypeScript,
100
108
  },
@@ -106,7 +114,7 @@ export default function config(params: ConfigParams = {}): Array<ConfigObject> {
106
114
 
107
115
  // React Config: turned off by default
108
116
  const reactConfig: ConfigObject =
109
- params.react ?? false
117
+ ((params.next || params.react) ?? false)
110
118
  ? {
111
119
  files: ["**/*.{js,jsx,mjs,cjs,ts,tsx}"],
112
120
  languageOptions: {
@@ -120,20 +128,38 @@ export default function config(params: ConfigParams = {}): Array<ConfigObject> {
120
128
  },
121
129
  },
122
130
  },
131
+ name: "lqbach/react",
123
132
  plugins: {
124
133
  react: pluginReact,
125
134
  },
126
135
  rules: {
127
- ...pluginReact.configs.flat.recommended.rules,
136
+ ...pluginReact.configs.flat?.recommended.rules,
128
137
  // ignore `css` for emotion usage
129
138
  "react/no-unknown-property": ["error", { ignore: ["css"] }],
130
139
  },
131
140
  }
132
141
  : {}
133
142
 
143
+ const reactHooksConfig: ConfigObject =
144
+ ((params.next ||
145
+ (typeof params.react !== "boolean" && params.react?.hooks === true)) ??
146
+ false)
147
+ ? {
148
+ files: ["**/*.{js,jsx,mjs,cjs,ts,tsx}"],
149
+ name: "lqbach/react-hooks",
150
+ plugins: {
151
+ "react-hooks": pluginReactHooks,
152
+ },
153
+ rules: {
154
+ "react-hooks/exhaustive-deps": "warn",
155
+ "react-hooks/rules-of-hooks": "error",
156
+ },
157
+ }
158
+ : {}
159
+
134
160
  // Vue Config: turned off by default
135
161
  const vueConfig: ConfigObject =
136
- params.vue ?? false
162
+ (params.vue ?? false)
137
163
  ? {
138
164
  files: ["**/*.vue"],
139
165
  languageOptions: {
@@ -143,26 +169,52 @@ export default function config(params: ConfigParams = {}): Array<ConfigObject> {
143
169
  sourceType: "module",
144
170
  },
145
171
  },
172
+ name: "lqbach/vue",
146
173
  plugins: {
147
174
  vue: pluginVue,
148
175
  },
149
176
  processor: pluginVue.processors[".vue"],
150
177
  rules: {
151
178
  ...pluginVue.configs["base"].rules,
152
- ...pluginVue.configs["vue3-essential"].rules,
153
- ...pluginVue.configs["vue3-strongly-recommended"].rules,
179
+ ...pluginVue.configs["essential"].rules,
180
+ ...pluginVue.configs["strongly-recommended"].rules,
154
181
  },
155
182
  }
156
183
  : {}
157
184
 
185
+ // NextJS Config
186
+ const nextConfig: ConfigObject =
187
+ (params.next ?? false)
188
+ ? {
189
+ files: ["**/*.{js,jsx,ts,tsx}"],
190
+ name: "lqbach/next",
191
+ plugins: {
192
+ "@next/next": pluginNext,
193
+ },
194
+ rules: {
195
+ ...pluginNext.configs.recommended.rules,
196
+ ...pluginNext.configs["core-web-vitals"].rules,
197
+ },
198
+ ...(typeof params.next !== "boolean" &&
199
+ params.next?.rootDir && {
200
+ settings: {
201
+ nextjs: {
202
+ rootDir: params.next.rootDir,
203
+ },
204
+ },
205
+ }),
206
+ }
207
+ : {}
208
+
158
209
  // YAML Config
159
210
  const yamlConfig: ConfigObject =
160
- params.yaml ?? true
211
+ (params.yaml ?? true)
161
212
  ? {
162
213
  files: ["**/*.{yaml, yml}"],
163
214
  languageOptions: {
164
215
  parser: parserYaml,
165
216
  },
217
+ name: "lqbach/yaml",
166
218
  plugins: {
167
219
  yml: pluginYaml,
168
220
  },
@@ -175,9 +227,10 @@ export default function config(params: ConfigParams = {}): Array<ConfigObject> {
175
227
 
176
228
  // JSONC Config
177
229
  const jsoncConfig: Array<ConfigObject> =
178
- params.json ?? true
230
+ (params.json ?? true)
179
231
  ? [
180
232
  {
233
+ name: "lqbach/jsonc-plugin",
181
234
  plugins: {
182
235
  jsonc: pluginJsonc,
183
236
  },
@@ -187,6 +240,7 @@ export default function config(params: ConfigParams = {}): Array<ConfigObject> {
187
240
  languageOptions: {
188
241
  parser: parserJsonc,
189
242
  },
243
+ name: "lqbach/json",
190
244
  rules: {
191
245
  ...pluginJsonc.configs["recommended-with-json"].rules,
192
246
  ...pluginJsonc.configs["recommended-with-jsonc"].rules,
@@ -200,8 +254,9 @@ export default function config(params: ConfigParams = {}): Array<ConfigObject> {
200
254
 
201
255
  // Perfectionist Config
202
256
  const perfectionistConfig: ConfigObject =
203
- params.perfectionist ?? true
257
+ (params.perfectionist ?? true)
204
258
  ? {
259
+ name: "lqbach/perfectionist",
205
260
  plugins: {
206
261
  perfectionist: pluginPerfectionist,
207
262
  },
@@ -211,6 +266,37 @@ export default function config(params: ConfigParams = {}): Array<ConfigObject> {
211
266
  }
212
267
  : {}
213
268
 
269
+ // Astro Config
270
+ const astroConfig: ConfigObject =
271
+ (params.astro ?? false)
272
+ ? {
273
+ files: ["**/*.astro"],
274
+ languageOptions: {
275
+ parserOptions: params.typescript
276
+ ? {
277
+ extraFileExtensions: [".astro"],
278
+ parser: parserTypeScript,
279
+ sourceType: "module",
280
+ }
281
+ : {},
282
+ },
283
+ name: "lqbach/astro",
284
+ plugins: {
285
+ astro: pluginAstro,
286
+ },
287
+ rules: {
288
+ "astro/missing-client-only-directive-value": "error",
289
+ "astro/no-conflict-set-directives": "error",
290
+ "astro/no-deprecated-astro-canonicalurl": "error",
291
+ "astro/no-deprecated-astro-fetchcontent": "error",
292
+ "astro/no-deprecated-astro-resolve": "error",
293
+ "astro/no-deprecated-getentrybyslug": "error",
294
+ "astro/no-unused-define-vars-in-style": "error",
295
+ "astro/valid-compile": "error",
296
+ },
297
+ }
298
+ : {}
299
+
214
300
  const prettierConfig: ConfigObject = configPrettier
215
301
 
216
302
  let config: Array<ConfigObject> = []
@@ -218,7 +304,10 @@ export default function config(params: ConfigParams = {}): Array<ConfigObject> {
218
304
  config.push(...javascriptConfig)
219
305
  config.push(typescriptConfig)
220
306
  config.push(reactConfig)
307
+ config.push(reactHooksConfig)
221
308
  config.push(vueConfig)
309
+ config.push(nextConfig)
310
+ config.push(astroConfig)
222
311
  config.push(yamlConfig)
223
312
  config.push(...jsoncConfig)
224
313
  config.push(perfectionistConfig)
package/src/libs.ts CHANGED
@@ -3,18 +3,18 @@
3
3
  // @ts-nocheck
4
4
 
5
5
  export { default as jsConfig } from "@eslint/js"
6
+ export { default as pluginNext } from "@next/eslint-plugin-next"
6
7
  export { default as pluginTypeScript } from "@typescript-eslint/eslint-plugin"
7
8
  export { default as parserTypeScript } from "@typescript-eslint/parser"
8
9
  export { default as configPrettier } from "eslint-config-prettier"
10
+ export { default as pluginAstro } from "eslint-plugin-astro"
9
11
  export { default as pluginJsonc } from "eslint-plugin-jsonc"
10
12
  export { default as pluginPerfectionist } from "eslint-plugin-perfectionist"
11
13
  export { default as pluginReact } from "eslint-plugin-react"
12
-
14
+ export { default as pluginReactHooks } from "eslint-plugin-react-hooks"
13
15
  export { default as pluginUnusedImports } from "eslint-plugin-unused-imports"
14
16
  export { default as pluginVue } from "eslint-plugin-vue"
15
17
  export { default as pluginYaml } from "eslint-plugin-yml"
16
18
  export { default as parserJsonc } from "jsonc-eslint-parser"
17
-
18
19
  export { default as parserVue } from "vue-eslint-parser"
19
-
20
20
  export { default as parserYaml } from "yaml-eslint-parser"
package/src/types.ts CHANGED
@@ -1,16 +1,35 @@
1
+ export interface AstroConfigParams {
2
+ typescript?: boolean
3
+ }
4
+
5
+ export interface ConfigObject {
6
+ files?: Array<string>
7
+ ignores?: Array<string>
8
+ languageOptions?: LanguageOptions
9
+ linterOptions?: LinterOptions
10
+ name?: string
11
+ plugins?: object
12
+ processor?: object
13
+ rules?: object
14
+ // eslint-disable-next-line
15
+ settings?: any
16
+ }
17
+
1
18
  export interface ConfigParams {
19
+ astro?: AstroConfigParams | boolean
2
20
  ignores?: Array<string>
3
21
  json?: boolean
4
22
  markdown?: boolean
23
+ next?: boolean | NextJSConfigParams
5
24
  perfectionist?: boolean
6
- react?: boolean
25
+ react?: boolean | ReactJSConfigParams
7
26
  typescript?: boolean
8
27
  vue?: boolean
9
28
  yaml?: boolean
10
29
  }
11
30
 
12
31
  export interface LanguageOptions {
13
- ecmaVersion?: string
32
+ ecmaVersion?: number | string
14
33
  globals?: object
15
34
  parser?: object
16
35
  parserOptions?: object
@@ -22,13 +41,13 @@ export interface LinterOptions {
22
41
  reportUnusedDisableDirectives?: boolean
23
42
  }
24
43
 
25
- export interface ConfigObject {
26
- files?: Array<string>
27
- ignores?: Array<string>
28
- languageOptions?: LanguageOptions
29
- linterOptions?: LinterOptions
30
- plugins?: object
31
- processor?: object
32
- rules?: object
33
- settings?: object
44
+ export interface NextJSConfigParams {
45
+ rootDir?: string
46
+ }
47
+
48
+ export interface ReactJSConfigParams {
49
+ /**
50
+ * use react hooks
51
+ */
52
+ hooks?: boolean
34
53
  }
@@ -0,0 +1,120 @@
1
+ import { describe, expect, test } from "vitest"
2
+
3
+ import eslintConfig from "../src"
4
+ import { getConfigObjectByName } from "./utils"
5
+
6
+ const ASTRO_CONFIG_OBJECT_NAME = "lqbach/astro"
7
+
8
+ // test astro configuration
9
+ describe("test astro eslint configuration", () => {
10
+ describe("if astro parameter is not true", () => {
11
+ const config = eslintConfig({ astro: false })
12
+
13
+ test("should not have a configuration object lqbach/astro", () => {
14
+ expect(getConfigObjectByName(ASTRO_CONFIG_OBJECT_NAME, config)).toBe(
15
+ undefined,
16
+ )
17
+ })
18
+ })
19
+
20
+ describe("if astro parameter is true", () => {
21
+ const config = eslintConfig({ astro: true })
22
+
23
+ test("should have a configuration object lqbach/astro", () => {
24
+ expect(
25
+ getConfigObjectByName(ASTRO_CONFIG_OBJECT_NAME, config),
26
+ ).toBeTruthy()
27
+ })
28
+
29
+ test("should target *.astro files", () => {
30
+ const astroConfig = getConfigObjectByName(
31
+ ASTRO_CONFIG_OBJECT_NAME,
32
+ config,
33
+ )
34
+ expect(astroConfig?.files).toEqual(["**/*.astro"])
35
+ })
36
+
37
+ test("should include astro plugin", () => {
38
+ const astroConfig = getConfigObjectByName(
39
+ ASTRO_CONFIG_OBJECT_NAME,
40
+ config,
41
+ )
42
+ expect(astroConfig?.plugins).toHaveProperty("astro")
43
+ })
44
+
45
+ test("should include astro recommended rules", () => {
46
+ const astroConfig = getConfigObjectByName(
47
+ ASTRO_CONFIG_OBJECT_NAME,
48
+ config,
49
+ )
50
+ expect(astroConfig?.rules).toBeDefined()
51
+ expect(Object.keys(astroConfig?.rules || {})).toContain(
52
+ "astro/no-conflict-set-directives",
53
+ )
54
+ })
55
+
56
+ test("should not have typescript parser options when typescript is not enabled", () => {
57
+ const astroConfig = getConfigObjectByName(
58
+ ASTRO_CONFIG_OBJECT_NAME,
59
+ config,
60
+ )
61
+ expect(astroConfig?.languageOptions?.parserOptions).toEqual({})
62
+ })
63
+ })
64
+
65
+ describe("if astro parameter is a config object", () => {
66
+ test("should have a configuration object lqbach/astro when typescript is enabled", () => {
67
+ const config = eslintConfig({
68
+ astro: { typescript: true },
69
+ typescript: true,
70
+ })
71
+ expect(
72
+ getConfigObjectByName(ASTRO_CONFIG_OBJECT_NAME, config),
73
+ ).toBeTruthy()
74
+ })
75
+
76
+ test("should include typescript parser options when typescript is enabled", () => {
77
+ const config = eslintConfig({
78
+ astro: { typescript: true },
79
+ typescript: true,
80
+ })
81
+ const astroConfig = getConfigObjectByName(
82
+ ASTRO_CONFIG_OBJECT_NAME,
83
+ config,
84
+ )
85
+ expect(astroConfig?.languageOptions?.parserOptions).toEqual({
86
+ extraFileExtensions: [".astro"],
87
+ parser: expect.any(Object),
88
+ sourceType: "module",
89
+ })
90
+ })
91
+
92
+ test("should not include typescript parser options when typescript is disabled", () => {
93
+ const config = eslintConfig({
94
+ astro: { typescript: false },
95
+ typescript: false,
96
+ })
97
+ const astroConfig = getConfigObjectByName(
98
+ ASTRO_CONFIG_OBJECT_NAME,
99
+ config,
100
+ )
101
+ expect(astroConfig?.languageOptions?.parserOptions).toEqual({})
102
+ })
103
+ })
104
+
105
+ describe("default behavior", () => {
106
+ test("should not include astro config by default", () => {
107
+ const config = eslintConfig({})
108
+ expect(getConfigObjectByName(ASTRO_CONFIG_OBJECT_NAME, config)).toBe(
109
+ undefined,
110
+ )
111
+ })
112
+
113
+ test("should not include astro config when astro is undefined", () => {
114
+ const config = eslintConfig({ astro: undefined })
115
+ expect(getConfigObjectByName(ASTRO_CONFIG_OBJECT_NAME, config)).toBe(
116
+ undefined,
117
+ )
118
+ })
119
+ })
120
+ })
@@ -0,0 +1,39 @@
1
+ import { describe, expect, test } from "vitest"
2
+
3
+ import eslintConfig from "../src"
4
+
5
+ // test tailwind configuration
6
+ describe("test ignore list in eslint configuration", () => {
7
+ describe("if ignores array is populated", () => {
8
+ const IGNORE_FILE_NAME = "**/.ignorethisfile"
9
+
10
+ const IGNORE_LOCK_FILES = [
11
+ "**/package-lock.json",
12
+ "**/yarn.lock",
13
+ "**/pnpm-lock.yaml",
14
+ "**/bun.lockb",
15
+ ]
16
+
17
+ const config = eslintConfig({ ignores: [IGNORE_FILE_NAME] })
18
+
19
+ test("should include the ignored file name in an `ignores` object", () => {
20
+ expect(
21
+ config.some((obj) => obj.ignores?.includes(IGNORE_FILE_NAME)),
22
+ ).toBe(true)
23
+ })
24
+
25
+ test("edge case test for random files", () => {
26
+ expect(config.some((obj) => obj.ignores?.includes("fakefile"))).toBe(
27
+ false,
28
+ )
29
+ })
30
+
31
+ test("test lock files are included", () => {
32
+ expect(
33
+ IGNORE_LOCK_FILES.every((lock) =>
34
+ config.some((obj) => obj.ignores?.includes(lock)),
35
+ ),
36
+ ).toBe(true)
37
+ })
38
+ })
39
+ })
@@ -0,0 +1,56 @@
1
+ import { describe, expect, test } from "vitest"
2
+
3
+ import eslintConfig from "../src"
4
+ import { getConfigObjectByName } from "./utils"
5
+
6
+ const NEXT_CONFIG_OBJECT_NAME = "lqbach/next"
7
+
8
+ // test tailwind configuration
9
+ describe("test nextjs eslint configuration", () => {
10
+ describe("if next parameter is not true", () => {
11
+ const config = eslintConfig({ next: false })
12
+
13
+ test("should not include the ignored folder **/.next in the `ignores` object", () => {
14
+ expect(config.some((obj) => obj.ignores?.includes("**/.next"))).toBe(true)
15
+ })
16
+ test("should not have a configuration object lqbach/next", () => {
17
+ expect(getConfigObjectByName(NEXT_CONFIG_OBJECT_NAME, config)).toBe(
18
+ undefined,
19
+ )
20
+ })
21
+ })
22
+ describe("if next parameter is true", () => {
23
+ const config = eslintConfig({ next: true })
24
+
25
+ test("should include react config", () => {
26
+ expect(getConfigObjectByName("lqbach/react", config)).toBeTruthy()
27
+ })
28
+ test("should include react hooks config", () => {
29
+ expect(getConfigObjectByName("lqbach/react-hooks", config)).toBeTruthy()
30
+ })
31
+
32
+ test("should include the ignored folder **/.next in the `ignores` object", () => {
33
+ expect(
34
+ getConfigObjectByName("lqbach/ignores", config)?.ignores?.includes(
35
+ "**/.next",
36
+ ),
37
+ ).toBe(true)
38
+ })
39
+ })
40
+ describe("if next parameter is a config object", () => {
41
+ test("should include react config", () => {
42
+ const config = eslintConfig({ next: { rootDir: "packages/example" } })
43
+ expect(getConfigObjectByName("lqbach/react", config)).toBeTruthy()
44
+ })
45
+ test("should include react hooks config", () => {
46
+ const config = eslintConfig({ next: { rootDir: "packages/example" } })
47
+ expect(getConfigObjectByName("lqbach/react-hooks", config)).toBeTruthy()
48
+ })
49
+ test("should parse rootDir properly", () => {
50
+ const config = eslintConfig({ next: { rootDir: "packages/example" } })
51
+ expect(
52
+ getConfigObjectByName("lqbach/next", config)?.settings?.nextjs?.rootDir,
53
+ ).toEqual("packages/example")
54
+ })
55
+ })
56
+ })
package/test/utils.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { ConfigObject } from "../src/types"
2
+
3
+ /**
4
+ *
5
+ * @param name A name for the configuration object. Use naming convention recommended by ESLint (https://eslint.org/docs/latest/use/configure/configuration-files#configuration-naming-conventions)
6
+ * @param config ESLint config
7
+ * @returns config object based on the configuration object name
8
+ */
9
+ export const getConfigObjectByName = (
10
+ name: string,
11
+ config: Array<ConfigObject>,
12
+ ) => {
13
+ return config.find((configObj) => {
14
+ return configObj.name === name
15
+ })
16
+ }