oxlint-plugin-effect 0.1.0 → 0.2.1
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/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/plugin.d.ts +5 -0
- package/dist/plugin.js +18 -0
- package/dist/presets/core.d.ts +43 -0
- package/dist/presets/core.js +43 -0
- package/dist/presets/effect-native.d.ts +28 -0
- package/dist/presets/effect-native.js +28 -0
- package/dist/presets/full.d.ts +60 -0
- package/dist/presets/full.js +60 -0
- package/dist/presets/functional.d.ts +21 -0
- package/dist/presets/functional.js +21 -0
- package/dist/presets/index.d.ts +13 -0
- package/dist/presets/index.js +18 -0
- package/dist/presets/strict.d.ts +69 -0
- package/dist/presets/strict.js +69 -0
- package/dist/presets/v3.d.ts +15 -0
- package/dist/presets/v3.js +15 -0
- package/dist/rules/_effect-context.d.ts +19 -0
- package/dist/rules/_effect-context.js +22 -0
- package/dist/rules/index.d.ts +66 -0
- package/dist/rules/index.js +124 -0
- package/dist/rules/no-arrow-ladder.d.ts +6 -0
- package/dist/rules/no-arrow-ladder.js +61 -0
- package/dist/rules/no-async-function.d.ts +6 -0
- package/dist/rules/no-async-function.js +46 -0
- package/dist/rules/no-catch-all-to-map-error.d.ts +6 -0
- package/dist/rules/no-catch-all-to-map-error.js +67 -0
- package/dist/rules/no-effect-as.d.ts +6 -0
- package/dist/rules/no-effect-as.js +16 -0
- package/dist/rules/no-effect-async.d.ts +6 -0
- package/dist/rules/no-effect-async.js +16 -0
- package/dist/rules/no-effect-bind.d.ts +6 -0
- package/dist/rules/no-effect-bind.js +16 -0
- package/dist/rules/no-effect-do.d.ts +6 -0
- package/dist/rules/no-effect-do.js +16 -0
- package/dist/rules/no-effect-fn-generator.d.ts +6 -0
- package/dist/rules/no-effect-fn-generator.js +30 -0
- package/dist/rules/no-effect-gen-adapter.d.ts +6 -0
- package/dist/rules/no-effect-gen-adapter.js +30 -0
- package/dist/rules/no-effect-map-flatten.d.ts +6 -0
- package/dist/rules/no-effect-map-flatten.js +53 -0
- package/dist/rules/no-effect-map-void.d.ts +6 -0
- package/dist/rules/no-effect-map-void.js +40 -0
- package/dist/rules/no-effect-never.d.ts +6 -0
- package/dist/rules/no-effect-never.js +16 -0
- package/dist/rules/no-effect-orElse-ladder.d.ts +6 -0
- package/dist/rules/no-effect-orElse-ladder.js +53 -0
- package/dist/rules/no-effect-succeed-string.d.ts +6 -0
- package/dist/rules/no-effect-succeed-string.js +30 -0
- package/dist/rules/no-effect-succeed-variable.d.ts +6 -0
- package/dist/rules/no-effect-succeed-variable.js +30 -0
- package/dist/rules/no-effect-succeed-void.d.ts +6 -0
- package/dist/rules/no-effect-succeed-void.js +33 -0
- package/dist/rules/no-effect-sync-console.d.ts +6 -0
- package/dist/rules/no-effect-sync-console.js +63 -0
- package/dist/rules/no-effect-sync-wrapper.d.ts +6 -0
- package/dist/rules/no-effect-sync-wrapper.js +37 -0
- package/dist/rules/no-effect-type-alias.d.ts +6 -0
- package/dist/rules/no-effect-type-alias.js +52 -0
- package/dist/rules/no-effect-wrapper-alias.d.ts +6 -0
- package/dist/rules/no-effect-wrapper-alias.js +69 -0
- package/dist/rules/no-extends-native-error.d.ts +6 -0
- package/dist/rules/no-extends-native-error.js +41 -0
- package/dist/rules/no-flatmap-ladder.d.ts +6 -0
- package/dist/rules/no-flatmap-ladder.js +48 -0
- package/dist/rules/no-fromnullable-coalesce.d.ts +6 -0
- package/dist/rules/no-fromnullable-coalesce.js +36 -0
- package/dist/rules/no-global-error-in-catch.d.ts +6 -0
- package/dist/rules/no-global-error-in-catch.js +75 -0
- package/dist/rules/no-global-error-in-failure.d.ts +6 -0
- package/dist/rules/no-global-error-in-failure.js +54 -0
- package/dist/rules/no-globals.d.ts +6 -0
- package/dist/rules/no-globals.js +164 -0
- package/dist/rules/no-if-statement.d.ts +6 -0
- package/dist/rules/no-if-statement.js +18 -0
- package/dist/rules/no-iife-wrapper.d.ts +6 -0
- package/dist/rules/no-iife-wrapper.js +37 -0
- package/dist/rules/no-inline-provide.d.ts +6 -0
- package/dist/rules/no-inline-provide.js +31 -0
- package/dist/rules/no-instanceof-schema.d.ts +6 -0
- package/dist/rules/no-instanceof-schema.js +35 -0
- package/dist/rules/no-manual-effect-channels.d.ts +6 -0
- package/dist/rules/no-manual-effect-channels.js +40 -0
- package/dist/rules/no-match-effect-branch.d.ts +6 -0
- package/dist/rules/no-match-effect-branch.js +64 -0
- package/dist/rules/no-match-void-branch.d.ts +6 -0
- package/dist/rules/no-match-void-branch.js +50 -0
- package/dist/rules/no-multiple-effect-provide.d.ts +6 -0
- package/dist/rules/no-multiple-effect-provide.js +45 -0
- package/dist/rules/no-nested-effect-call.d.ts +6 -0
- package/dist/rules/no-nested-effect-call.js +97 -0
- package/dist/rules/no-nested-effect-gen.d.ts +6 -0
- package/dist/rules/no-nested-effect-gen.js +46 -0
- package/dist/rules/no-nested-pipe.d.ts +6 -0
- package/dist/rules/no-nested-pipe.js +38 -0
- package/dist/rules/no-new-error.d.ts +6 -0
- package/dist/rules/no-new-error.js +39 -0
- package/dist/rules/no-new-promise.d.ts +6 -0
- package/dist/rules/no-new-promise.js +38 -0
- package/dist/rules/no-node-builtin-import.d.ts +6 -0
- package/dist/rules/no-node-builtin-import.js +61 -0
- package/dist/rules/no-option-as.d.ts +6 -0
- package/dist/rules/no-option-as.js +16 -0
- package/dist/rules/no-option-boolean-normalization.d.ts +6 -0
- package/dist/rules/no-option-boolean-normalization.js +50 -0
- package/dist/rules/no-positional-log-error.d.ts +6 -0
- package/dist/rules/no-positional-log-error.js +52 -0
- package/dist/rules/no-redundant-schema-tag-identifier.d.ts +6 -0
- package/dist/rules/no-redundant-schema-tag-identifier.js +46 -0
- package/dist/rules/no-return-in-arrow.d.ts +6 -0
- package/dist/rules/no-return-in-arrow.js +41 -0
- package/dist/rules/no-return-nullish.d.ts +6 -0
- package/dist/rules/no-return-nullish.js +47 -0
- package/dist/rules/no-run-in-effect-gen.d.ts +6 -0
- package/dist/rules/no-run-in-effect-gen.js +48 -0
- package/dist/rules/no-runtime-run-fork.d.ts +6 -0
- package/dist/rules/no-runtime-run-fork.js +19 -0
- package/dist/rules/no-schema-struct-with-tag.d.ts +6 -0
- package/dist/rules/no-schema-struct-with-tag.js +48 -0
- package/dist/rules/no-schema-union-of-literals.d.ts +6 -0
- package/dist/rules/no-schema-union-of-literals.js +35 -0
- package/dist/rules/no-string-sentinel-const.d.ts +6 -0
- package/dist/rules/no-string-sentinel-const.js +38 -0
- package/dist/rules/no-switch-statement.d.ts +6 -0
- package/dist/rules/no-switch-statement.js +16 -0
- package/dist/rules/no-ternary.d.ts +6 -0
- package/dist/rules/no-ternary.js +30 -0
- package/dist/rules/no-throw-statement.d.ts +6 -0
- package/dist/rules/no-throw-statement.js +34 -0
- package/dist/rules/no-try-catch.d.ts +6 -0
- package/dist/rules/no-try-catch.js +34 -0
- package/dist/rules/no-unnecessary-arrow-block.d.ts +6 -0
- package/dist/rules/no-unnecessary-arrow-block.js +34 -0
- package/dist/rules/no-unnecessary-effect-gen.d.ts +6 -0
- package/dist/rules/no-unnecessary-effect-gen.js +35 -0
- package/dist/rules/no-unnecessary-pipe-chain.d.ts +6 -0
- package/dist/rules/no-unnecessary-pipe-chain.js +36 -0
- package/dist/rules/no-unnecessary-pipe.d.ts +6 -0
- package/dist/rules/no-unnecessary-pipe.js +29 -0
- package/dist/vendor/effect-oxlint/AST.d.ts +199 -0
- package/dist/vendor/effect-oxlint/AST.js +214 -0
- package/dist/vendor/effect-oxlint/Comment.d.ts +55 -0
- package/dist/vendor/effect-oxlint/Comment.js +67 -0
- package/dist/vendor/effect-oxlint/Diagnostic.d.ts +86 -0
- package/dist/vendor/effect-oxlint/Diagnostic.js +115 -0
- package/dist/vendor/effect-oxlint/Plugin.d.ts +38 -0
- package/dist/vendor/effect-oxlint/Plugin.js +45 -0
- package/dist/vendor/effect-oxlint/Rule.d.ts +218 -0
- package/dist/vendor/effect-oxlint/Rule.js +309 -0
- package/dist/vendor/effect-oxlint/RuleContext.d.ts +77 -0
- package/dist/vendor/effect-oxlint/RuleContext.js +74 -0
- package/dist/vendor/effect-oxlint/Scope.d.ts +100 -0
- package/dist/vendor/effect-oxlint/Scope.js +110 -0
- package/dist/vendor/effect-oxlint/SourceCode.d.ts +180 -0
- package/dist/vendor/effect-oxlint/SourceCode.js +203 -0
- package/dist/vendor/effect-oxlint/Testing.d.ts +510 -0
- package/dist/vendor/effect-oxlint/Testing.js +963 -0
- package/dist/vendor/effect-oxlint/Token.d.ts +98 -0
- package/dist/vendor/effect-oxlint/Token.js +103 -0
- package/dist/vendor/effect-oxlint/Visitor.d.ts +166 -0
- package/dist/vendor/effect-oxlint/Visitor.js +138 -0
- package/dist/vendor/effect-oxlint/index.d.ts +13 -0
- package/dist/vendor/effect-oxlint/index.js +12 -0
- package/package.json +24 -5
- package/.effect-lsp.json +0 -60
- package/.oxlintrc.json +0 -33
- package/CHANGELOG.md +0 -37
- package/scripts/add-rule.ts +0 -87
- package/scripts/codegen.ts +0 -108
- package/src/index.ts +0 -9
- package/src/plugin.ts +0 -16
- package/src/presets/core.ts +0 -47
- package/src/presets/effect-native.ts +0 -34
- package/src/presets/full.ts +0 -70
- package/src/presets/functional.ts +0 -18
- package/src/presets/index.ts +0 -5
- package/src/presets/strict.ts +0 -77
- package/src/rules/_effect-context.ts +0 -28
- package/src/rules/index.ts +0 -72
- package/src/rules/no-arrow-ladder.ts +0 -70
- package/src/rules/no-async-function.ts +0 -47
- package/src/rules/no-catch-all-to-map-error.ts +0 -89
- package/src/rules/no-effect-as.ts +0 -14
- package/src/rules/no-effect-async.ts +0 -14
- package/src/rules/no-effect-bind.ts +0 -13
- package/src/rules/no-effect-do.ts +0 -14
- package/src/rules/no-effect-fn-generator.ts +0 -58
- package/src/rules/no-effect-gen-adapter.ts +0 -50
- package/src/rules/no-effect-map-flatten.ts +0 -82
- package/src/rules/no-effect-map-void.ts +0 -80
- package/src/rules/no-effect-never.ts +0 -14
- package/src/rules/no-effect-orElse-ladder.ts +0 -79
- package/src/rules/no-effect-succeed-string.ts +0 -52
- package/src/rules/no-effect-succeed-variable.ts +0 -50
- package/src/rules/no-effect-succeed-void.ts +0 -63
- package/src/rules/no-effect-sync-console.ts +0 -91
- package/src/rules/no-effect-sync-wrapper.ts +0 -67
- package/src/rules/no-effect-type-alias.ts +0 -71
- package/src/rules/no-effect-wrapper-alias.ts +0 -95
- package/src/rules/no-extends-native-error.ts +0 -54
- package/src/rules/no-flatmap-ladder.ts +0 -77
- package/src/rules/no-fromnullable-coalesce.ts +0 -62
- package/src/rules/no-global-error-in-catch.ts +0 -104
- package/src/rules/no-global-error-in-failure.ts +0 -83
- package/src/rules/no-globals.ts +0 -178
- package/src/rules/no-if-statement.ts +0 -16
- package/src/rules/no-iife-wrapper.ts +0 -51
- package/src/rules/no-inline-provide.ts +0 -41
- package/src/rules/no-instanceof-schema.ts +0 -42
- package/src/rules/no-manual-effect-channels.ts +0 -58
- package/src/rules/no-match-effect-branch.ts +0 -82
- package/src/rules/no-match-void-branch.ts +0 -72
- package/src/rules/no-multiple-effect-provide.ts +0 -75
- package/src/rules/no-nested-effect-call.ts +0 -57
- package/src/rules/no-nested-effect-gen.ts +0 -43
- package/src/rules/no-nested-pipe.ts +0 -73
- package/src/rules/no-new-error.ts +0 -53
- package/src/rules/no-new-promise.ts +0 -50
- package/src/rules/no-node-builtin-import.ts +0 -68
- package/src/rules/no-option-as.ts +0 -13
- package/src/rules/no-option-boolean-normalization.ts +0 -88
- package/src/rules/no-positional-log-error.ts +0 -76
- package/src/rules/no-redundant-schema-tag-identifier.ts +0 -62
- package/src/rules/no-return-in-arrow.ts +0 -76
- package/src/rules/no-return-nullish.ts +0 -80
- package/src/rules/no-run-in-effect.ts +0 -18
- package/src/rules/no-runtime-run-fork.ts +0 -14
- package/src/rules/no-schema-struct-with-tag.ts +0 -64
- package/src/rules/no-schema-union-of-literals.ts +0 -48
- package/src/rules/no-string-sentinel-const.ts +0 -49
- package/src/rules/no-switch-statement.ts +0 -13
- package/src/rules/no-ternary.ts +0 -30
- package/src/rules/no-throw-statement.ts +0 -39
- package/src/rules/no-try-catch.ts +0 -39
- package/src/rules/no-unnecessary-arrow-block.ts +0 -51
- package/src/rules/no-unnecessary-effect-gen.ts +0 -83
- package/src/rules/no-unnecessary-pipe-chain.ts +0 -47
- package/src/rules/no-unnecessary-pipe.ts +0 -45
- package/src/vendor/effect-oxlint/AST.ts +0 -516
- package/src/vendor/effect-oxlint/Comment.ts +0 -86
- package/src/vendor/effect-oxlint/Diagnostic.ts +0 -199
- package/src/vendor/effect-oxlint/Plugin.ts +0 -71
- package/src/vendor/effect-oxlint/Rule.ts +0 -528
- package/src/vendor/effect-oxlint/RuleContext.ts +0 -144
- package/src/vendor/effect-oxlint/Scope.ts +0 -177
- package/src/vendor/effect-oxlint/SourceCode.ts +0 -377
- package/src/vendor/effect-oxlint/Testing.ts +0 -1227
- package/src/vendor/effect-oxlint/Token.ts +0 -131
- package/src/vendor/effect-oxlint/Visitor.ts +0 -287
- package/src/vendor/effect-oxlint/index.ts +0 -88
- package/tests/ban-rules.test.ts +0 -85
- package/tests/context-rules.test.ts +0 -198
- package/tests/pattern-rules.test.ts +0 -247
- package/tsconfig.json +0 -15
|
@@ -1,1227 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Public testing infrastructure for Effect-first oxlint rules.
|
|
3
|
-
*
|
|
4
|
-
* Provides AST node builders, mock context factories, rule runners,
|
|
5
|
-
* and assertion helpers for use with `@effect/vitest`.
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* ```ts
|
|
9
|
-
* import { Testing, Rule } from 'effect-oxlint'
|
|
10
|
-
*
|
|
11
|
-
* const result = Testing.runRule(myRule, 'ThrowStatement', Testing.throwStmt())
|
|
12
|
-
* Testing.expectDiagnostics(result, [{ message: 'No throw in Effect.gen' }])
|
|
13
|
-
* ```
|
|
14
|
-
*
|
|
15
|
-
* @since 0.2.0
|
|
16
|
-
*/
|
|
17
|
-
import type {
|
|
18
|
-
Comment,
|
|
19
|
-
Context as OxlintContext,
|
|
20
|
-
CreateRule,
|
|
21
|
-
Diagnostic as OxlintDiagnostic,
|
|
22
|
-
ESTree,
|
|
23
|
-
Scope as OxlintScope,
|
|
24
|
-
Token,
|
|
25
|
-
Variable,
|
|
26
|
-
Visitor as OxlintVisitor
|
|
27
|
-
} from '@oxlint/plugins';
|
|
28
|
-
import * as Arr from 'effect/Array';
|
|
29
|
-
import * as Effect from 'effect/Effect';
|
|
30
|
-
import * as Layer from 'effect/Layer';
|
|
31
|
-
import * as Option from 'effect/Option';
|
|
32
|
-
import * as P from 'effect/Predicate';
|
|
33
|
-
|
|
34
|
-
import { fromOxlintContext, RuleContext } from './RuleContext.ts';
|
|
35
|
-
|
|
36
|
-
// ---------------------------------------------------------------------------
|
|
37
|
-
// AST Node Builders
|
|
38
|
-
// ---------------------------------------------------------------------------
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Minimal AST node builders for tests.
|
|
42
|
-
*
|
|
43
|
-
* These produce mock objects satisfying the type shapes that AST/Visitor/Rule
|
|
44
|
-
* modules expect. They use boundary casts at the seam between test mock
|
|
45
|
-
* data and oxlint's strict branded types.
|
|
46
|
-
*
|
|
47
|
-
* @since 0.2.0
|
|
48
|
-
*/
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Identifier: `{ type: "Identifier", name }`
|
|
52
|
-
*
|
|
53
|
-
* @since 0.2.0
|
|
54
|
-
*/
|
|
55
|
-
export const id = (name: string): ESTree.IdentifierName =>
|
|
56
|
-
({ type: 'Identifier', name }) as never;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* MemberExpression: `obj.prop` (non-computed)
|
|
60
|
-
*
|
|
61
|
-
* @since 0.2.0
|
|
62
|
-
*/
|
|
63
|
-
export const memberExpr = (
|
|
64
|
-
obj: string,
|
|
65
|
-
prop: string
|
|
66
|
-
): ESTree.MemberExpression =>
|
|
67
|
-
({
|
|
68
|
-
type: 'MemberExpression',
|
|
69
|
-
object: id(obj),
|
|
70
|
-
property: id(prop),
|
|
71
|
-
computed: false,
|
|
72
|
-
optional: false
|
|
73
|
-
}) as never;
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* MemberExpression: `obj[prop]` (computed)
|
|
77
|
-
*
|
|
78
|
-
* @since 0.2.0
|
|
79
|
-
*/
|
|
80
|
-
export const computedMemberExpr = (
|
|
81
|
-
obj: string,
|
|
82
|
-
prop: string
|
|
83
|
-
): ESTree.MemberExpression =>
|
|
84
|
-
({
|
|
85
|
-
type: 'MemberExpression',
|
|
86
|
-
object: id(obj),
|
|
87
|
-
property: id(prop),
|
|
88
|
-
computed: true,
|
|
89
|
-
optional: false
|
|
90
|
-
}) as never;
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Chained MemberExpression: `a.b.c` (non-computed)
|
|
94
|
-
*
|
|
95
|
-
* @since 0.2.0
|
|
96
|
-
*/
|
|
97
|
-
export const chainedMemberExpr = (
|
|
98
|
-
...names: readonly [string, string, ...ReadonlyArray<string>]
|
|
99
|
-
): ESTree.MemberExpression => {
|
|
100
|
-
const [first, second, ...rest] = names;
|
|
101
|
-
const initial = memberExpr(first, second);
|
|
102
|
-
return Arr.reduce(
|
|
103
|
-
rest,
|
|
104
|
-
initial,
|
|
105
|
-
(acc, name) =>
|
|
106
|
-
({
|
|
107
|
-
type: 'MemberExpression',
|
|
108
|
-
object: acc,
|
|
109
|
-
property: id(name),
|
|
110
|
-
computed: false,
|
|
111
|
-
optional: false
|
|
112
|
-
}) as never
|
|
113
|
-
);
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* CallExpression with bare identifier callee: `name(args)`
|
|
118
|
-
*
|
|
119
|
-
* @since 0.2.0
|
|
120
|
-
*/
|
|
121
|
-
export const callExpr = (
|
|
122
|
-
name: string,
|
|
123
|
-
args: ReadonlyArray<unknown> = []
|
|
124
|
-
): ESTree.CallExpression =>
|
|
125
|
-
({
|
|
126
|
-
type: 'CallExpression',
|
|
127
|
-
callee: id(name),
|
|
128
|
-
arguments: args
|
|
129
|
-
}) as never;
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* CallExpression with MemberExpression callee: `obj.prop(args)`
|
|
133
|
-
*
|
|
134
|
-
* @since 0.2.0
|
|
135
|
-
*/
|
|
136
|
-
export const callOfMember = (
|
|
137
|
-
obj: string,
|
|
138
|
-
prop: string,
|
|
139
|
-
args: ReadonlyArray<unknown> = []
|
|
140
|
-
): ESTree.CallExpression =>
|
|
141
|
-
({
|
|
142
|
-
type: 'CallExpression',
|
|
143
|
-
callee: memberExpr(obj, prop),
|
|
144
|
-
arguments: args
|
|
145
|
-
}) as never;
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* ImportDeclaration: `import ... from "source"`
|
|
149
|
-
*
|
|
150
|
-
* @since 0.2.0
|
|
151
|
-
*/
|
|
152
|
-
export const importDecl = (source: string): ESTree.ImportDeclaration =>
|
|
153
|
-
({
|
|
154
|
-
type: 'ImportDeclaration',
|
|
155
|
-
source: { type: 'Literal', value: source },
|
|
156
|
-
specifiers: []
|
|
157
|
-
}) as never;
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* String literal: `{ type: "Literal", value }`
|
|
161
|
-
*
|
|
162
|
-
* @since 0.2.0
|
|
163
|
-
*/
|
|
164
|
-
export const strLiteral = (value: string): ESTree.StringLiteral =>
|
|
165
|
-
({ type: 'Literal', value }) as never;
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Numeric literal: `{ type: "Literal", value }`
|
|
169
|
-
*
|
|
170
|
-
* @since 0.2.0
|
|
171
|
-
*/
|
|
172
|
-
export const numLiteral = (value: number): ESTree.NumericLiteral =>
|
|
173
|
-
({ type: 'Literal', value }) as never;
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* Boolean literal: `{ type: "Literal", value }`
|
|
177
|
-
*
|
|
178
|
-
* @since 0.2.0
|
|
179
|
-
*/
|
|
180
|
-
export const boolLiteral = (value: boolean): ESTree.BooleanLiteral =>
|
|
181
|
-
({ type: 'Literal', value }) as never;
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* ObjectExpression with identifier-keyed properties.
|
|
185
|
-
*
|
|
186
|
-
* @since 0.2.0
|
|
187
|
-
*/
|
|
188
|
-
export const objectExpr = (
|
|
189
|
-
properties: ReadonlyArray<{
|
|
190
|
-
readonly key: string;
|
|
191
|
-
readonly value?: unknown;
|
|
192
|
-
}>
|
|
193
|
-
): ESTree.ObjectExpression =>
|
|
194
|
-
({
|
|
195
|
-
type: 'ObjectExpression',
|
|
196
|
-
properties: Arr.map(properties, (p) => ({
|
|
197
|
-
type: 'Property',
|
|
198
|
-
key: id(p.key),
|
|
199
|
-
value: p.value ?? strLiteral('')
|
|
200
|
-
}))
|
|
201
|
-
}) as never;
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* ObjectExpression with string literal keys.
|
|
205
|
-
*
|
|
206
|
-
* @since 0.2.0
|
|
207
|
-
*/
|
|
208
|
-
export const objectExprLiteralKeys = (
|
|
209
|
-
properties: ReadonlyArray<{
|
|
210
|
-
readonly key: string;
|
|
211
|
-
readonly value?: unknown;
|
|
212
|
-
}>
|
|
213
|
-
): ESTree.ObjectExpression =>
|
|
214
|
-
({
|
|
215
|
-
type: 'ObjectExpression',
|
|
216
|
-
properties: Arr.map(properties, (p) => ({
|
|
217
|
-
type: 'Property',
|
|
218
|
-
key: strLiteral(p.key),
|
|
219
|
-
value: p.value ?? strLiteral('')
|
|
220
|
-
}))
|
|
221
|
-
}) as never;
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* ObjectExpression with a SpreadElement.
|
|
225
|
-
*
|
|
226
|
-
* @since 0.2.0
|
|
227
|
-
*/
|
|
228
|
-
export const objectExprWithSpread = (
|
|
229
|
-
spreadArg: unknown
|
|
230
|
-
): ESTree.ObjectExpression =>
|
|
231
|
-
({
|
|
232
|
-
type: 'ObjectExpression',
|
|
233
|
-
properties: [{ type: 'SpreadElement', argument: spreadArg }]
|
|
234
|
-
}) as never;
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* ThrowStatement.
|
|
238
|
-
*
|
|
239
|
-
* @since 0.2.0
|
|
240
|
-
*/
|
|
241
|
-
export const throwStmt = (): ESTree.ThrowStatement =>
|
|
242
|
-
({ type: 'ThrowStatement' }) as never;
|
|
243
|
-
|
|
244
|
-
/**
|
|
245
|
-
* TryStatement.
|
|
246
|
-
*
|
|
247
|
-
* @since 0.2.0
|
|
248
|
-
*/
|
|
249
|
-
export const tryStmt = (): ESTree.Node => ({ type: 'TryStatement' }) as never;
|
|
250
|
-
|
|
251
|
-
/**
|
|
252
|
-
* ReturnStatement.
|
|
253
|
-
*
|
|
254
|
-
* @since 0.2.0
|
|
255
|
-
*/
|
|
256
|
-
export const returnStmt = (argument?: unknown): ESTree.ReturnStatement =>
|
|
257
|
-
({ type: 'ReturnStatement', argument: argument ?? null }) as never;
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* BlockStatement.
|
|
261
|
-
*
|
|
262
|
-
* @since 0.2.0
|
|
263
|
-
*/
|
|
264
|
-
export const blockStmt = (
|
|
265
|
-
body: ReadonlyArray<unknown> = []
|
|
266
|
-
): ESTree.BlockStatement =>
|
|
267
|
-
({ type: 'BlockStatement', body: Array.from(body) }) as never;
|
|
268
|
-
|
|
269
|
-
/**
|
|
270
|
-
* ArrowFunctionExpression.
|
|
271
|
-
*
|
|
272
|
-
* @since 0.2.0
|
|
273
|
-
*/
|
|
274
|
-
export const arrowFn = (
|
|
275
|
-
body?: unknown,
|
|
276
|
-
params: ReadonlyArray<unknown> = []
|
|
277
|
-
): ESTree.ArrowFunctionExpression =>
|
|
278
|
-
({
|
|
279
|
-
type: 'ArrowFunctionExpression',
|
|
280
|
-
params: Array.from(params),
|
|
281
|
-
body: body ?? blockStmt(),
|
|
282
|
-
expression: false,
|
|
283
|
-
async: false
|
|
284
|
-
}) as never;
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* VariableDeclaration: `const/let/var name = init`
|
|
288
|
-
*
|
|
289
|
-
* @since 0.2.0
|
|
290
|
-
*/
|
|
291
|
-
export const varDecl = (
|
|
292
|
-
kind: 'const' | 'let' | 'var',
|
|
293
|
-
name: string,
|
|
294
|
-
init?: unknown
|
|
295
|
-
): ESTree.VariableDeclaration =>
|
|
296
|
-
({
|
|
297
|
-
type: 'VariableDeclaration',
|
|
298
|
-
kind,
|
|
299
|
-
declarations: [
|
|
300
|
-
{
|
|
301
|
-
type: 'VariableDeclarator',
|
|
302
|
-
id: id(name),
|
|
303
|
-
init: init ?? null
|
|
304
|
-
}
|
|
305
|
-
]
|
|
306
|
-
}) as never;
|
|
307
|
-
|
|
308
|
-
/**
|
|
309
|
-
* ExpressionStatement.
|
|
310
|
-
*
|
|
311
|
-
* @since 0.2.0
|
|
312
|
-
*/
|
|
313
|
-
export const exprStmt = (expression: unknown): ESTree.ExpressionStatement =>
|
|
314
|
-
({ type: 'ExpressionStatement', expression }) as never;
|
|
315
|
-
|
|
316
|
-
/**
|
|
317
|
-
* Program node.
|
|
318
|
-
*
|
|
319
|
-
* @since 0.2.0
|
|
320
|
-
*/
|
|
321
|
-
export const program = (
|
|
322
|
-
body: ReadonlyArray<unknown> = [],
|
|
323
|
-
comments: ReadonlyArray<unknown> = []
|
|
324
|
-
): ESTree.Program =>
|
|
325
|
-
({
|
|
326
|
-
type: 'Program',
|
|
327
|
-
body: Array.from(body),
|
|
328
|
-
comments: Array.from(comments),
|
|
329
|
-
sourceType: 'module'
|
|
330
|
-
}) as never;
|
|
331
|
-
|
|
332
|
-
/**
|
|
333
|
-
* IfStatement.
|
|
334
|
-
*
|
|
335
|
-
* All parameters are optional — `ifStmt()` produces a minimal
|
|
336
|
-
* `{ type: 'IfStatement' }` node suitable for enter/exit tracking.
|
|
337
|
-
*
|
|
338
|
-
* @since 0.2.0
|
|
339
|
-
*/
|
|
340
|
-
export const ifStmt = (
|
|
341
|
-
test?: unknown,
|
|
342
|
-
consequent?: unknown,
|
|
343
|
-
alternate?: unknown
|
|
344
|
-
): ESTree.IfStatement =>
|
|
345
|
-
({
|
|
346
|
-
type: 'IfStatement',
|
|
347
|
-
test: test ?? null,
|
|
348
|
-
consequent: consequent ?? null,
|
|
349
|
-
alternate: alternate ?? null
|
|
350
|
-
}) as never;
|
|
351
|
-
|
|
352
|
-
/**
|
|
353
|
-
* BinaryExpression.
|
|
354
|
-
*
|
|
355
|
-
* @since 0.2.0
|
|
356
|
-
*/
|
|
357
|
-
export const binaryExpr = (
|
|
358
|
-
operator: string,
|
|
359
|
-
left: unknown,
|
|
360
|
-
right: unknown
|
|
361
|
-
): ESTree.BinaryExpression =>
|
|
362
|
-
({ type: 'BinaryExpression', operator, left, right }) as never;
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* NewExpression: `new callee(args)`
|
|
366
|
-
*
|
|
367
|
-
* When `callee` is a string it is auto-wrapped in `id()`, so
|
|
368
|
-
* `newExpr('Date')` is equivalent to `newExpr(id('Date'))`.
|
|
369
|
-
*
|
|
370
|
-
* @since 0.2.0
|
|
371
|
-
*/
|
|
372
|
-
export const newExpr: {
|
|
373
|
-
(callee: string, args?: ReadonlyArray<unknown>): ESTree.NewExpression;
|
|
374
|
-
(callee: unknown, args?: ReadonlyArray<unknown>): ESTree.NewExpression;
|
|
375
|
-
} = (
|
|
376
|
-
callee: unknown,
|
|
377
|
-
args: ReadonlyArray<unknown> = []
|
|
378
|
-
): ESTree.NewExpression =>
|
|
379
|
-
({
|
|
380
|
-
type: 'NewExpression',
|
|
381
|
-
callee: P.isString(callee) ? id(callee) : callee,
|
|
382
|
-
arguments: Array.from(args)
|
|
383
|
-
}) as never;
|
|
384
|
-
|
|
385
|
-
/**
|
|
386
|
-
* A generic AST node with type and optional parent pointer.
|
|
387
|
-
*
|
|
388
|
-
* @since 0.2.0
|
|
389
|
-
*/
|
|
390
|
-
export const astNode = (
|
|
391
|
-
type: string,
|
|
392
|
-
parent?: { readonly type: string; readonly parent?: unknown }
|
|
393
|
-
): { readonly type: string; readonly parent?: unknown } => ({
|
|
394
|
-
type,
|
|
395
|
-
parent
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
/**
|
|
399
|
-
* Build a parent chain from outermost → innermost.
|
|
400
|
-
*
|
|
401
|
-
* Returns the innermost node with `.parent` links to each ancestor.
|
|
402
|
-
*
|
|
403
|
-
* @example
|
|
404
|
-
* ```ts
|
|
405
|
-
* // Creates: FunctionDeclaration → BlockStatement → ThrowStatement
|
|
406
|
-
* withParentChain('FunctionDeclaration', 'BlockStatement', 'ThrowStatement')
|
|
407
|
-
* ```
|
|
408
|
-
*
|
|
409
|
-
* @since 0.2.0
|
|
410
|
-
*/
|
|
411
|
-
export const withParentChain = (
|
|
412
|
-
first: string,
|
|
413
|
-
...rest: ReadonlyArray<string>
|
|
414
|
-
): { readonly type: string; readonly parent?: unknown } =>
|
|
415
|
-
Arr.reduce(
|
|
416
|
-
rest,
|
|
417
|
-
astNode(first) satisfies {
|
|
418
|
-
readonly type: string;
|
|
419
|
-
readonly parent?: unknown;
|
|
420
|
-
},
|
|
421
|
-
(parent, type) => astNode(type, parent)
|
|
422
|
-
);
|
|
423
|
-
|
|
424
|
-
/**
|
|
425
|
-
* Mock Token.
|
|
426
|
-
*
|
|
427
|
-
* @since 0.2.0
|
|
428
|
-
*/
|
|
429
|
-
export const token = (type: Token['type'], value: string): Token =>
|
|
430
|
-
({
|
|
431
|
-
type,
|
|
432
|
-
value,
|
|
433
|
-
start: 0,
|
|
434
|
-
end: value.length,
|
|
435
|
-
range: [0, value.length],
|
|
436
|
-
loc: {
|
|
437
|
-
start: { line: 1, column: 0 },
|
|
438
|
-
end: { line: 1, column: value.length }
|
|
439
|
-
},
|
|
440
|
-
regex: undefined
|
|
441
|
-
}) as never;
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Mock Comment.
|
|
445
|
-
*
|
|
446
|
-
* @since 0.2.0
|
|
447
|
-
*/
|
|
448
|
-
export const comment = (type: Comment['type'], value: string): Comment =>
|
|
449
|
-
({
|
|
450
|
-
type,
|
|
451
|
-
value,
|
|
452
|
-
start: 0,
|
|
453
|
-
end: value.length + 4,
|
|
454
|
-
range: [0, value.length + 4],
|
|
455
|
-
loc: {
|
|
456
|
-
start: { line: 1, column: 0 },
|
|
457
|
-
end: { line: 1, column: value.length + 4 }
|
|
458
|
-
}
|
|
459
|
-
}) as never;
|
|
460
|
-
|
|
461
|
-
/**
|
|
462
|
-
* Mock Scope with minimal surface.
|
|
463
|
-
*
|
|
464
|
-
* @since 0.2.0
|
|
465
|
-
*/
|
|
466
|
-
export const scope = (
|
|
467
|
-
opts: {
|
|
468
|
-
readonly type?: OxlintScope['type'];
|
|
469
|
-
readonly isStrict?: boolean;
|
|
470
|
-
readonly variables?: ReadonlyArray<Variable>;
|
|
471
|
-
readonly upper?: OxlintScope | null;
|
|
472
|
-
} = {}
|
|
473
|
-
): OxlintScope => {
|
|
474
|
-
const vars = Array.from(opts.variables ?? []);
|
|
475
|
-
const set = new Map<string, Variable>(vars.map((v) => [v.name, v]));
|
|
476
|
-
return {
|
|
477
|
-
type: opts.type ?? 'function',
|
|
478
|
-
isStrict: opts.isStrict ?? false,
|
|
479
|
-
upper: opts.upper ?? null,
|
|
480
|
-
childScopes: [],
|
|
481
|
-
variableScope: null as never,
|
|
482
|
-
block: {} as never,
|
|
483
|
-
variables: vars,
|
|
484
|
-
set,
|
|
485
|
-
references: [],
|
|
486
|
-
through: [],
|
|
487
|
-
functionExpressionScope: false
|
|
488
|
-
};
|
|
489
|
-
};
|
|
490
|
-
|
|
491
|
-
/**
|
|
492
|
-
* Mock Variable.
|
|
493
|
-
*
|
|
494
|
-
* @since 0.2.0
|
|
495
|
-
*/
|
|
496
|
-
export const variable = (
|
|
497
|
-
name: string,
|
|
498
|
-
opts: {
|
|
499
|
-
readonly references?: ReadonlyArray<{
|
|
500
|
-
readonly isRead: () => boolean;
|
|
501
|
-
readonly isWrite: () => boolean;
|
|
502
|
-
readonly isReadOnly: () => boolean;
|
|
503
|
-
readonly isWriteOnly: () => boolean;
|
|
504
|
-
readonly isReadWrite: () => boolean;
|
|
505
|
-
}>;
|
|
506
|
-
} = {}
|
|
507
|
-
): Variable =>
|
|
508
|
-
({
|
|
509
|
-
name,
|
|
510
|
-
scope: {} as never,
|
|
511
|
-
identifiers: [id(name)],
|
|
512
|
-
references: Array.from(opts.references ?? []),
|
|
513
|
-
defs: []
|
|
514
|
-
}) as never;
|
|
515
|
-
|
|
516
|
-
// ---------------------------------------------------------------------------
|
|
517
|
-
// Statement Builders
|
|
518
|
-
// ---------------------------------------------------------------------------
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* SwitchStatement.
|
|
522
|
-
*
|
|
523
|
-
* @since 0.2.0
|
|
524
|
-
*/
|
|
525
|
-
export const switchStmt = (): ESTree.SwitchStatement =>
|
|
526
|
-
({ type: 'SwitchStatement', discriminant: null, cases: [] }) as never;
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* ForStatement.
|
|
530
|
-
*
|
|
531
|
-
* @since 0.2.0
|
|
532
|
-
*/
|
|
533
|
-
export const forStmt = (): ESTree.ForStatement =>
|
|
534
|
-
({
|
|
535
|
-
type: 'ForStatement',
|
|
536
|
-
init: null,
|
|
537
|
-
test: null,
|
|
538
|
-
update: null,
|
|
539
|
-
body: blockStmt()
|
|
540
|
-
}) as never;
|
|
541
|
-
|
|
542
|
-
/**
|
|
543
|
-
* ForInStatement.
|
|
544
|
-
*
|
|
545
|
-
* @since 0.2.0
|
|
546
|
-
*/
|
|
547
|
-
export const forInStmt = (): ESTree.ForInStatement =>
|
|
548
|
-
({
|
|
549
|
-
type: 'ForInStatement',
|
|
550
|
-
left: id('_'),
|
|
551
|
-
right: id('_'),
|
|
552
|
-
body: blockStmt()
|
|
553
|
-
}) as never;
|
|
554
|
-
|
|
555
|
-
/**
|
|
556
|
-
* ForOfStatement.
|
|
557
|
-
*
|
|
558
|
-
* @since 0.2.0
|
|
559
|
-
*/
|
|
560
|
-
export const forOfStmt = (): ESTree.ForOfStatement =>
|
|
561
|
-
({
|
|
562
|
-
type: 'ForOfStatement',
|
|
563
|
-
await: false,
|
|
564
|
-
left: id('_'),
|
|
565
|
-
right: id('_'),
|
|
566
|
-
body: blockStmt()
|
|
567
|
-
}) as never;
|
|
568
|
-
|
|
569
|
-
/**
|
|
570
|
-
* WhileStatement.
|
|
571
|
-
*
|
|
572
|
-
* @since 0.2.0
|
|
573
|
-
*/
|
|
574
|
-
export const whileStmt = (): ESTree.WhileStatement =>
|
|
575
|
-
({
|
|
576
|
-
type: 'WhileStatement',
|
|
577
|
-
test: boolLiteral(true),
|
|
578
|
-
body: blockStmt()
|
|
579
|
-
}) as never;
|
|
580
|
-
|
|
581
|
-
/**
|
|
582
|
-
* DoWhileStatement.
|
|
583
|
-
*
|
|
584
|
-
* @since 0.2.0
|
|
585
|
-
*/
|
|
586
|
-
export const doWhileStmt = (): ESTree.DoWhileStatement =>
|
|
587
|
-
({
|
|
588
|
-
type: 'DoWhileStatement',
|
|
589
|
-
test: boolLiteral(true),
|
|
590
|
-
body: blockStmt()
|
|
591
|
-
}) as never;
|
|
592
|
-
|
|
593
|
-
// ---------------------------------------------------------------------------
|
|
594
|
-
// Expression Builders
|
|
595
|
-
// ---------------------------------------------------------------------------
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* YieldExpression.
|
|
599
|
-
*
|
|
600
|
-
* @since 0.2.0
|
|
601
|
-
*/
|
|
602
|
-
export const yieldExpr = (
|
|
603
|
-
argument?: unknown,
|
|
604
|
-
delegate: boolean = false
|
|
605
|
-
): ESTree.YieldExpression =>
|
|
606
|
-
({
|
|
607
|
-
type: 'YieldExpression',
|
|
608
|
-
argument: argument ?? null,
|
|
609
|
-
delegate
|
|
610
|
-
}) as never;
|
|
611
|
-
|
|
612
|
-
/**
|
|
613
|
-
* UnaryExpression: `operator argument`
|
|
614
|
-
*
|
|
615
|
-
* @since 0.2.0
|
|
616
|
-
*/
|
|
617
|
-
export const unaryExpr = (
|
|
618
|
-
operator: string,
|
|
619
|
-
argument: unknown
|
|
620
|
-
): ESTree.UnaryExpression =>
|
|
621
|
-
({ type: 'UnaryExpression', operator, prefix: true, argument }) as never;
|
|
622
|
-
|
|
623
|
-
// ---------------------------------------------------------------------------
|
|
624
|
-
// Declaration Builders
|
|
625
|
-
// ---------------------------------------------------------------------------
|
|
626
|
-
|
|
627
|
-
/**
|
|
628
|
-
* VariableDeclarator (standalone, without wrapping VariableDeclaration).
|
|
629
|
-
*
|
|
630
|
-
* @since 0.2.0
|
|
631
|
-
*/
|
|
632
|
-
export const varDeclarator = (
|
|
633
|
-
name: string,
|
|
634
|
-
init?: unknown
|
|
635
|
-
): ESTree.VariableDeclarator =>
|
|
636
|
-
({
|
|
637
|
-
type: 'VariableDeclarator',
|
|
638
|
-
id: id(name),
|
|
639
|
-
init: init ?? null
|
|
640
|
-
}) as never;
|
|
641
|
-
|
|
642
|
-
/**
|
|
643
|
-
* ExportNamedDeclaration.
|
|
644
|
-
*
|
|
645
|
-
* @since 0.2.0
|
|
646
|
-
*/
|
|
647
|
-
export const exportNamedDecl = (
|
|
648
|
-
declaration?: unknown
|
|
649
|
-
): ESTree.ExportNamedDeclaration =>
|
|
650
|
-
({
|
|
651
|
-
type: 'ExportNamedDeclaration',
|
|
652
|
-
declaration: declaration ?? null,
|
|
653
|
-
specifiers: [],
|
|
654
|
-
source: null
|
|
655
|
-
}) as never;
|
|
656
|
-
|
|
657
|
-
// ---------------------------------------------------------------------------
|
|
658
|
-
// Import Specifier Builders
|
|
659
|
-
// ---------------------------------------------------------------------------
|
|
660
|
-
|
|
661
|
-
/**
|
|
662
|
-
* ImportDeclaration with specifiers: `import { a, b } from "source"`
|
|
663
|
-
*
|
|
664
|
-
* @since 0.2.0
|
|
665
|
-
*/
|
|
666
|
-
export const importDeclWithSpecifiers = (
|
|
667
|
-
source: string,
|
|
668
|
-
specifiers: ReadonlyArray<unknown>,
|
|
669
|
-
importKind: string = 'value'
|
|
670
|
-
): ESTree.ImportDeclaration =>
|
|
671
|
-
({
|
|
672
|
-
type: 'ImportDeclaration',
|
|
673
|
-
source: { type: 'Literal', value: source },
|
|
674
|
-
specifiers: Array.from(specifiers),
|
|
675
|
-
importKind
|
|
676
|
-
}) as never;
|
|
677
|
-
|
|
678
|
-
/**
|
|
679
|
-
* ImportSpecifier: `{ imported as local }`
|
|
680
|
-
*
|
|
681
|
-
* @since 0.2.0
|
|
682
|
-
*/
|
|
683
|
-
export const importSpecifier = (
|
|
684
|
-
imported: string,
|
|
685
|
-
local?: string,
|
|
686
|
-
importKind: string = 'value'
|
|
687
|
-
): ESTree.ImportSpecifier =>
|
|
688
|
-
({
|
|
689
|
-
type: 'ImportSpecifier',
|
|
690
|
-
imported: id(imported),
|
|
691
|
-
local: id(local ?? imported),
|
|
692
|
-
importKind
|
|
693
|
-
}) as never;
|
|
694
|
-
|
|
695
|
-
/**
|
|
696
|
-
* ImportNamespaceSpecifier: `* as local`
|
|
697
|
-
*
|
|
698
|
-
* @since 0.2.0
|
|
699
|
-
*/
|
|
700
|
-
export const importNamespaceSpecifier = (
|
|
701
|
-
local: string
|
|
702
|
-
): ESTree.ImportNamespaceSpecifier =>
|
|
703
|
-
({
|
|
704
|
-
type: 'ImportNamespaceSpecifier',
|
|
705
|
-
local: id(local)
|
|
706
|
-
}) as never;
|
|
707
|
-
|
|
708
|
-
// ---------------------------------------------------------------------------
|
|
709
|
-
// TypeScript Node Builders
|
|
710
|
-
// ---------------------------------------------------------------------------
|
|
711
|
-
|
|
712
|
-
/**
|
|
713
|
-
* TSAsExpression: `expr as Type`
|
|
714
|
-
*
|
|
715
|
-
* @since 0.2.0
|
|
716
|
-
*/
|
|
717
|
-
export const tsAsExpr = (
|
|
718
|
-
typeKind: string,
|
|
719
|
-
parent?: { readonly type: string; readonly parent?: unknown }
|
|
720
|
-
): ESTree.TSAsExpression =>
|
|
721
|
-
({
|
|
722
|
-
type: 'TSAsExpression',
|
|
723
|
-
expression: id('_'),
|
|
724
|
-
typeAnnotation: { type: typeKind },
|
|
725
|
-
parent
|
|
726
|
-
}) as never;
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* TSUnionType: `A | B | C`
|
|
730
|
-
*
|
|
731
|
-
* @since 0.2.0
|
|
732
|
-
*/
|
|
733
|
-
export const tsUnionType = (
|
|
734
|
-
typeKinds: ReadonlyArray<string>
|
|
735
|
-
): ESTree.TSUnionType =>
|
|
736
|
-
({
|
|
737
|
-
type: 'TSUnionType',
|
|
738
|
-
types: Arr.map(typeKinds, (t) => ({ type: t }))
|
|
739
|
-
}) as never;
|
|
740
|
-
|
|
741
|
-
/**
|
|
742
|
-
* TSTypeReference: `TypeName`
|
|
743
|
-
*
|
|
744
|
-
* @since 0.2.0
|
|
745
|
-
*/
|
|
746
|
-
export const tsTypeRef = (name: string): ESTree.TSTypeReference =>
|
|
747
|
-
({
|
|
748
|
-
type: 'TSTypeReference',
|
|
749
|
-
typeName: id(name),
|
|
750
|
-
typeArguments: null
|
|
751
|
-
}) as never;
|
|
752
|
-
|
|
753
|
-
/**
|
|
754
|
-
* TSTypeLiteral: `{ ... }` with N members.
|
|
755
|
-
*
|
|
756
|
-
* @since 0.2.0
|
|
757
|
-
*/
|
|
758
|
-
export const tsTypeLiteral = (memberCount: number): ESTree.TSTypeLiteral =>
|
|
759
|
-
({
|
|
760
|
-
type: 'TSTypeLiteral',
|
|
761
|
-
members: Array.from({ length: memberCount }, () => ({
|
|
762
|
-
type: 'TSPropertySignature'
|
|
763
|
-
}))
|
|
764
|
-
}) as never;
|
|
765
|
-
|
|
766
|
-
/**
|
|
767
|
-
* TSInterfaceDeclaration: `interface Name { }`
|
|
768
|
-
*
|
|
769
|
-
* @since 0.2.0
|
|
770
|
-
*/
|
|
771
|
-
export const interfaceDecl = (name: string): ESTree.TSInterfaceDeclaration =>
|
|
772
|
-
({
|
|
773
|
-
type: 'TSInterfaceDeclaration',
|
|
774
|
-
id: { type: 'BindingIdentifier', name },
|
|
775
|
-
body: { type: 'TSInterfaceBody', body: [] }
|
|
776
|
-
}) as never;
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
* TSTypeAliasDeclaration: `type Name = ...`
|
|
780
|
-
*
|
|
781
|
-
* @since 0.2.0
|
|
782
|
-
*/
|
|
783
|
-
export const typeAliasDecl = (name: string): ESTree.TSTypeAliasDeclaration =>
|
|
784
|
-
({
|
|
785
|
-
type: 'TSTypeAliasDeclaration',
|
|
786
|
-
id: { type: 'BindingIdentifier', name },
|
|
787
|
-
typeAnnotation: null
|
|
788
|
-
}) as never;
|
|
789
|
-
|
|
790
|
-
// ---------------------------------------------------------------------------
|
|
791
|
-
// Class Node Builders
|
|
792
|
-
// ---------------------------------------------------------------------------
|
|
793
|
-
|
|
794
|
-
/**
|
|
795
|
-
* ClassDeclaration with optional superClass and body members.
|
|
796
|
-
*
|
|
797
|
-
* @example
|
|
798
|
-
* ```ts
|
|
799
|
-
* // Simple: class Foo {}
|
|
800
|
-
* classDecl('Foo')
|
|
801
|
-
*
|
|
802
|
-
* // With super: class Foo extends Bar {}
|
|
803
|
-
* classDecl('Foo', { superClass: Testing.id('Bar') })
|
|
804
|
-
*
|
|
805
|
-
* // With members: class Foo { x; static y() {} }
|
|
806
|
-
* classDecl('Foo', {
|
|
807
|
-
* members: [Testing.propertyDef('x'), Testing.methodDef('y', true)]
|
|
808
|
-
* })
|
|
809
|
-
* ```
|
|
810
|
-
*
|
|
811
|
-
* @since 0.2.0
|
|
812
|
-
*/
|
|
813
|
-
export const classDecl = (
|
|
814
|
-
name: string,
|
|
815
|
-
opts: {
|
|
816
|
-
readonly superClass?: unknown;
|
|
817
|
-
readonly members?: ReadonlyArray<unknown>;
|
|
818
|
-
} = {}
|
|
819
|
-
): ESTree.Class =>
|
|
820
|
-
({
|
|
821
|
-
type: 'ClassDeclaration',
|
|
822
|
-
id: { type: 'BindingIdentifier', name },
|
|
823
|
-
superClass: opts.superClass ?? null,
|
|
824
|
-
body: {
|
|
825
|
-
type: 'ClassBody',
|
|
826
|
-
body: Array.from(opts.members ?? [])
|
|
827
|
-
},
|
|
828
|
-
decorators: []
|
|
829
|
-
}) as never;
|
|
830
|
-
|
|
831
|
-
/**
|
|
832
|
-
* PropertyDefinition: class field.
|
|
833
|
-
*
|
|
834
|
-
* @since 0.2.0
|
|
835
|
-
*/
|
|
836
|
-
export const propertyDef = (
|
|
837
|
-
name: string,
|
|
838
|
-
isStatic: boolean = false
|
|
839
|
-
): ESTree.PropertyDefinition =>
|
|
840
|
-
({
|
|
841
|
-
type: 'PropertyDefinition',
|
|
842
|
-
key: id(name),
|
|
843
|
-
value: null,
|
|
844
|
-
computed: false,
|
|
845
|
-
static: isStatic,
|
|
846
|
-
decorators: []
|
|
847
|
-
}) as never;
|
|
848
|
-
|
|
849
|
-
/**
|
|
850
|
-
* MethodDefinition: class method.
|
|
851
|
-
*
|
|
852
|
-
* @since 0.2.0
|
|
853
|
-
*/
|
|
854
|
-
export const methodDef = (
|
|
855
|
-
name: string,
|
|
856
|
-
isStatic: boolean = false
|
|
857
|
-
): ESTree.MethodDefinition =>
|
|
858
|
-
({
|
|
859
|
-
type: 'MethodDefinition',
|
|
860
|
-
key: id(name),
|
|
861
|
-
kind: 'method',
|
|
862
|
-
value: arrowFn(),
|
|
863
|
-
computed: false,
|
|
864
|
-
static: isStatic,
|
|
865
|
-
decorators: []
|
|
866
|
-
}) as never;
|
|
867
|
-
|
|
868
|
-
// ---------------------------------------------------------------------------
|
|
869
|
-
// Mock Context
|
|
870
|
-
// ---------------------------------------------------------------------------
|
|
871
|
-
|
|
872
|
-
/**
|
|
873
|
-
* A collected diagnostic from the mock context's `report`.
|
|
874
|
-
*
|
|
875
|
-
* @since 0.2.0
|
|
876
|
-
*/
|
|
877
|
-
export interface ReportedDiagnostic {
|
|
878
|
-
readonly diagnostic: OxlintDiagnostic;
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
/**
|
|
882
|
-
* Options for creating a mock oxlint Context.
|
|
883
|
-
*
|
|
884
|
-
* @since 0.2.0
|
|
885
|
-
*/
|
|
886
|
-
export interface MockContextOptions {
|
|
887
|
-
readonly filename?: string;
|
|
888
|
-
readonly cwd?: string;
|
|
889
|
-
readonly options?: ReadonlyArray<unknown>;
|
|
890
|
-
readonly sourceText?: string;
|
|
891
|
-
readonly comments?: ReadonlyArray<Comment>;
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
/**
|
|
895
|
-
* Create a mock oxlint `Context` and a diagnostics collector.
|
|
896
|
-
*
|
|
897
|
-
* The mock provides the minimal surface required by `RuleContext.fromOxlintContext`.
|
|
898
|
-
*
|
|
899
|
-
* @since 0.2.0
|
|
900
|
-
*/
|
|
901
|
-
export const createMockContext = (opts: MockContextOptions = {}) => {
|
|
902
|
-
const diagnostics: Array<ReportedDiagnostic> = [];
|
|
903
|
-
const filename = opts.filename ?? '/test/file.ts';
|
|
904
|
-
const cwd = opts.cwd ?? '/test';
|
|
905
|
-
const text = opts.sourceText ?? '';
|
|
906
|
-
const comments = Array.from(opts.comments ?? []);
|
|
907
|
-
const sourceCode = {
|
|
908
|
-
text,
|
|
909
|
-
ast: { type: 'Program', body: [], comments },
|
|
910
|
-
getText() {
|
|
911
|
-
return text;
|
|
912
|
-
},
|
|
913
|
-
getAllComments() {
|
|
914
|
-
return comments;
|
|
915
|
-
},
|
|
916
|
-
getLocFromIndex(index: number) {
|
|
917
|
-
return { line: 1, column: index };
|
|
918
|
-
},
|
|
919
|
-
getIndexFromLoc(loc: { line: number; column: number }) {
|
|
920
|
-
return loc.column;
|
|
921
|
-
},
|
|
922
|
-
getAncestors() {
|
|
923
|
-
return [];
|
|
924
|
-
},
|
|
925
|
-
getScope() {
|
|
926
|
-
return scope();
|
|
927
|
-
},
|
|
928
|
-
getDeclaredVariables() {
|
|
929
|
-
return [];
|
|
930
|
-
},
|
|
931
|
-
isGlobalReference() {
|
|
932
|
-
return false;
|
|
933
|
-
},
|
|
934
|
-
markVariableAsUsed() {
|
|
935
|
-
return false;
|
|
936
|
-
},
|
|
937
|
-
getFirstToken() {
|
|
938
|
-
return null;
|
|
939
|
-
},
|
|
940
|
-
getLastToken() {
|
|
941
|
-
return null;
|
|
942
|
-
},
|
|
943
|
-
getTokens() {
|
|
944
|
-
return [];
|
|
945
|
-
},
|
|
946
|
-
getTokenBefore() {
|
|
947
|
-
return null;
|
|
948
|
-
},
|
|
949
|
-
getTokenAfter() {
|
|
950
|
-
return null;
|
|
951
|
-
},
|
|
952
|
-
getTokensBetween() {
|
|
953
|
-
return [];
|
|
954
|
-
},
|
|
955
|
-
getFirstTokenBetween() {
|
|
956
|
-
return null;
|
|
957
|
-
},
|
|
958
|
-
getTokenByRangeStart() {
|
|
959
|
-
return null;
|
|
960
|
-
},
|
|
961
|
-
getCommentsBefore() {
|
|
962
|
-
return [];
|
|
963
|
-
},
|
|
964
|
-
getCommentsAfter() {
|
|
965
|
-
return [];
|
|
966
|
-
},
|
|
967
|
-
getCommentsInside() {
|
|
968
|
-
return [];
|
|
969
|
-
},
|
|
970
|
-
commentsExistBetween() {
|
|
971
|
-
return false;
|
|
972
|
-
},
|
|
973
|
-
getJSDocComment() {
|
|
974
|
-
return null;
|
|
975
|
-
},
|
|
976
|
-
getNodeByRangeIndex() {
|
|
977
|
-
return null;
|
|
978
|
-
},
|
|
979
|
-
getRange() {
|
|
980
|
-
return [0, 0];
|
|
981
|
-
},
|
|
982
|
-
getLoc() {
|
|
983
|
-
return {
|
|
984
|
-
start: { line: 1, column: 0 },
|
|
985
|
-
end: { line: 1, column: 0 }
|
|
986
|
-
};
|
|
987
|
-
},
|
|
988
|
-
getLines() {
|
|
989
|
-
return text.split('\n');
|
|
990
|
-
},
|
|
991
|
-
isSpaceBetween() {
|
|
992
|
-
return false;
|
|
993
|
-
}
|
|
994
|
-
};
|
|
995
|
-
const context = {
|
|
996
|
-
id: 'effect/test-rule',
|
|
997
|
-
filename,
|
|
998
|
-
physicalFilename: filename,
|
|
999
|
-
cwd,
|
|
1000
|
-
options: opts.options ?? [],
|
|
1001
|
-
report(diagnostic: OxlintDiagnostic) {
|
|
1002
|
-
diagnostics.push({ diagnostic });
|
|
1003
|
-
},
|
|
1004
|
-
getFilename: () => filename,
|
|
1005
|
-
getCwd: () => cwd,
|
|
1006
|
-
sourceCode,
|
|
1007
|
-
languageOptions: {
|
|
1008
|
-
sourceType: 'module',
|
|
1009
|
-
ecmaVersion: 2024
|
|
1010
|
-
},
|
|
1011
|
-
settings: {},
|
|
1012
|
-
getSourceCode: () => sourceCode,
|
|
1013
|
-
getPhysicalFilename: () => filename,
|
|
1014
|
-
parserOptions: {},
|
|
1015
|
-
parserPath: undefined
|
|
1016
|
-
} as unknown as OxlintContext;
|
|
1017
|
-
|
|
1018
|
-
return { context, diagnostics } as const;
|
|
1019
|
-
};
|
|
1020
|
-
|
|
1021
|
-
// ---------------------------------------------------------------------------
|
|
1022
|
-
// Mock RuleContext Layer
|
|
1023
|
-
// ---------------------------------------------------------------------------
|
|
1024
|
-
|
|
1025
|
-
/**
|
|
1026
|
-
* Create a `Layer` that provides a mock `RuleContext` service.
|
|
1027
|
-
*
|
|
1028
|
-
* Use in `it.effect` tests that need to `yield*` visitor handlers
|
|
1029
|
-
* (which carry `RuleContext` in their context type).
|
|
1030
|
-
*
|
|
1031
|
-
* @since 0.2.0
|
|
1032
|
-
*/
|
|
1033
|
-
export const mockRuleContextLayer = (
|
|
1034
|
-
opts?: MockContextOptions
|
|
1035
|
-
): Layer.Layer<RuleContext> => {
|
|
1036
|
-
const { context } = createMockContext(opts);
|
|
1037
|
-
return Layer.succeed(RuleContext, fromOxlintContext(context));
|
|
1038
|
-
};
|
|
1039
|
-
|
|
1040
|
-
/**
|
|
1041
|
-
* Provide a mock `RuleContext` to an effect for testing.
|
|
1042
|
-
*
|
|
1043
|
-
* @since 0.2.0
|
|
1044
|
-
*/
|
|
1045
|
-
export const withMockRuleContext = <A, E>(
|
|
1046
|
-
effect: Effect.Effect<A, E, RuleContext>,
|
|
1047
|
-
opts?: MockContextOptions
|
|
1048
|
-
): Effect.Effect<A, E> => Effect.provide(effect, mockRuleContextLayer(opts));
|
|
1049
|
-
|
|
1050
|
-
// ---------------------------------------------------------------------------
|
|
1051
|
-
// Rule Runner
|
|
1052
|
-
// ---------------------------------------------------------------------------
|
|
1053
|
-
|
|
1054
|
-
/** @internal Call a visitor handler on a mock AST node. */
|
|
1055
|
-
const callHandler = (
|
|
1056
|
-
visitors: OxlintVisitor,
|
|
1057
|
-
key: string,
|
|
1058
|
-
visitorNode: unknown
|
|
1059
|
-
): void => {
|
|
1060
|
-
const handler = visitors[key];
|
|
1061
|
-
// Boundary cast: test nodes are plain objects, not real AST nodes
|
|
1062
|
-
if (handler) handler(visitorNode as never);
|
|
1063
|
-
};
|
|
1064
|
-
|
|
1065
|
-
/**
|
|
1066
|
-
* Run a rule with a single visitor event and collect diagnostics.
|
|
1067
|
-
*
|
|
1068
|
-
* @since 0.2.0
|
|
1069
|
-
*/
|
|
1070
|
-
export const runRule = (
|
|
1071
|
-
rule: CreateRule,
|
|
1072
|
-
visitor: string,
|
|
1073
|
-
visitorNode: unknown,
|
|
1074
|
-
opts?: MockContextOptions
|
|
1075
|
-
): ReadonlyArray<ReportedDiagnostic> => {
|
|
1076
|
-
const { context, diagnostics } = createMockContext(opts);
|
|
1077
|
-
const visitors = rule.create(context);
|
|
1078
|
-
callHandler(visitors, visitor, visitorNode);
|
|
1079
|
-
return diagnostics;
|
|
1080
|
-
};
|
|
1081
|
-
|
|
1082
|
-
/**
|
|
1083
|
-
* Run a rule with multiple visitor/node events sequentially.
|
|
1084
|
-
*
|
|
1085
|
-
* Same context is shared so `Ref` state persists across calls.
|
|
1086
|
-
*
|
|
1087
|
-
* @since 0.2.0
|
|
1088
|
-
*/
|
|
1089
|
-
export const runRuleMulti = (
|
|
1090
|
-
rule: CreateRule,
|
|
1091
|
-
pairs: ReadonlyArray<readonly [visitor: string, node: unknown]>,
|
|
1092
|
-
opts?: MockContextOptions
|
|
1093
|
-
): ReadonlyArray<ReportedDiagnostic> => {
|
|
1094
|
-
const { context, diagnostics } = createMockContext(opts);
|
|
1095
|
-
const visitors = rule.create(context);
|
|
1096
|
-
Arr.forEach(pairs, ([visitor, visitorNode]) => {
|
|
1097
|
-
callHandler(visitors, visitor, visitorNode);
|
|
1098
|
-
});
|
|
1099
|
-
return diagnostics;
|
|
1100
|
-
};
|
|
1101
|
-
|
|
1102
|
-
// ---------------------------------------------------------------------------
|
|
1103
|
-
// Diagnostic Accessors
|
|
1104
|
-
// ---------------------------------------------------------------------------
|
|
1105
|
-
|
|
1106
|
-
/**
|
|
1107
|
-
* Extract the diagnostic messages from a result array.
|
|
1108
|
-
*
|
|
1109
|
-
* Returns `Option.none()` when a diagnostic uses `messageId` instead
|
|
1110
|
-
* of `message`, or when the message is `null`.
|
|
1111
|
-
*
|
|
1112
|
-
* @example
|
|
1113
|
-
* ```ts
|
|
1114
|
-
* import * as Option from 'effect/Option'
|
|
1115
|
-
*
|
|
1116
|
-
* const result = Testing.runRule(rule, 'ThrowStatement', Testing.throwStmt())
|
|
1117
|
-
* expect(Testing.messages(result)).toEqual([Option.some('Use Effect.fail instead')])
|
|
1118
|
-
* ```
|
|
1119
|
-
*
|
|
1120
|
-
* @since 0.2.0
|
|
1121
|
-
*/
|
|
1122
|
-
export const messages = (
|
|
1123
|
-
result: ReadonlyArray<ReportedDiagnostic>
|
|
1124
|
-
): ReadonlyArray<Option.Option<string>> =>
|
|
1125
|
-
Arr.map(result, (r) => Option.fromNullishOr(r.diagnostic.message));
|
|
1126
|
-
|
|
1127
|
-
/**
|
|
1128
|
-
* Extract the diagnostic messageIds from a result array.
|
|
1129
|
-
*
|
|
1130
|
-
* Returns `Option.none()` when a diagnostic uses `message` instead
|
|
1131
|
-
* of `messageId`, or when the messageId is `null`.
|
|
1132
|
-
*
|
|
1133
|
-
* @since 0.2.0
|
|
1134
|
-
*/
|
|
1135
|
-
export const messageIds = (
|
|
1136
|
-
result: ReadonlyArray<ReportedDiagnostic>
|
|
1137
|
-
): ReadonlyArray<Option.Option<string>> =>
|
|
1138
|
-
Arr.map(result, (r) => Option.fromNullishOr(r.diagnostic.messageId));
|
|
1139
|
-
|
|
1140
|
-
// ---------------------------------------------------------------------------
|
|
1141
|
-
// Assertion Helpers
|
|
1142
|
-
// ---------------------------------------------------------------------------
|
|
1143
|
-
|
|
1144
|
-
/**
|
|
1145
|
-
* Assert that diagnostics match expected patterns.
|
|
1146
|
-
*
|
|
1147
|
-
* Each matcher is partially checked — only provided fields are compared.
|
|
1148
|
-
* This allows flexible matching without specifying every field.
|
|
1149
|
-
*
|
|
1150
|
-
* @example
|
|
1151
|
-
* ```ts
|
|
1152
|
-
* Testing.expectDiagnostics(result, [
|
|
1153
|
-
* { message: 'No throw in Effect.gen' },
|
|
1154
|
-
* { messageId: 'noTryCatch' }
|
|
1155
|
-
* ])
|
|
1156
|
-
* ```
|
|
1157
|
-
*
|
|
1158
|
-
* @since 0.2.0
|
|
1159
|
-
*/
|
|
1160
|
-
export const expectDiagnostics = (
|
|
1161
|
-
result: ReadonlyArray<ReportedDiagnostic>,
|
|
1162
|
-
expected: ReadonlyArray<{
|
|
1163
|
-
readonly message?: string;
|
|
1164
|
-
readonly messageId?: string;
|
|
1165
|
-
}>
|
|
1166
|
-
): void => {
|
|
1167
|
-
if (result.length !== expected.length) {
|
|
1168
|
-
throw new Error(
|
|
1169
|
-
`Expected ${expected.length} diagnostics, got ${result.length}:\n` +
|
|
1170
|
-
Arr.join(
|
|
1171
|
-
Arr.map(
|
|
1172
|
-
result,
|
|
1173
|
-
(r) =>
|
|
1174
|
-
` - ${r.diagnostic.message ?? r.diagnostic.messageId ?? '(unknown)'}`
|
|
1175
|
-
),
|
|
1176
|
-
'\n'
|
|
1177
|
-
)
|
|
1178
|
-
);
|
|
1179
|
-
}
|
|
1180
|
-
Arr.forEach(expected, (exp, i) => {
|
|
1181
|
-
const actual = result[i];
|
|
1182
|
-
if (actual === undefined) {
|
|
1183
|
-
throw new Error(`Missing diagnostic at index ${i}`);
|
|
1184
|
-
}
|
|
1185
|
-
if (
|
|
1186
|
-
exp.message !== undefined &&
|
|
1187
|
-
actual.diagnostic.message !== exp.message
|
|
1188
|
-
) {
|
|
1189
|
-
throw new Error(
|
|
1190
|
-
`Diagnostic ${i}: expected message "${exp.message}", got "${actual.diagnostic.message}"`
|
|
1191
|
-
);
|
|
1192
|
-
}
|
|
1193
|
-
if (
|
|
1194
|
-
exp.messageId !== undefined &&
|
|
1195
|
-
actual.diagnostic.messageId !== exp.messageId
|
|
1196
|
-
) {
|
|
1197
|
-
throw new Error(
|
|
1198
|
-
`Diagnostic ${i}: expected messageId "${exp.messageId}", got "${actual.diagnostic.messageId}"`
|
|
1199
|
-
);
|
|
1200
|
-
}
|
|
1201
|
-
});
|
|
1202
|
-
};
|
|
1203
|
-
|
|
1204
|
-
/**
|
|
1205
|
-
* Assert that no diagnostics were reported.
|
|
1206
|
-
*
|
|
1207
|
-
* @since 0.2.0
|
|
1208
|
-
*/
|
|
1209
|
-
export const expectNoDiagnostics = (
|
|
1210
|
-
result: ReadonlyArray<ReportedDiagnostic>
|
|
1211
|
-
): void =>
|
|
1212
|
-
Arr.match(result, {
|
|
1213
|
-
onEmpty: () => {},
|
|
1214
|
-
onNonEmpty: (items) => {
|
|
1215
|
-
throw new Error(
|
|
1216
|
-
`Expected no diagnostics, got ${items.length}:\n` +
|
|
1217
|
-
Arr.join(
|
|
1218
|
-
Arr.map(
|
|
1219
|
-
items,
|
|
1220
|
-
(r) =>
|
|
1221
|
-
` - ${r.diagnostic.message ?? r.diagnostic.messageId ?? '(unknown)'}`
|
|
1222
|
-
),
|
|
1223
|
-
'\n'
|
|
1224
|
-
)
|
|
1225
|
-
);
|
|
1226
|
-
}
|
|
1227
|
-
});
|