@syntopica/eslint-config 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/LICENSE +21 -0
- package/PUBLIC_API.md +47 -0
- package/README.md +106 -0
- package/eslint.config.ts +7 -0
- package/package.json +186 -0
- package/src/accessibility.ts +26 -0
- package/src/astro.ts +62 -0
- package/src/base.ts +252 -0
- package/src/code-quality-sonar.ts +27 -0
- package/src/code-quality.ts +119 -0
- package/src/data-files.ts +71 -0
- package/src/eslint-config-next.d.ts +2 -0
- package/src/frontend-boundaries.ts +92 -0
- package/src/nestjs.ts +25 -0
- package/src/nextjs.ts +85 -0
- package/src/node.ts +28 -0
- package/src/react-version.ts +37 -0
- package/src/tailwind.ts +64 -0
- package/src/testing.ts +35 -0
- package/src/vite-react.ts +64 -0
- package/src/vite-vue.ts +88 -0
- package/tsconfig.json +17 -0
package/src/nextjs.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import nextPlugin from '@next/eslint-plugin-next'
|
|
2
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
3
|
+
import { globalIgnores } from 'eslint/config'
|
|
4
|
+
|
|
5
|
+
import { createFrontendBoundariesConfig } from './frontend-boundaries'
|
|
6
|
+
import { resolveReactVersion } from './react-version'
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
8
|
+
const react = require('eslint-plugin-react') as {
|
|
9
|
+
configs: { flat: { recommended: unknown; 'jsx-runtime': unknown } }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type NextjsConfigOptions = {
|
|
13
|
+
/**
|
|
14
|
+
* React version handed to `eslint-plugin-react`. Defaults to the version of
|
|
15
|
+
* the React installed beside the project, and only falls back to `'detect'`
|
|
16
|
+
* when that cannot be resolved - see `resolveReactVersion`.
|
|
17
|
+
*/
|
|
18
|
+
reactVersion?: string
|
|
19
|
+
tsconfigRootDir?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// This preset registers @next/next, react and react-hooks itself and pulls only
|
|
23
|
+
// Next's Core Web Vitals *rules* from @next/eslint-plugin-next. It does NOT spread
|
|
24
|
+
// the full `eslint-config-next` flat config, because that package also registers
|
|
25
|
+
// `import`, `jsx-a11y` and `@typescript-eslint` — plugins the layered baseline
|
|
26
|
+
// (base + accessibility) already owns — which throws "Cannot redefine plugin".
|
|
27
|
+
// `eslint-config-next` is kept as an optional peer dependency so standalone Next
|
|
28
|
+
// projects that do not layer the full baseline can import it directly.
|
|
29
|
+
const nextRules = {
|
|
30
|
+
...nextPlugin.configs['core-web-vitals'].rules,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const createNextjsConfig = (options: NextjsConfigOptions = {}) => {
|
|
34
|
+
return [
|
|
35
|
+
globalIgnores([
|
|
36
|
+
'.next/**',
|
|
37
|
+
'out/**',
|
|
38
|
+
'build/**',
|
|
39
|
+
'dist/**',
|
|
40
|
+
'coverage/**',
|
|
41
|
+
'next-env.d.ts',
|
|
42
|
+
]),
|
|
43
|
+
|
|
44
|
+
// Core React correctness rules + automatic JSX transform (React 17+)
|
|
45
|
+
react.configs.flat.recommended,
|
|
46
|
+
react.configs.flat['jsx-runtime'],
|
|
47
|
+
|
|
48
|
+
// Unscoped on purpose: the two configs above carry no `files` key, so their
|
|
49
|
+
// rules can run on any linted file and each one would otherwise re-enter
|
|
50
|
+
// version detection.
|
|
51
|
+
{
|
|
52
|
+
settings: {
|
|
53
|
+
react: {
|
|
54
|
+
version: options.reactVersion ?? resolveReactVersion() ?? 'detect',
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
{
|
|
60
|
+
files: ['**/*.{js,jsx,ts,tsx}'],
|
|
61
|
+
plugins: {
|
|
62
|
+
'@next/next': nextPlugin,
|
|
63
|
+
'react-hooks': reactHooks,
|
|
64
|
+
},
|
|
65
|
+
rules: {
|
|
66
|
+
...nextRules,
|
|
67
|
+
...reactHooks.configs.recommended.rules,
|
|
68
|
+
|
|
69
|
+
// prevent {count && <Comp />} rendering "0" when count === 0
|
|
70
|
+
'react/jsx-no-leaked-render': 'error',
|
|
71
|
+
// avoid array index as list key (causes stale renders)
|
|
72
|
+
'react/no-array-index-key': 'warn',
|
|
73
|
+
// <MyComp></MyComp> → <MyComp /> (auto-fixable)
|
|
74
|
+
'react/self-closing-comp': 'warn',
|
|
75
|
+
// dangerouslySetInnerHTML opens XSS vectors
|
|
76
|
+
'react/no-danger': 'error',
|
|
77
|
+
// inline component definitions cause infinite re-mount loops
|
|
78
|
+
'react/no-unstable-nested-components': 'error',
|
|
79
|
+
// remove pointless <> wrappers
|
|
80
|
+
'react/jsx-no-useless-fragment': ['warn', { allowExpressions: true }],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
...createFrontendBoundariesConfig(),
|
|
84
|
+
]
|
|
85
|
+
}
|
package/src/node.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import unicorn from 'eslint-plugin-unicorn'
|
|
2
|
+
import globals from 'globals'
|
|
3
|
+
|
|
4
|
+
export const createNodeConfig = () => [
|
|
5
|
+
{
|
|
6
|
+
// A CLI's stdout is its product, and a maintenance script's output is the
|
|
7
|
+
// reason it is run. `no-console` is an error everywhere else - a
|
|
8
|
+
// `console.log` in application code is a debugging statement that reached
|
|
9
|
+
// production - but forbidding it here would only produce a file of
|
|
10
|
+
// suppressions. Scoped by directory rather than by preset: a NestJS
|
|
11
|
+
// service composes this same config and its request handlers must not
|
|
12
|
+
// print to stdout.
|
|
13
|
+
files: ['**/bin/**/*.{ts,js,mjs,cjs}', '**/scripts/**/*.{ts,js,mjs,cjs}'],
|
|
14
|
+
rules: {
|
|
15
|
+
'no-console': 'off',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
files: ['**/*.{ts,js,mjs,cjs}'],
|
|
20
|
+
languageOptions: {
|
|
21
|
+
globals: { ...globals.node },
|
|
22
|
+
},
|
|
23
|
+
plugins: { unicorn },
|
|
24
|
+
rules: {
|
|
25
|
+
'unicorn/prefer-node-protocol': 'error',
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs'
|
|
2
|
+
import { createRequire } from 'node:module'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The React version installed next to the project being linted, or `undefined`
|
|
6
|
+
* when it cannot be resolved.
|
|
7
|
+
*
|
|
8
|
+
* `settings.react.version: 'detect'` makes `eslint-plugin-react` walk up from
|
|
9
|
+
* the linted file with its own resolver, and on ESLint 10 that walk crashes:
|
|
10
|
+
* `resolveBasedir` still calls `context.getFilename()`, removed in ESLint 10,
|
|
11
|
+
* so every file fails with `contextOrFilename.getFilename is not a function`
|
|
12
|
+
* before a single rule runs (eslint-plugin-react 7.37.5, the newest published).
|
|
13
|
+
* Handing the plugin a concrete version skips detection entirely.
|
|
14
|
+
*
|
|
15
|
+
* Resolution is from `process.cwd()` - where ESLint is invoked - and not from
|
|
16
|
+
* this package, so a consumer's own React is found rather than whichever copy
|
|
17
|
+
* a hoisted install placed beside the config.
|
|
18
|
+
*/
|
|
19
|
+
export const resolveReactVersion = (): string | undefined => {
|
|
20
|
+
try {
|
|
21
|
+
const manifestPath = createRequire(import.meta.url).resolve(
|
|
22
|
+
'react/package.json',
|
|
23
|
+
{ paths: [process.cwd()] },
|
|
24
|
+
)
|
|
25
|
+
// eslint-disable-next-line security/detect-non-literal-fs-filename -- path comes from require.resolve, not from user input
|
|
26
|
+
const manifest: unknown = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
27
|
+
|
|
28
|
+
return typeof manifest === 'object' &&
|
|
29
|
+
manifest !== null &&
|
|
30
|
+
'version' in manifest &&
|
|
31
|
+
typeof manifest.version === 'string'
|
|
32
|
+
? manifest.version
|
|
33
|
+
: undefined
|
|
34
|
+
} catch {
|
|
35
|
+
return undefined
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/tailwind.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
2
|
+
const tailwindcss = require('eslint-plugin-tailwindcss') as {
|
|
3
|
+
// v4 stable exposes a single flat-config object (v4 beta had 'flat/recommended' as an array).
|
|
4
|
+
configs: { recommended: object }
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tailwind CSS lint rules.
|
|
9
|
+
* Enforces class ordering, detects contradicting classes (e.g. `flex block`),
|
|
10
|
+
* promotes shorthands (e.g. `pt-4 pb-4` → `py-4`).
|
|
11
|
+
*
|
|
12
|
+
* `cssConfigPath` is MANDATORY since eslint-plugin-tailwindcss v4 stable: the
|
|
13
|
+
* plugin resolves the design system from the Tailwind v4 CSS entry file (the
|
|
14
|
+
* one with `@import "tailwindcss"`), e.g. `./src/styles.css`.
|
|
15
|
+
*
|
|
16
|
+
* `no-custom-classname` is intentionally disabled: real apps routinely mix
|
|
17
|
+
* Tailwind utilities with their own CSS layer (BEM-style classes, component
|
|
18
|
+
* classes) and custom prefixes, which this rule cannot distinguish from typos.
|
|
19
|
+
* On those codebases it produces overwhelming false-positive noise with no
|
|
20
|
+
* reliable signal, so it is off by default. Re-enable per-project with an
|
|
21
|
+
* allowlist (`settings.tailwindcss.whitelist`) if a project is utility-only.
|
|
22
|
+
*
|
|
23
|
+
* `classnames-order` is intentionally disabled too: `prettier-plugin-tailwindcss`
|
|
24
|
+
* (shipped in `@syntopica/prettier-config/frontend`) is the class sorter of
|
|
25
|
+
* record and orders classes differently from this rule. A project running
|
|
26
|
+
* both `eslint --fix` and `prettier --write` would see each pass fight the
|
|
27
|
+
* other's ordering, so `lint` and `format:check` could never agree at the
|
|
28
|
+
* same time. Prettier owns class ordering; this rule stays off to avoid the
|
|
29
|
+
* conflict.
|
|
30
|
+
*
|
|
31
|
+
* Requires `tailwindcss` to be installed in the consuming project.
|
|
32
|
+
* Only add this to projects that use Tailwind CSS.
|
|
33
|
+
*/
|
|
34
|
+
export const createTailwindConfig = (options: { cssConfigPath: string }) => {
|
|
35
|
+
// eslint.config.ts is loaded by jiti at runtime, which does not type-check
|
|
36
|
+
// the call, so a missing option surfaces as a bare
|
|
37
|
+
// "Cannot read properties of undefined" thrown from inside ESLint with no
|
|
38
|
+
// hint about which factory asked for what. Say it plainly instead.
|
|
39
|
+
// The type says `options` is required, and for a TypeScript caller it is.
|
|
40
|
+
// At runtime it is whatever the config file passed, so the check is real.
|
|
41
|
+
const given = options as { cssConfigPath?: string } | undefined
|
|
42
|
+
if (!given?.cssConfigPath) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
'createTailwindConfig requires { cssConfigPath }. ' +
|
|
45
|
+
'eslint-plugin-tailwindcss v4 resolves the design system from your ' +
|
|
46
|
+
'Tailwind CSS entry file - the one with `@import "tailwindcss"` - so ' +
|
|
47
|
+
"pass it: createTailwindConfig({ cssConfigPath: './src/styles.css' }).",
|
|
48
|
+
)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return [
|
|
52
|
+
tailwindcss.configs.recommended,
|
|
53
|
+
{
|
|
54
|
+
files: ['**/*.{js,jsx,ts,tsx,vue,astro,html}'],
|
|
55
|
+
settings: {
|
|
56
|
+
tailwindcss: { cssConfigPath: options.cssConfigPath },
|
|
57
|
+
},
|
|
58
|
+
rules: {
|
|
59
|
+
'tailwindcss/no-custom-classname': 'off',
|
|
60
|
+
'tailwindcss/classnames-order': 'off',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
]
|
|
64
|
+
}
|
package/src/testing.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import vitest from '@vitest/eslint-plugin'
|
|
2
|
+
import testingLibrary from 'eslint-plugin-testing-library'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Test-file rules. These catch what review misses in a green build: a
|
|
6
|
+
* committed `.only` silently skipping the rest of the suite, a test with no
|
|
7
|
+
* assertion, and Testing Library queries whose promises are never awaited.
|
|
8
|
+
*
|
|
9
|
+
* `eslint-plugin-vitest` is deprecated; `@vitest/eslint-plugin` is its
|
|
10
|
+
* maintained successor.
|
|
11
|
+
*/
|
|
12
|
+
export const createTestingConfig = () => [
|
|
13
|
+
{
|
|
14
|
+
files: [
|
|
15
|
+
'**/*.{test,spec}.{ts,tsx}',
|
|
16
|
+
'**/__tests__/**/*.{ts,tsx}',
|
|
17
|
+
'**/test/**/*.{ts,tsx}',
|
|
18
|
+
],
|
|
19
|
+
plugins: { vitest, 'testing-library': testingLibrary },
|
|
20
|
+
rules: {
|
|
21
|
+
'vitest/no-focused-tests': ['error', { fixable: false }],
|
|
22
|
+
'vitest/no-disabled-tests': 'warn',
|
|
23
|
+
'vitest/no-identical-title': 'error',
|
|
24
|
+
'vitest/expect-expect': 'error',
|
|
25
|
+
'vitest/valid-expect': 'error',
|
|
26
|
+
'vitest/no-conditional-expect': 'error',
|
|
27
|
+
'testing-library/await-async-queries': 'error',
|
|
28
|
+
'testing-library/await-async-utils': 'error',
|
|
29
|
+
'testing-library/no-await-sync-queries': 'error',
|
|
30
|
+
'testing-library/no-container': 'error',
|
|
31
|
+
'testing-library/no-node-access': 'warn',
|
|
32
|
+
'testing-library/prefer-screen-queries': 'error',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import reactHooks from 'eslint-plugin-react-hooks'
|
|
2
|
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
|
3
|
+
|
|
4
|
+
import { createFrontendBoundariesConfig } from './frontend-boundaries'
|
|
5
|
+
import { resolveReactVersion } from './react-version'
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
7
|
+
const react = require('eslint-plugin-react') as {
|
|
8
|
+
configs: { flat: { recommended: unknown; 'jsx-runtime': unknown } }
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type ViteReactConfigOptions = {
|
|
12
|
+
/**
|
|
13
|
+
* React version handed to `eslint-plugin-react`. Defaults to the version of
|
|
14
|
+
* the React installed beside the project, and only falls back to `'detect'`
|
|
15
|
+
* when that cannot be resolved - see `resolveReactVersion`.
|
|
16
|
+
*/
|
|
17
|
+
reactVersion?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const createViteReactConfig = (options: ViteReactConfigOptions = {}) => [
|
|
21
|
+
// Core React correctness rules + automatic JSX transform (React 17+)
|
|
22
|
+
react.configs.flat.recommended,
|
|
23
|
+
react.configs.flat['jsx-runtime'],
|
|
24
|
+
// Unscoped on purpose: the two configs above carry no `files` key, so their
|
|
25
|
+
// rules can run on any linted file and each one would otherwise re-enter
|
|
26
|
+
// version detection.
|
|
27
|
+
{
|
|
28
|
+
settings: {
|
|
29
|
+
react: {
|
|
30
|
+
version: options.reactVersion ?? resolveReactVersion() ?? 'detect',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
files: ['**/*.{ts,tsx,js,jsx}'],
|
|
36
|
+
plugins: {
|
|
37
|
+
'react-hooks': reactHooks,
|
|
38
|
+
'react-refresh': reactRefresh,
|
|
39
|
+
},
|
|
40
|
+
rules: {
|
|
41
|
+
// hooks rules
|
|
42
|
+
...reactHooks.configs.recommended.rules,
|
|
43
|
+
'react-refresh/only-export-components': [
|
|
44
|
+
'warn',
|
|
45
|
+
{ allowConstantExport: true },
|
|
46
|
+
],
|
|
47
|
+
|
|
48
|
+
// prevent {count && <Comp />} rendering "0" when count === 0
|
|
49
|
+
'react/jsx-no-leaked-render': 'error',
|
|
50
|
+
// avoid array index as list key (causes stale renders)
|
|
51
|
+
'react/no-array-index-key': 'warn',
|
|
52
|
+
// <MyComp></MyComp> → <MyComp /> (auto-fixable)
|
|
53
|
+
'react/self-closing-comp': 'warn',
|
|
54
|
+
// dangerouslySetInnerHTML opens XSS vectors
|
|
55
|
+
'react/no-danger': 'error',
|
|
56
|
+
// inline component definitions cause infinite re-mount loops
|
|
57
|
+
'react/no-unstable-nested-components': 'error',
|
|
58
|
+
// remove pointless <> wrappers
|
|
59
|
+
'react/jsx-no-useless-fragment': ['warn', { allowExpressions: true }],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
// Same layered boundaries as Next.js (src/components, src/services, etc.)
|
|
63
|
+
...createFrontendBoundariesConfig(),
|
|
64
|
+
]
|
package/src/vite-vue.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import unusedImports from 'eslint-plugin-unused-imports'
|
|
2
|
+
import tseslint from 'typescript-eslint'
|
|
3
|
+
|
|
4
|
+
import { createFrontendBoundariesConfig } from './frontend-boundaries'
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
7
|
+
const pluginVue = require('eslint-plugin-vue') as {
|
|
8
|
+
configs: Record<string, unknown[]>
|
|
9
|
+
}
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
11
|
+
const vueA11y = require('eslint-plugin-vuejs-accessibility') as {
|
|
12
|
+
configs: Record<string, unknown[]>
|
|
13
|
+
}
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
15
|
+
const vueParser = require('vue-eslint-parser') as {
|
|
16
|
+
parseForESLint: (...args: unknown[]) => unknown
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type ViteVueConfigOptions = {
|
|
20
|
+
tsconfigRootDir?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Vue 3 SFC linting layer for Vite apps.
|
|
25
|
+
*
|
|
26
|
+
* - eslint-plugin-vue `flat/recommended` (highest priority tier: essential +
|
|
27
|
+
* strongly-recommended + recommended).
|
|
28
|
+
* - eslint-plugin-vuejs-accessibility `flat/recommended` (jsx-a11y does not lint
|
|
29
|
+
* `.vue` SFCs, so Vue gets its own a11y layer here).
|
|
30
|
+
* - `.vue` files parsed by vue-eslint-parser with typescript-eslint as the
|
|
31
|
+
* `<script lang="ts">` parser (type-aware via projectService).
|
|
32
|
+
* - Reuses the shared frontend import boundaries.
|
|
33
|
+
*/
|
|
34
|
+
export const createViteVueConfig = (options: ViteVueConfigOptions = {}) => {
|
|
35
|
+
const tsconfigRootDir = options.tsconfigRootDir ?? process.cwd()
|
|
36
|
+
const vueRecommended = pluginVue.configs['flat/recommended'] ?? []
|
|
37
|
+
const a11yRecommended = vueA11y.configs['flat/recommended'] ?? []
|
|
38
|
+
|
|
39
|
+
return [
|
|
40
|
+
...vueRecommended,
|
|
41
|
+
...a11yRecommended,
|
|
42
|
+
{
|
|
43
|
+
files: ['**/*.vue'],
|
|
44
|
+
languageOptions: {
|
|
45
|
+
parser: vueParser,
|
|
46
|
+
parserOptions: {
|
|
47
|
+
parser: tseslint.parser,
|
|
48
|
+
projectService: true,
|
|
49
|
+
tsconfigRootDir,
|
|
50
|
+
extraFileExtensions: ['.vue'],
|
|
51
|
+
ecmaVersion: 2024,
|
|
52
|
+
sourceType: 'module',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
plugins: {
|
|
56
|
+
'unused-imports': unusedImports,
|
|
57
|
+
},
|
|
58
|
+
rules: {
|
|
59
|
+
// TypeScript (not core ESLint) resolves identifiers inside `<script
|
|
60
|
+
// setup lang="ts">`; core `no-undef` cannot see ambient/global types and
|
|
61
|
+
// would false-positive on them. typescript-eslint disables it for .ts
|
|
62
|
+
// for the same reason — mirror that for .vue SFCs.
|
|
63
|
+
'no-undef': 'off',
|
|
64
|
+
// Unused-binding handling consistent with the .ts layer: defer to
|
|
65
|
+
// unused-imports with the `_`-prefix escape hatch.
|
|
66
|
+
'no-unused-vars': 'off',
|
|
67
|
+
'unused-imports/no-unused-vars': [
|
|
68
|
+
'error',
|
|
69
|
+
{
|
|
70
|
+
args: 'after-used',
|
|
71
|
+
argsIgnorePattern: '^_',
|
|
72
|
+
varsIgnorePattern: '^_',
|
|
73
|
+
ignoreRestSiblings: true,
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
// raw HTML binding is an XSS vector
|
|
77
|
+
'vue/no-v-html': 'error',
|
|
78
|
+
// force the type-based defineProps<...>() form
|
|
79
|
+
'vue/define-props-declaration': ['error', 'type-based'],
|
|
80
|
+
// refs must carry a type when it cannot be inferred
|
|
81
|
+
'vue/require-typed-ref': 'error',
|
|
82
|
+
// multi-word component names (root App is the conventional exception)
|
|
83
|
+
'vue/multi-word-component-names': ['error', { ignores: ['App'] }],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
...createFrontendBoundariesConfig(),
|
|
87
|
+
]
|
|
88
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2024",
|
|
4
|
+
"lib": ["ES2024"],
|
|
5
|
+
"strict": true,
|
|
6
|
+
"noEmit": true,
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"moduleResolution": "Bundler",
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"skipLibCheck": true,
|
|
13
|
+
"types": ["node"],
|
|
14
|
+
"allowImportingTsExtensions": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src/**/*.ts", "eslint.config.ts"]
|
|
17
|
+
}
|