@gost-kit/eslint-plugin 0.1.0 → 0.2.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/CHANGELOG.md +9 -0
- package/README.md +10 -3
- package/RULES.md +63 -0
- package/dist/index.js +2 -0
- package/dist/lib/boolean-type.d.ts +2 -0
- package/dist/lib/boolean-type.js +14 -0
- package/dist/lib/member-key.d.ts +3 -1
- package/dist/lib/member-key.js +10 -5
- package/dist/lib/ready-boolean-expression.d.ts +2 -0
- package/dist/lib/ready-boolean-expression.js +17 -0
- package/dist/rules/ready-boolean-condition.d.ts +2 -0
- package/dist/rules/ready-boolean-condition.js +49 -0
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @gost-kit/eslint-plugin
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9f59da3: Add the opt-in ready-boolean-condition rule for Svelte if/else-if conditions.
|
|
8
|
+
Accept ready boolean reads and literals, report computations/truthiness, and
|
|
9
|
+
require typed parser services with strictNullChecks for reads. The Svelte config
|
|
10
|
+
registers the rule off; projects select business/UI-Kit scopes explicitly.
|
|
11
|
+
|
|
3
12
|
## 0.1.0
|
|
4
13
|
|
|
5
14
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -8,7 +8,8 @@ ESLint ^10.14.0 and TypeScript ^5.9.3.
|
|
|
8
8
|
|
|
9
9
|
`@gost-kit/eslint-config-svelte` installs and enables the plugin for typed Svelte
|
|
10
10
|
components and `.svelte.ts` modules. The selector rule is enabled by default;
|
|
11
|
-
`gost/no-component-state
|
|
11
|
+
`gost/no-component-state`, `gost/single-intent-dispatch` and
|
|
12
|
+
`gost/ready-boolean-condition` require explicit
|
|
12
13
|
consumer overrides for business component paths. UI-Kit paths can opt out.
|
|
13
14
|
No separate plugin registration is needed.
|
|
14
15
|
Other consumers may register the named public export in their flat config:
|
|
@@ -32,8 +33,8 @@ Nested common installations are supported; a direct common dependency of the
|
|
|
32
33
|
application is not required for inferred Store detection.
|
|
33
34
|
Local `$state.raw` bindings are recognized from their original lexical scope.
|
|
34
35
|
For `require-feature-state-selector`, type information is required; an enabled typed rule without parser services
|
|
35
|
-
reports a configuration error.
|
|
36
|
-
skipped. Mixed components are checked per script: a typed module script does
|
|
36
|
+
reports a configuration error. For the selector rule, stateless and JavaScript-script
|
|
37
|
+
components are skipped. Mixed components are checked per script: a typed module script does
|
|
37
38
|
not enable the rule for a JavaScript instance script. The Svelte config does not enable this typed rule for `.svelte.js`.
|
|
38
39
|
|
|
39
40
|
`single-intent-dispatch` uses an `on*` name heuristic, including function-valued
|
|
@@ -44,3 +45,9 @@ ordinary optional data reads remain outside that gate check.
|
|
|
44
45
|
|
|
45
46
|
See [RULES.md](RULES.md) for diagnostics, autofix boundaries and examples.
|
|
46
47
|
Tests and test helpers are excluded from published artifacts.
|
|
48
|
+
|
|
49
|
+
`ready-boolean-condition` checks Svelte if/else-if conditions: true/false or
|
|
50
|
+
ready boolean identifier/static field reads. Computations and truthiness are
|
|
51
|
+
reported; typed reads require strictNullChecks and parser services. JS/stateless
|
|
52
|
+
reads are checked too when services are available. No autofix or presenter
|
|
53
|
+
provenance guarantee is provided. See the inventory for opt-in boundaries.
|
package/RULES.md
CHANGED
|
@@ -247,3 +247,66 @@ a dispatch diagnostic, although it violates the business-component boundary.
|
|
|
247
247
|
Event adaptation belongs in UI-Kit. Effects outside discovered UI callbacks may
|
|
248
248
|
perform several dispatches. Presentation computations (including templates) are
|
|
249
249
|
#19, boolean template conditions #18, and testable local UI-state #27.
|
|
250
|
+
|
|
251
|
+
## gost/ready-boolean-condition
|
|
252
|
+
|
|
253
|
+
Opt in for business `.svelte` files. `{#if}` and `{:else if}` read a ready
|
|
254
|
+
boolean; ordinary else and conditional rendering remain available.
|
|
255
|
+
|
|
256
|
+
```svelte
|
|
257
|
+
{#if view.isLoaded}
|
|
258
|
+
<p>{view.name}</p>
|
|
259
|
+
{:else if isFailed}
|
|
260
|
+
<p>Failed</p>
|
|
261
|
+
{/if}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Allowed: boolean literals true/false, identifiers and static field chains rooted
|
|
265
|
+
at an identifier (for example view.ready), including quoted keys and number-valued
|
|
266
|
+
AST literal indexes such as flags[0] or flags[0x10]. The final type must be
|
|
267
|
+
definitely boolean. this.isReady is outside the supported root form. Signed keys
|
|
268
|
+
such as flags[-1] are unary expressions, and bigint keys such as flags[10n] are
|
|
269
|
+
not number-valued literals; both are outside this first-version syntax boundary.
|
|
270
|
+
Optional chains are accepted only when the complete expression excludes
|
|
271
|
+
undefined/null. Boolean aliases, boolean-only unions and proven boolean generic
|
|
272
|
+
constraints are accepted. TypeScript flow narrowing applies at the expression.
|
|
273
|
+
|
|
274
|
+
Reported with `readyBoolean`: comparisons, logical operations, negation, calls,
|
|
275
|
+
ternaries, dynamic keys, assertions/non-null assertions/satisfies wrappers,
|
|
276
|
+
object/string/number truthiness, nullable booleans and any/unknown/never. Move
|
|
277
|
+
the calculation to a pure presenter and read its named boolean result. There
|
|
278
|
+
is no autofix or suggestion that invents a presenter/field name.
|
|
279
|
+
|
|
280
|
+
Identifier/member reads require actual parser type services and strictNullChecks,
|
|
281
|
+
either directly or via strict. Explicit strictNullChecks: false overrides strict.
|
|
282
|
+
Missing services/null checking is a configuration error, not a successful check.
|
|
283
|
+
Literal booleans and invalid syntax do not need services; components without
|
|
284
|
+
conditions do not request them. Type services also cover JS-script and stateless
|
|
285
|
+
components when the configured parser supplies a program. Inferred any in JS
|
|
286
|
+
props is rejected just like TypeScript any. Script if statements, ordinary TS and
|
|
287
|
+
rune modules are outside the rule.
|
|
288
|
+
|
|
289
|
+
This checks shape and type, not provenance/purity. A local boolean or boolean
|
|
290
|
+
getter is not proof of a presenter. Rule #19 addresses presentation calculations
|
|
291
|
+
outside template conditions. Static indexed reads follow consumer TypeScript
|
|
292
|
+
options; enable noUncheckedIndexedAccess to expose possible missing entries.
|
|
293
|
+
The rule does not validate runtime input that the declared types conceal.
|
|
294
|
+
|
|
295
|
+
`createSvelteConfig` registers it explicitly off. Enable with a later override:
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
{
|
|
299
|
+
files: ['src/**/*.svelte'],
|
|
300
|
+
rules: { 'gost/ready-boolean-condition': 'error' },
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Independent UI-Kit components may use a later matching off override while
|
|
305
|
+
retaining the other TypeScript/Svelte checks.
|
|
306
|
+
|
|
307
|
+
The current svelte-eslint-parser 1.10.1 models component generics as aliases to
|
|
308
|
+
their constraints (unknown when unconstrained). Conditional types over those
|
|
309
|
+
component generics may therefore resolve differently from a particular caller's
|
|
310
|
+
instantiation. This rule checks the parser-provided program, not every generic
|
|
311
|
+
instantiation; use an explicitly boolean view field for that boundary. It cannot
|
|
312
|
+
recover source type distinctions already erased by the parser.
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { readyBooleanCondition } from './rules/ready-boolean-condition.js';
|
|
1
2
|
import { singleIntentDispatch } from './rules/single-intent-dispatch.js';
|
|
2
3
|
import { noComponentState } from './rules/no-component-state.js';
|
|
3
4
|
import { requireFeatureStateSelector } from './rules/require-feature-state-selector.js';
|
|
@@ -6,6 +7,7 @@ export const plugin = {
|
|
|
6
7
|
// typescript-eslint still types the legacy context members removed in ESLint 10.
|
|
7
8
|
// At this public boundary the implementation uses only the current APIs.
|
|
8
9
|
rules: {
|
|
10
|
+
'ready-boolean-condition': readyBooleanCondition,
|
|
9
11
|
'single-intent-dispatch': singleIntentDispatch,
|
|
10
12
|
'no-component-state': noComponentState,
|
|
11
13
|
'require-feature-state-selector': requireFeatureStateSelector,
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import ts from 'typescript';
|
|
2
|
+
export const booleanType = (checker, type) => {
|
|
3
|
+
if (type.flags &
|
|
4
|
+
(ts.TypeFlags.Any | ts.TypeFlags.Unknown | ts.TypeFlags.Never)) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
if (type.flags & ts.TypeFlags.TypeParameter) {
|
|
8
|
+
const constraint = checker.getBaseConstraintOfType(type);
|
|
9
|
+
return (constraint !== undefined &&
|
|
10
|
+
constraint !== type &&
|
|
11
|
+
booleanType(checker, constraint));
|
|
12
|
+
}
|
|
13
|
+
return checker.isTypeAssignableTo(type, checker.getBooleanType());
|
|
14
|
+
};
|
package/dist/lib/member-key.d.ts
CHANGED
|
@@ -1,2 +1,4 @@
|
|
|
1
1
|
import { type TSESTree } from '@typescript-eslint/utils';
|
|
2
|
-
export declare const memberKey: (node: TSESTree.MemberExpression
|
|
2
|
+
export declare const memberKey: (node: TSESTree.MemberExpression, { numeric }?: {
|
|
3
|
+
numeric?: boolean;
|
|
4
|
+
}) => string | undefined;
|
package/dist/lib/member-key.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
2
|
-
export const memberKey = (node) => {
|
|
2
|
+
export const memberKey = (node, { numeric = false } = {}) => {
|
|
3
3
|
if (!node.computed && node.property.type === AST_NODE_TYPES.Identifier) {
|
|
4
4
|
return node.property.name;
|
|
5
5
|
}
|
|
6
6
|
if (node.computed && node.property.type === AST_NODE_TYPES.Literal) {
|
|
7
|
-
|
|
8
|
-
node.property.raw.startsWith('"')
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
if (node.property.raw.startsWith("'") ||
|
|
8
|
+
node.property.raw.startsWith('"')) {
|
|
9
|
+
return String(node.property.value);
|
|
10
|
+
}
|
|
11
|
+
// Match numeric AST literals, including values such as 1e999; finiteness
|
|
12
|
+
// would change syntax classification. Existing callers remain names-only.
|
|
13
|
+
if (numeric && typeof node.property.value === 'number') {
|
|
14
|
+
return String(node.property.value);
|
|
15
|
+
}
|
|
11
16
|
}
|
|
12
17
|
return undefined;
|
|
13
18
|
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { AST_NODE_TYPES } from '@typescript-eslint/utils';
|
|
2
|
+
import { memberKey } from './member-key.js';
|
|
3
|
+
export const readyBooleanExpression = (node) => {
|
|
4
|
+
if (node.type === AST_NODE_TYPES.Identifier) {
|
|
5
|
+
return true;
|
|
6
|
+
}
|
|
7
|
+
if (node.type === AST_NODE_TYPES.ChainExpression) {
|
|
8
|
+
return readyBooleanExpression(node.expression);
|
|
9
|
+
}
|
|
10
|
+
if (node.type !== AST_NODE_TYPES.MemberExpression) {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const staticKey = memberKey(node, { numeric: true }) !== undefined;
|
|
14
|
+
return (staticKey &&
|
|
15
|
+
node.object.type !== AST_NODE_TYPES.Super &&
|
|
16
|
+
readyBooleanExpression(node.object));
|
|
17
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { AST_NODE_TYPES, ESLintUtils, } from '@typescript-eslint/utils';
|
|
2
|
+
import { readyBooleanExpression } from '../lib/ready-boolean-expression.js';
|
|
3
|
+
import { booleanType } from '../lib/boolean-type.js';
|
|
4
|
+
export const readyBooleanCondition = ESLintUtils.RuleCreator.withoutDocs({
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'suggestion',
|
|
7
|
+
docs: {
|
|
8
|
+
description: 'Require ready boolean reads in Svelte if/else-if conditions.',
|
|
9
|
+
},
|
|
10
|
+
schema: [],
|
|
11
|
+
messages: {
|
|
12
|
+
readyBoolean: 'Prepare a named boolean in a presenter and read it here instead of computing a condition or using truthiness.',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
defaultOptions: [],
|
|
16
|
+
create(context) {
|
|
17
|
+
if (!context.filename.endsWith('.svelte')) {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
let checkType;
|
|
21
|
+
return {
|
|
22
|
+
SvelteIfBlock(node) {
|
|
23
|
+
const expression = node.expression;
|
|
24
|
+
if (expression.type === AST_NODE_TYPES.Literal &&
|
|
25
|
+
(expression.value === true || expression.value === false)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (!readyBooleanExpression(expression)) {
|
|
29
|
+
context.report({ node: expression, messageId: 'readyBoolean' });
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!checkType) {
|
|
33
|
+
const services = ESLintUtils.getParserServices(context);
|
|
34
|
+
const options = services.program.getCompilerOptions();
|
|
35
|
+
if (!(options.strictNullChecks ?? options.strict ?? false)) {
|
|
36
|
+
throw new Error('gost/ready-boolean-condition requires strictNullChecks (or strict) for typed conditions.');
|
|
37
|
+
}
|
|
38
|
+
const checker = services.program.getTypeChecker();
|
|
39
|
+
checkType = (condition) => {
|
|
40
|
+
return booleanType(checker, services.getTypeAtLocation(condition));
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
if (!checkType(expression)) {
|
|
44
|
+
context.report({ node: expression, messageId: 'readyBoolean' });
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gost-kit/eslint-plugin",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "GOST architecture rules for agent-assisted frontend development.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,8 +43,8 @@
|
|
|
43
43
|
"svelte-eslint-parser": "1.10.1",
|
|
44
44
|
"typescript-eslint": "8.72.1",
|
|
45
45
|
"svelte": "5.57.2",
|
|
46
|
-
"@gost-kit/common": "0.
|
|
47
|
-
"@gost-kit/svelte": "0.
|
|
46
|
+
"@gost-kit/common": "0.3.0",
|
|
47
|
+
"@gost-kit/svelte": "0.2.0",
|
|
48
48
|
"@typescript-eslint/rule-tester": "8.72.1"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|