@pyreon/lint 0.12.13 → 0.12.14
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/README.md +55 -2
- package/lib/analysis/cli.js.html +1 -1
- package/lib/analysis/index.js.html +1 -1
- package/lib/cli.js +960 -162
- package/lib/cli.js.map +1 -1
- package/lib/index.js +935 -161
- package/lib/index.js.map +1 -1
- package/lib/types/index.d.ts +96 -23
- package/lib/types/index.d.ts.map +1 -1
- package/package.json +2 -1
- package/schema/pyreonlintrc.schema.json +64 -0
- package/src/cli.ts +44 -2
- package/src/config/presets.ts +13 -1
- package/src/index.ts +7 -0
- package/src/lint.ts +37 -6
- package/src/lsp/index.ts +15 -2
- package/src/rules/architecture/dev-guard-warnings.ts +172 -17
- package/src/rules/architecture/no-circular-import.ts +7 -0
- package/src/rules/architecture/no-process-dev-gate.ts +18 -45
- package/src/rules/architecture/require-browser-smoke-test.ts +227 -0
- package/src/rules/form/no-submit-without-validation.ts +9 -0
- package/src/rules/form/no-unregistered-field.ts +9 -0
- package/src/rules/hooks/no-raw-addeventlistener.ts +7 -0
- package/src/rules/hooks/no-raw-localstorage.ts +12 -1
- package/src/rules/hooks/no-raw-setinterval.ts +14 -0
- package/src/rules/index.ts +4 -1
- package/src/rules/jsx/no-props-destructure.ts +20 -6
- package/src/rules/lifecycle/no-dom-in-setup.ts +67 -7
- package/src/rules/reactivity/no-bare-signal-in-jsx.ts +12 -1
- package/src/rules/reactivity/no-unbatched-updates.ts +3 -0
- package/src/rules/router/no-imperative-navigate-in-render.ts +131 -35
- package/src/rules/ssr/no-window-in-ssr.ts +418 -35
- package/src/rules/store/no-duplicate-store-id.ts +11 -0
- package/src/rules/store/no-mutate-store-state.ts +11 -1
- package/src/rules/styling/no-dynamic-styled.ts +13 -24
- package/src/rules/styling/no-theme-outside-provider.ts +34 -2
- package/src/runner.ts +100 -10
- package/src/tests/runner.test.ts +1573 -21
- package/src/types.ts +74 -3
- package/src/utils/component-context.ts +106 -0
- package/src/utils/exempt-paths.ts +39 -0
- package/src/utils/file-roles.ts +32 -0
- package/src/utils/imports.ts +4 -1
- package/src/utils/validate-options.ts +68 -0
- package/src/watcher.ts +17 -0
package/src/types.ts
CHANGED
|
@@ -42,23 +42,64 @@ export type RuleCategory =
|
|
|
42
42
|
| 'accessibility'
|
|
43
43
|
| 'router'
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Declared type of an option slot. Minimal on purpose — sufficient for
|
|
47
|
+
* the exemption patterns we actually use. Extend when a rule needs more.
|
|
48
|
+
*/
|
|
49
|
+
export type OptionType = 'string' | 'string[]' | 'number' | 'boolean'
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Schema for a rule's options bag — keys are option names, values are
|
|
53
|
+
* their declared types. Unknown keys in user config emit a warning;
|
|
54
|
+
* wrong-typed values disable the rule and emit an error. Rules with no
|
|
55
|
+
* schema accept any options (no validation).
|
|
56
|
+
*/
|
|
57
|
+
export type RuleOptionsSchema = Record<string, OptionType>
|
|
58
|
+
|
|
45
59
|
export interface RuleMeta {
|
|
46
60
|
id: string
|
|
47
61
|
category: RuleCategory
|
|
48
62
|
description: string
|
|
49
63
|
severity: Severity
|
|
50
64
|
fixable: boolean
|
|
65
|
+
/**
|
|
66
|
+
* Declared options shape. Validated once when a config enables the rule;
|
|
67
|
+
* bad options either get reported (unknown key → warn, wrong type →
|
|
68
|
+
* error + rule disabled for that run).
|
|
69
|
+
*/
|
|
70
|
+
schema?: RuleOptionsSchema
|
|
51
71
|
}
|
|
52
72
|
|
|
73
|
+
// ── Rule Options ────────────────────────────────────────────────────────────
|
|
74
|
+
//
|
|
75
|
+
// Rules can be configured with an options object in addition to severity.
|
|
76
|
+
// This lets users opt files out of a rule without hardcoding paths in the
|
|
77
|
+
// rule source (which would ship to every consuming project).
|
|
78
|
+
//
|
|
79
|
+
// Convention: rules that support path-based exemption read
|
|
80
|
+
// `options.exemptPaths: string[]` — each entry is a substring matched
|
|
81
|
+
// against the file path. See `utils/exempt-paths.ts` for the helper.
|
|
82
|
+
|
|
83
|
+
export type RuleOptions = Record<string, unknown>
|
|
84
|
+
|
|
53
85
|
// ── Rule Context & Visitor ──────────────────────────────────────────────────
|
|
54
86
|
|
|
55
87
|
export interface RuleContext {
|
|
56
88
|
report(diagnostic: Omit<Diagnostic, 'ruleId' | 'severity' | 'loc'>): void
|
|
57
89
|
getSourceText(): string
|
|
58
90
|
getFilePath(): string
|
|
91
|
+
/** Options passed via config (tuple form: `[severity, options]`). */
|
|
92
|
+
getOptions(): RuleOptions
|
|
59
93
|
}
|
|
60
94
|
|
|
61
|
-
|
|
95
|
+
/**
|
|
96
|
+
* Visitor callback. oxc's walker only passes the current node — it does NOT
|
|
97
|
+
* pass `parent`. Rules that need parent context must track it via
|
|
98
|
+
* enter/exit depth counters or pre-mark child nodes via WeakSet on the way
|
|
99
|
+
* in. An earlier `parent?: any` signature here was a false promise that
|
|
100
|
+
* silently disabled `parent.type === '…'` checks across multiple rules.
|
|
101
|
+
*/
|
|
102
|
+
export type VisitorCallback = (node: any) => void
|
|
62
103
|
|
|
63
104
|
export interface VisitorCallbacks {
|
|
64
105
|
[nodeType: string]: VisitorCallback
|
|
@@ -73,15 +114,25 @@ export interface Rule {
|
|
|
73
114
|
|
|
74
115
|
// ── Configuration ───────────────────────────────────────────────────────────
|
|
75
116
|
|
|
117
|
+
/**
|
|
118
|
+
* A rule entry is either a bare severity (`"error"`, `"warn"`, `"info"`,
|
|
119
|
+
* `"off"`) or a tuple `[severity, options]`. The tuple form lets consumers
|
|
120
|
+
* pass per-rule options without a bespoke API per rule.
|
|
121
|
+
*
|
|
122
|
+
* "pyreon/no-window-in-ssr": "error"
|
|
123
|
+
* "pyreon/no-window-in-ssr": ["error", { "exemptPaths": ["packages/core/runtime-dom/"] }]
|
|
124
|
+
*/
|
|
125
|
+
export type RuleEntry = Severity | readonly [Severity, RuleOptions]
|
|
126
|
+
|
|
76
127
|
export interface LintConfig {
|
|
77
|
-
rules: Record<string,
|
|
128
|
+
rules: Record<string, RuleEntry>
|
|
78
129
|
include?: string[] | undefined
|
|
79
130
|
exclude?: string[] | undefined
|
|
80
131
|
}
|
|
81
132
|
|
|
82
133
|
export interface LintConfigFile {
|
|
83
134
|
preset?: PresetName | undefined
|
|
84
|
-
rules?: Record<string,
|
|
135
|
+
rules?: Record<string, RuleEntry> | undefined
|
|
85
136
|
include?: string[] | undefined
|
|
86
137
|
exclude?: string[] | undefined
|
|
87
138
|
}
|
|
@@ -96,11 +147,25 @@ export interface LintFileResult {
|
|
|
96
147
|
fixedSource?: string | undefined
|
|
97
148
|
}
|
|
98
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Config-level diagnostic — emitted by `validateRuleOptions` when a rule's
|
|
152
|
+
* configured options don't match its declared `schema`. Not tied to a
|
|
153
|
+
* source file; lives on `LintResult.configDiagnostics` so programmatic
|
|
154
|
+
* consumers (CI, LSP, JSON reporters) surface them alongside file diags.
|
|
155
|
+
*/
|
|
156
|
+
export interface ConfigDiagnostic {
|
|
157
|
+
ruleId: string
|
|
158
|
+
severity: 'error' | 'warn'
|
|
159
|
+
message: string
|
|
160
|
+
}
|
|
161
|
+
|
|
99
162
|
export interface LintResult {
|
|
100
163
|
files: LintFileResult[]
|
|
101
164
|
totalErrors: number
|
|
102
165
|
totalWarnings: number
|
|
103
166
|
totalInfos: number
|
|
167
|
+
/** Config-level diagnostics (malformed rule options, etc.). */
|
|
168
|
+
configDiagnostics: ConfigDiagnostic[]
|
|
104
169
|
}
|
|
105
170
|
|
|
106
171
|
// ── Lint Options ────────────────────────────────────────────────────────────
|
|
@@ -111,6 +176,12 @@ export interface LintOptions {
|
|
|
111
176
|
fix?: boolean | undefined
|
|
112
177
|
quiet?: boolean | undefined
|
|
113
178
|
ruleOverrides?: Record<string, Severity> | undefined
|
|
179
|
+
/**
|
|
180
|
+
* Per-rule options overrides — typically populated from the
|
|
181
|
+
* `--rule-options id='{json}'` CLI flag. Merged on top of any
|
|
182
|
+
* options coming from the config file's tuple form.
|
|
183
|
+
*/
|
|
184
|
+
ruleOptionsOverrides?: Record<string, RuleOptions> | undefined
|
|
114
185
|
config?: string | undefined
|
|
115
186
|
ignore?: string | undefined
|
|
116
187
|
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component / hook scope tracker for lint rules.
|
|
3
|
+
*
|
|
4
|
+
* Many rules in this package only matter *inside* a component or hook —
|
|
5
|
+
* e.g. `no-raw-setinterval` wants you to wrap timers in `onMount` so they
|
|
6
|
+
* are cleaned up when the component unmounts. A `setInterval` at module
|
|
7
|
+
* scope, in a utility function, or inside a test callback has its own
|
|
8
|
+
* lifecycle and doesn't need component-tied cleanup.
|
|
9
|
+
*
|
|
10
|
+
* Previously these rules used a path-string `isTestFile()` skip as a
|
|
11
|
+
* proxy for "outside component context". That's a heuristic — it
|
|
12
|
+
* accidentally exempts test files where the pattern *is* still wrong,
|
|
13
|
+
* and it accidentally fires on utility/library files where the pattern
|
|
14
|
+
* is fine.
|
|
15
|
+
*
|
|
16
|
+
* This tracker maintains a "component depth" counter via visitor
|
|
17
|
+
* callbacks. A function counts as a component or hook when its name
|
|
18
|
+
* follows the framework's naming conventions:
|
|
19
|
+
* - `MyThing` (PascalCase) → component
|
|
20
|
+
* - `useThing` (camelCase, `use` prefix + uppercase next char) → hook
|
|
21
|
+
*
|
|
22
|
+
* Rules consume it via `createComponentContextTracker()` and merge the
|
|
23
|
+
* returned `callbacks` into their visitor:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* create(context) {
|
|
27
|
+
* const ctx = createComponentContextTracker()
|
|
28
|
+
* return {
|
|
29
|
+
* ...ctx.callbacks,
|
|
30
|
+
* CallExpression(node) {
|
|
31
|
+
* if (!ctx.isInComponentOrHook()) return
|
|
32
|
+
* // ... existing rule logic ...
|
|
33
|
+
* },
|
|
34
|
+
* }
|
|
35
|
+
* }
|
|
36
|
+
* ```
|
|
37
|
+
*
|
|
38
|
+
* The tracker also recognizes named arrow / function expressions assigned
|
|
39
|
+
* to `const X = (props) => …`, since the framework treats those as
|
|
40
|
+
* components too. Anonymous callbacks (e.g. `it('...', () => { … })`,
|
|
41
|
+
* `setTimeout(() => { … }, 0)`, `arr.map(x => x)`) never push depth — so
|
|
42
|
+
* test bodies, IIFEs, and inline callbacks are correctly seen as
|
|
43
|
+
* "outside any component" without needing a path-based skip.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import type { VisitorCallbacks } from '../types'
|
|
47
|
+
|
|
48
|
+
const COMPONENT_NAME = /^[A-Z]/
|
|
49
|
+
const HOOK_NAME = /^use[A-Z]/
|
|
50
|
+
|
|
51
|
+
export function isComponentOrHookName(name: string | null | undefined): boolean {
|
|
52
|
+
if (!name) return false
|
|
53
|
+
return COMPONENT_NAME.test(name) || HOOK_NAME.test(name)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface ComponentContextTracker {
|
|
57
|
+
/** True iff the current AST position is inside a function recognised as a component or hook. */
|
|
58
|
+
isInComponentOrHook(): boolean
|
|
59
|
+
/**
|
|
60
|
+
* Visitor callbacks that maintain the depth counter. Spread them into the
|
|
61
|
+
* rule's returned visitor first; per-node listeners after override only
|
|
62
|
+
* the keys the rule itself implements (FunctionDeclaration etc. rarely).
|
|
63
|
+
*/
|
|
64
|
+
callbacks: VisitorCallbacks
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function createComponentContextTracker(): ComponentContextTracker {
|
|
68
|
+
let depth = 0
|
|
69
|
+
|
|
70
|
+
// For arrow / function expressions assigned to a `const X = (...) => …`,
|
|
71
|
+
// we can't read the binding name from the function node — and the oxc
|
|
72
|
+
// visitor doesn't pass `parent` to callbacks. Instead, hook the parent
|
|
73
|
+
// `VariableDeclarator` enter/exit: it visits BEFORE its `init` child
|
|
74
|
+
// (the function expression) and EXITS AFTER, so a depth bump tied to
|
|
75
|
+
// the declarator correctly brackets the function body.
|
|
76
|
+
function declaratorIsComponentOrHook(node: any): boolean {
|
|
77
|
+
if (node?.id?.type !== 'Identifier') return false
|
|
78
|
+
const init = node.init
|
|
79
|
+
if (
|
|
80
|
+
init?.type !== 'ArrowFunctionExpression' &&
|
|
81
|
+
init?.type !== 'FunctionExpression'
|
|
82
|
+
)
|
|
83
|
+
return false
|
|
84
|
+
return isComponentOrHookName(node.id.name)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
isInComponentOrHook: () => depth > 0,
|
|
89
|
+
callbacks: {
|
|
90
|
+
// function MyComp() {} / function useFoo() {}
|
|
91
|
+
FunctionDeclaration(node: any) {
|
|
92
|
+
if (isComponentOrHookName(node.id?.name)) depth++
|
|
93
|
+
},
|
|
94
|
+
'FunctionDeclaration:exit'(node: any) {
|
|
95
|
+
if (isComponentOrHookName(node.id?.name)) depth--
|
|
96
|
+
},
|
|
97
|
+
// const MyComp = () => {} / const useFoo = function () {}
|
|
98
|
+
VariableDeclarator(node: any) {
|
|
99
|
+
if (declaratorIsComponentOrHook(node)) depth++
|
|
100
|
+
},
|
|
101
|
+
'VariableDeclarator:exit'(node: any) {
|
|
102
|
+
if (declaratorIsComponentOrHook(node)) depth--
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper for rules that support path-based exemption via options.
|
|
3
|
+
*
|
|
4
|
+
* Rules that need to be "turn-off-able for specific directories" (e.g.
|
|
5
|
+
* a package that IS the foundation the rule recommends against using
|
|
6
|
+
* directly) don't hardcode the paths anymore — they read an
|
|
7
|
+
* `exemptPaths: string[]` option from the user's config:
|
|
8
|
+
*
|
|
9
|
+
* ```json
|
|
10
|
+
* // .pyreonlintrc.json
|
|
11
|
+
* {
|
|
12
|
+
* "rules": {
|
|
13
|
+
* "pyreon/no-window-in-ssr": [
|
|
14
|
+
* "error",
|
|
15
|
+
* { "exemptPaths": ["packages/core/runtime-dom/"] }
|
|
16
|
+
* ]
|
|
17
|
+
* }
|
|
18
|
+
* }
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Each entry is substring-matched against the file path (same convention
|
|
22
|
+
* the old hardcoded patterns used). Empty / missing → no exemptions,
|
|
23
|
+
* which is the correct default for a rule shipping to user apps.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { RuleContext } from '../types'
|
|
27
|
+
|
|
28
|
+
export function isPathExempt(ctx: RuleContext): boolean {
|
|
29
|
+
const options = ctx.getOptions()
|
|
30
|
+
const raw = options.exemptPaths
|
|
31
|
+
if (!Array.isArray(raw) || raw.length === 0) return false
|
|
32
|
+
const filePath = ctx.getFilePath()
|
|
33
|
+
for (const entry of raw) {
|
|
34
|
+
if (typeof entry === 'string' && entry.length > 0 && filePath.includes(entry)) {
|
|
35
|
+
return true
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return false
|
|
39
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal file-path classifiers for lint rules.
|
|
3
|
+
*
|
|
4
|
+
* What belongs here:
|
|
5
|
+
* - Conventions that exist in every project the linter runs on
|
|
6
|
+
* (test files, example directories — the `*.test.*` convention
|
|
7
|
+
* is not Pyreon-specific).
|
|
8
|
+
*
|
|
9
|
+
* What does NOT belong here:
|
|
10
|
+
* - Monorepo-specific paths like `packages/core/runtime-dom/` —
|
|
11
|
+
* those are implementation knowledge of one particular codebase
|
|
12
|
+
* and have no meaning in a user's app. Exemptions for such paths
|
|
13
|
+
* belong in the consuming project's lint config via the
|
|
14
|
+
* `exemptPaths: string[]` rule option — see `utils/exempt-paths.ts`
|
|
15
|
+
* and the Pyreon monorepo's `.pyreonlintrc.json` at repo root for
|
|
16
|
+
* reference.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Matches files that are tests by convention. Universal — the
|
|
21
|
+
* `*.test.*` / `*.spec.*` / `/tests/` / `/__tests__/` conventions
|
|
22
|
+
* exist in every codebase this linter runs on, not just Pyreon.
|
|
23
|
+
*/
|
|
24
|
+
export function isTestFile(filePath: string): boolean {
|
|
25
|
+
return (
|
|
26
|
+
filePath.includes('/tests/') ||
|
|
27
|
+
filePath.includes('/test/') ||
|
|
28
|
+
filePath.includes('/__tests__/') ||
|
|
29
|
+
filePath.includes('.test.') ||
|
|
30
|
+
filePath.includes('.spec.')
|
|
31
|
+
)
|
|
32
|
+
}
|
package/src/utils/imports.ts
CHANGED
|
@@ -48,7 +48,10 @@ export const BROWSER_GLOBALS = new Set([
|
|
|
48
48
|
'localStorage',
|
|
49
49
|
'sessionStorage',
|
|
50
50
|
'indexedDB',
|
|
51
|
-
|
|
51
|
+
// NOTE: `fetch` is intentionally OMITTED — it's a universal global in
|
|
52
|
+
// Node 18+, Bun, Deno, browsers, and edge runtimes. Code using it isn't
|
|
53
|
+
// browser-specific. (`XMLHttpRequest`/`WebSocket` are still here because
|
|
54
|
+
// they're DOM-only and Node code uses different libraries.)
|
|
52
55
|
'XMLHttpRequest',
|
|
53
56
|
'WebSocket',
|
|
54
57
|
'requestAnimationFrame',
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate a rule's user-configured options against its declared schema.
|
|
3
|
+
*
|
|
4
|
+
* Called once per (rule, options) pair at config-merge time — NOT per
|
|
5
|
+
* lint'd file. Separates config problems from source-code problems:
|
|
6
|
+
* wrong-typed options aren't a file diagnostic, they're a setup error
|
|
7
|
+
* for the tool.
|
|
8
|
+
*
|
|
9
|
+
* Return shape:
|
|
10
|
+
* - `errors`: hard failures. Runner disables the rule for this run.
|
|
11
|
+
* - `warnings`: unknown option keys, typos, etc. Runner keeps the
|
|
12
|
+
* rule enabled but prints the warning so the user knows.
|
|
13
|
+
*
|
|
14
|
+
* Rules without `meta.schema` accept any options (no validation).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { OptionType, Rule, RuleOptions, RuleOptionsSchema } from '../types'
|
|
18
|
+
|
|
19
|
+
export interface ValidationResult {
|
|
20
|
+
errors: string[]
|
|
21
|
+
warnings: string[]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function validateRuleOptions(rule: Rule, options: RuleOptions): ValidationResult {
|
|
25
|
+
const schema = rule.meta.schema
|
|
26
|
+
const errors: string[] = []
|
|
27
|
+
const warnings: string[] = []
|
|
28
|
+
if (!schema) return { errors, warnings }
|
|
29
|
+
|
|
30
|
+
for (const [key, value] of Object.entries(options)) {
|
|
31
|
+
const expected = schema[key]
|
|
32
|
+
if (expected === undefined) {
|
|
33
|
+
warnings.push(
|
|
34
|
+
`[${rule.meta.id}] unknown option "${key}" — allowed options: ${Object.keys(schema).join(', ') || '(none)'}`,
|
|
35
|
+
)
|
|
36
|
+
continue
|
|
37
|
+
}
|
|
38
|
+
if (!matchesType(value, expected)) {
|
|
39
|
+
errors.push(
|
|
40
|
+
`[${rule.meta.id}] option "${key}" must be ${expected}, got ${describe(value)}`,
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { errors, warnings }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function matchesType(value: unknown, type: OptionType): boolean {
|
|
49
|
+
switch (type) {
|
|
50
|
+
case 'string':
|
|
51
|
+
return typeof value === 'string'
|
|
52
|
+
case 'string[]':
|
|
53
|
+
return Array.isArray(value) && value.every((x) => typeof x === 'string')
|
|
54
|
+
case 'number':
|
|
55
|
+
return typeof value === 'number' && Number.isFinite(value)
|
|
56
|
+
case 'boolean':
|
|
57
|
+
return typeof value === 'boolean'
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function describe(value: unknown): string {
|
|
62
|
+
if (value === null) return 'null'
|
|
63
|
+
if (Array.isArray(value)) {
|
|
64
|
+
const types = new Set(value.map((x) => (x === null ? 'null' : typeof x)))
|
|
65
|
+
return `Array<${[...types].join(' | ') || 'empty'}>`
|
|
66
|
+
}
|
|
67
|
+
return typeof value
|
|
68
|
+
}
|
package/src/watcher.ts
CHANGED
|
@@ -34,6 +34,7 @@ export function watchAndLint(options: LintOptions & { format: string }): void {
|
|
|
34
34
|
const config = getPreset(preset)
|
|
35
35
|
|
|
36
36
|
applyOverrides(config, options.ruleOverrides)
|
|
37
|
+
applyOptionsOverrides(config, options.ruleOptionsOverrides)
|
|
37
38
|
|
|
38
39
|
const cwd = resolve('.')
|
|
39
40
|
const isIgnored = createIgnoreFilter(cwd, options.ignore)
|
|
@@ -81,6 +82,21 @@ function applyOverrides(
|
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
|
|
85
|
+
function applyOptionsOverrides(
|
|
86
|
+
config: LintConfig,
|
|
87
|
+
overrides?: Record<string, Record<string, unknown>> | undefined,
|
|
88
|
+
): void {
|
|
89
|
+
if (!overrides) return
|
|
90
|
+
for (const [id, opts] of Object.entries(overrides)) {
|
|
91
|
+
const existing = config.rules[id]
|
|
92
|
+
const [severity, current]: [Severity, Record<string, unknown>] = Array.isArray(existing)
|
|
93
|
+
? [existing[0] as Severity, (existing[1] ?? {}) as Record<string, unknown>]
|
|
94
|
+
: [(existing ?? 'off') as Severity, {}]
|
|
95
|
+
if (severity === 'off') continue
|
|
96
|
+
config.rules[id] = [severity, { ...current, ...opts }] as const
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
84
100
|
function relintFile(filePath: string, config: LintConfig, cache: AstCache, format: string): void {
|
|
85
101
|
let source: string
|
|
86
102
|
try {
|
|
@@ -98,6 +114,7 @@ function relintFile(filePath: string, config: LintConfig, cache: AstCache, forma
|
|
|
98
114
|
totalErrors: 0,
|
|
99
115
|
totalWarnings: 0,
|
|
100
116
|
totalInfos: 0,
|
|
117
|
+
configDiagnostics: [],
|
|
101
118
|
}
|
|
102
119
|
|
|
103
120
|
for (const d of fileResult.diagnostics) {
|