@ttsc/lint 0.10.1 → 0.11.0-dev.20260517
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 +172 -33
- package/lib/defineConfig.d.ts +55 -0
- package/lib/defineConfig.js +39 -0
- package/lib/defineConfig.js.map +1 -0
- package/lib/index.d.ts +25 -1
- package/lib/index.js +622 -2
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintPlugin.d.ts +53 -0
- package/lib/structures/ITtscLintPlugin.js +3 -0
- package/lib/structures/ITtscLintPlugin.js.map +1 -0
- package/lib/structures/ITtscLintPluginConfig.d.ts +82 -5
- package/lib/structures/ITtscLintPluginMeta.d.ts +12 -0
- package/lib/structures/ITtscLintPluginMeta.js +3 -0
- package/lib/structures/ITtscLintPluginMeta.js.map +1 -0
- package/lib/structures/PluginRuleNames.d.ts +14 -0
- package/lib/structures/PluginRuleNames.js +3 -0
- package/lib/structures/PluginRuleNames.js.map +1 -0
- package/lib/structures/TtscLintConfig.d.ts +24 -8
- package/lib/structures/TtscLintConfigEntry.d.ts +39 -0
- package/lib/structures/TtscLintConfigEntry.js +3 -0
- package/lib/structures/TtscLintConfigEntry.js.map +1 -0
- package/lib/structures/TtscLintFormatConfig.d.ts +154 -0
- package/lib/structures/TtscLintFormatConfig.js +3 -0
- package/lib/structures/TtscLintFormatConfig.js.map +1 -0
- package/lib/structures/TtscLintPlugins.d.ts +9 -0
- package/lib/structures/TtscLintPlugins.js +3 -0
- package/lib/structures/TtscLintPlugins.js.map +1 -0
- package/lib/structures/TtscLintRule.d.ts +1 -1
- package/lib/structures/TtscLintRuleEntry.d.ts +22 -0
- package/lib/structures/TtscLintRuleEntry.js +3 -0
- package/lib/structures/TtscLintRuleEntry.js.map +1 -0
- package/lib/structures/TtscLintRuleMap.d.ts +44 -0
- package/lib/structures/TtscLintRuleMap.js +3 -0
- package/lib/structures/TtscLintRuleMap.js.map +1 -0
- package/lib/structures/TtscLintRuleOptions.d.ts +149 -0
- package/lib/structures/TtscLintRuleOptions.js +3 -0
- package/lib/structures/TtscLintRuleOptions.js.map +1 -0
- package/lib/structures/defaultFormat.d.ts +31 -0
- package/lib/structures/defaultFormat.js +35 -0
- package/lib/structures/defaultFormat.js.map +1 -0
- package/lib/structures/index.d.ts +10 -0
- package/lib/structures/index.js +10 -0
- package/lib/structures/index.js.map +1 -1
- package/{plugin → linthost}/ast_helpers.go +92 -1
- package/{plugin → linthost}/compile.go +1 -1
- package/{plugin → linthost}/config.go +583 -66
- package/linthost/config_format.go +360 -0
- package/linthost/contrib_adapter.go +134 -0
- package/{plugin → linthost}/directives.go +1 -1
- package/linthost/dispatch.go +62 -0
- package/{plugin → linthost}/engine.go +156 -11
- package/{plugin → linthost}/eslint_runtime.go +76 -2
- package/linthost/fix.go +249 -0
- package/linthost/format.go +107 -0
- package/{plugin → linthost}/host.go +1 -1
- package/linthost/print_dispatch.go +118 -0
- package/linthost/print_doc.go +168 -0
- package/linthost/print_engine.go +343 -0
- package/linthost/print_nodes_array.go +46 -0
- package/linthost/print_nodes_call.go +180 -0
- package/linthost/print_nodes_imports.go +171 -0
- package/linthost/print_nodes_list.go +64 -0
- package/linthost/print_nodes_object.go +51 -0
- package/{plugin → linthost}/rules_arrays.go +1 -1
- package/{plugin → linthost}/rules_console.go +1 -1
- package/{plugin → linthost}/rules_debugger.go +1 -1
- package/{plugin → linthost}/rules_dupes.go +1 -1
- package/{plugin → linthost}/rules_empty.go +1 -1
- package/linthost/rules_escape.go +238 -0
- package/{plugin → linthost}/rules_eval.go +1 -1
- package/{plugin → linthost}/rules_finally.go +1 -1
- package/linthost/rules_format_jsdoc.go +230 -0
- package/linthost/rules_format_print_width.go +332 -0
- package/linthost/rules_format_quotes.go +219 -0
- package/linthost/rules_format_semi.go +190 -0
- package/linthost/rules_format_sort_imports.go +462 -0
- package/linthost/rules_format_trailing_comma.go +460 -0
- package/{plugin → linthost}/rules_gap.go +101 -6
- package/linthost/rules_imports.go +101 -0
- package/{plugin → linthost}/rules_logic.go +55 -3
- package/{plugin → linthost}/rules_loops.go +1 -1
- package/{plugin → linthost}/rules_misc.go +1 -1
- package/linthost/rules_params.go +64 -0
- package/{plugin → linthost}/rules_problems.go +10 -2
- package/linthost/rules_promise.go +111 -0
- package/{plugin → linthost}/rules_protos.go +1 -1
- package/{plugin → linthost}/rules_self.go +1 -1
- package/{plugin → linthost}/rules_strings.go +1 -1
- package/{plugin → linthost}/rules_suggestions.go +51 -9
- package/{plugin → linthost}/rules_throw.go +1 -1
- package/{plugin → linthost}/rules_ts.go +12 -2
- package/{plugin → linthost}/rules_ts_extra.go +90 -4
- package/{plugin → linthost}/rules_var.go +36 -5
- package/package.json +8 -6
- package/plugin/main.go +14 -27
- package/rule/astutil/astutil.go +148 -0
- package/rule/rule.go +291 -0
- package/src/defineConfig.ts +69 -0
- package/src/index.ts +807 -3
- package/src/structures/ITtscLintPlugin.ts +56 -0
- package/src/structures/ITtscLintPluginConfig.ts +86 -5
- package/src/structures/ITtscLintPluginMeta.ts +14 -0
- package/src/structures/PluginRuleNames.ts +19 -0
- package/src/structures/TtscLintConfig.ts +27 -8
- package/src/structures/TtscLintConfigEntry.ts +50 -0
- package/src/structures/TtscLintFormatConfig.ts +169 -0
- package/src/structures/TtscLintPlugins.ts +10 -0
- package/src/structures/TtscLintRule.ts +14 -1
- package/src/structures/TtscLintRuleEntry.ts +28 -0
- package/src/structures/TtscLintRuleMap.ts +52 -0
- package/src/structures/TtscLintRuleOptions.ts +162 -0
- package/src/structures/defaultFormat.ts +33 -0
- package/src/structures/index.ts +10 -0
|
@@ -0,0 +1,460 @@
|
|
|
1
|
+
package linthost
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
// format/trailing-comma adds trailing commas to multi-line lists. Mirrors
|
|
8
|
+
// prettier's `trailingComma: "all"` default — *not* a tunable.
|
|
9
|
+
//
|
|
10
|
+
// Scope (intentionally narrower than the closing-brace surface of TS):
|
|
11
|
+
//
|
|
12
|
+
// - ArrayLiteralExpression `[a, b]`
|
|
13
|
+
// - ObjectLiteralExpression `{ a: 1 }`
|
|
14
|
+
// - CallExpression / NewExpression `foo(a, b)` / `new Foo(a, b)`
|
|
15
|
+
// - NamedImports / NamedExports `import { a, b } from "x"`
|
|
16
|
+
// - TupleType `[A, B]` at the type level
|
|
17
|
+
// - Function parameter lists `function foo(a, b) {}` etc.,
|
|
18
|
+
// including interface call/construct/method signatures and
|
|
19
|
+
// `(a, b) => …` / `new (a, b) => …` function/constructor type
|
|
20
|
+
// literals.
|
|
21
|
+
// - Type-parameter declaration lists `<T, U>` at the declaration site
|
|
22
|
+
// (prettier omits trailing commas on type-argument call sites such
|
|
23
|
+
// as `foo<A, B>(…)`; see prettier PR #10353).
|
|
24
|
+
//
|
|
25
|
+
// Out of scope on purpose:
|
|
26
|
+
//
|
|
27
|
+
// - Destructuring binding patterns. The last element may be a rest
|
|
28
|
+
// pattern, where a trailing comma is a syntax error.
|
|
29
|
+
// - JSX attribute lists. Prettier does not apply trailing commas there
|
|
30
|
+
// either.
|
|
31
|
+
//
|
|
32
|
+
// Rest parameters (`...rest`) are explicitly skipped: ECMAScript forbids
|
|
33
|
+
// a trailing comma after a rest element (TS1013), so the rule must not
|
|
34
|
+
// insert one even when the rest parameter is the multi-line list's last
|
|
35
|
+
// element. The same restriction applies to rest binding patterns, which
|
|
36
|
+
// the rule does not visit at all.
|
|
37
|
+
//
|
|
38
|
+
// Unparenthesized arrow parameters (`a => …`) are also skipped: there is
|
|
39
|
+
// no parameter-list paren to anchor the comma against, and ECMAScript has
|
|
40
|
+
// no place to insert one. `findCloseTokenAfter` bails on the first
|
|
41
|
+
// non-trivia byte after the parameter's `End()` (the `=>` token), so the
|
|
42
|
+
// rule abstains without emitting an edit.
|
|
43
|
+
type formatTrailingComma struct{}
|
|
44
|
+
|
|
45
|
+
// formatTrailingCommaOptions mirrors `TtscLintRuleOptions.TrailingComma`.
|
|
46
|
+
type formatTrailingCommaOptions struct {
|
|
47
|
+
Mode string `json:"mode"`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
func (formatTrailingComma) Name() string { return "format/trailing-comma" }
|
|
51
|
+
func (formatTrailingComma) IsFormat() bool { return true }
|
|
52
|
+
|
|
53
|
+
func (formatTrailingComma) Visits() []shimast.Kind {
|
|
54
|
+
return []shimast.Kind{
|
|
55
|
+
shimast.KindArrayLiteralExpression,
|
|
56
|
+
shimast.KindObjectLiteralExpression,
|
|
57
|
+
shimast.KindCallExpression,
|
|
58
|
+
shimast.KindNewExpression,
|
|
59
|
+
shimast.KindNamedImports,
|
|
60
|
+
shimast.KindNamedExports,
|
|
61
|
+
shimast.KindTupleType,
|
|
62
|
+
shimast.KindFunctionDeclaration,
|
|
63
|
+
shimast.KindFunctionExpression,
|
|
64
|
+
shimast.KindArrowFunction,
|
|
65
|
+
shimast.KindMethodDeclaration,
|
|
66
|
+
shimast.KindConstructor,
|
|
67
|
+
shimast.KindGetAccessor,
|
|
68
|
+
shimast.KindSetAccessor,
|
|
69
|
+
shimast.KindMethodSignature,
|
|
70
|
+
shimast.KindCallSignature,
|
|
71
|
+
shimast.KindConstructSignature,
|
|
72
|
+
shimast.KindFunctionType,
|
|
73
|
+
shimast.KindConstructorType,
|
|
74
|
+
shimast.KindTypeParameter,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
func (formatTrailingComma) Check(ctx *Context, node *shimast.Node) {
|
|
79
|
+
if ctx == nil || ctx.File == nil || node == nil {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
var opts formatTrailingCommaOptions
|
|
83
|
+
_ = ctx.DecodeOptions(&opts)
|
|
84
|
+
mode := opts.Mode
|
|
85
|
+
if mode == "" {
|
|
86
|
+
mode = "all"
|
|
87
|
+
}
|
|
88
|
+
if mode == "none" {
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
switch node.Kind {
|
|
92
|
+
case shimast.KindArrayLiteralExpression:
|
|
93
|
+
arr := node.AsArrayLiteralExpression()
|
|
94
|
+
if arr == nil {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
considerTrailingComma(ctx, arr.Elements, node.End()-1)
|
|
98
|
+
case shimast.KindObjectLiteralExpression:
|
|
99
|
+
obj := node.AsObjectLiteralExpression()
|
|
100
|
+
if obj == nil {
|
|
101
|
+
return
|
|
102
|
+
}
|
|
103
|
+
considerTrailingComma(ctx, obj.Properties, node.End()-1)
|
|
104
|
+
case shimast.KindCallExpression:
|
|
105
|
+
if mode == "es5" {
|
|
106
|
+
return // prettier's es5 mode skips call arguments
|
|
107
|
+
}
|
|
108
|
+
call := node.AsCallExpression()
|
|
109
|
+
if call == nil {
|
|
110
|
+
return
|
|
111
|
+
}
|
|
112
|
+
considerTrailingComma(ctx, call.Arguments, node.End()-1)
|
|
113
|
+
case shimast.KindNewExpression:
|
|
114
|
+
if mode == "es5" {
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
ne := node.AsNewExpression()
|
|
118
|
+
if ne == nil || ne.Arguments == nil {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
121
|
+
considerTrailingComma(ctx, ne.Arguments, node.End()-1)
|
|
122
|
+
case shimast.KindNamedImports:
|
|
123
|
+
named := node.AsNamedImports()
|
|
124
|
+
if named == nil {
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
considerTrailingComma(ctx, named.Elements, node.End()-1)
|
|
128
|
+
case shimast.KindNamedExports:
|
|
129
|
+
named := node.AsNamedExports()
|
|
130
|
+
if named == nil {
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
considerTrailingComma(ctx, named.Elements, node.End()-1)
|
|
134
|
+
case shimast.KindTupleType:
|
|
135
|
+
if mode == "es5" {
|
|
136
|
+
return // tuple types are type-level; ES5 mode is runtime-only
|
|
137
|
+
}
|
|
138
|
+
tup := node.AsTupleTypeNode()
|
|
139
|
+
if tup == nil {
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
considerTrailingComma(ctx, tup.Elements, node.End()-1)
|
|
143
|
+
case shimast.KindFunctionDeclaration:
|
|
144
|
+
if mode == "es5" {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
fn := node.AsFunctionDeclaration()
|
|
148
|
+
if fn == nil {
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
considerFunctionParameterComma(ctx, fn.Parameters)
|
|
152
|
+
case shimast.KindFunctionExpression:
|
|
153
|
+
if mode == "es5" {
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
fn := node.AsFunctionExpression()
|
|
157
|
+
if fn == nil {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
considerFunctionParameterComma(ctx, fn.Parameters)
|
|
161
|
+
case shimast.KindArrowFunction:
|
|
162
|
+
if mode == "es5" {
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
fn := node.AsArrowFunction()
|
|
166
|
+
if fn == nil {
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
considerFunctionParameterComma(ctx, fn.Parameters)
|
|
170
|
+
case shimast.KindMethodDeclaration:
|
|
171
|
+
if mode == "es5" {
|
|
172
|
+
return
|
|
173
|
+
}
|
|
174
|
+
fn := node.AsMethodDeclaration()
|
|
175
|
+
if fn == nil {
|
|
176
|
+
return
|
|
177
|
+
}
|
|
178
|
+
considerFunctionParameterComma(ctx, fn.Parameters)
|
|
179
|
+
case shimast.KindConstructor:
|
|
180
|
+
if mode == "es5" {
|
|
181
|
+
return
|
|
182
|
+
}
|
|
183
|
+
fn := node.AsConstructorDeclaration()
|
|
184
|
+
if fn == nil {
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
considerFunctionParameterComma(ctx, fn.Parameters)
|
|
188
|
+
case shimast.KindGetAccessor:
|
|
189
|
+
if mode == "es5" {
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
fn := node.AsGetAccessorDeclaration()
|
|
193
|
+
if fn == nil {
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
considerFunctionParameterComma(ctx, fn.Parameters)
|
|
197
|
+
case shimast.KindSetAccessor:
|
|
198
|
+
if mode == "es5" {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
fn := node.AsSetAccessorDeclaration()
|
|
202
|
+
if fn == nil {
|
|
203
|
+
return
|
|
204
|
+
}
|
|
205
|
+
considerFunctionParameterComma(ctx, fn.Parameters)
|
|
206
|
+
case shimast.KindMethodSignature:
|
|
207
|
+
if mode == "es5" {
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
sig := node.AsMethodSignatureDeclaration()
|
|
211
|
+
if sig == nil {
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
considerFunctionParameterComma(ctx, sig.Parameters)
|
|
215
|
+
case shimast.KindCallSignature:
|
|
216
|
+
if mode == "es5" {
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
sig := node.AsCallSignatureDeclaration()
|
|
220
|
+
if sig == nil {
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
considerFunctionParameterComma(ctx, sig.Parameters)
|
|
224
|
+
case shimast.KindConstructSignature:
|
|
225
|
+
if mode == "es5" {
|
|
226
|
+
return
|
|
227
|
+
}
|
|
228
|
+
sig := node.AsConstructSignatureDeclaration()
|
|
229
|
+
if sig == nil {
|
|
230
|
+
return
|
|
231
|
+
}
|
|
232
|
+
considerFunctionParameterComma(ctx, sig.Parameters)
|
|
233
|
+
case shimast.KindFunctionType:
|
|
234
|
+
if mode == "es5" {
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
ft := node.AsFunctionTypeNode()
|
|
238
|
+
if ft == nil {
|
|
239
|
+
return
|
|
240
|
+
}
|
|
241
|
+
considerFunctionParameterComma(ctx, ft.Parameters)
|
|
242
|
+
case shimast.KindConstructorType:
|
|
243
|
+
if mode == "es5" {
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
ct := node.AsConstructorTypeNode()
|
|
247
|
+
if ct == nil {
|
|
248
|
+
return
|
|
249
|
+
}
|
|
250
|
+
considerFunctionParameterComma(ctx, ct.Parameters)
|
|
251
|
+
case shimast.KindTypeParameter:
|
|
252
|
+
if mode == "es5" {
|
|
253
|
+
return // type parameters postdate ES5; prettier es5 skips them
|
|
254
|
+
}
|
|
255
|
+
if node.Parent == nil {
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
list := node.Parent.TypeParameterList()
|
|
259
|
+
if list == nil || len(list.Nodes) == 0 || list.Nodes[len(list.Nodes)-1] != node {
|
|
260
|
+
return
|
|
261
|
+
}
|
|
262
|
+
src := ctx.File.Text()
|
|
263
|
+
closePos := findCloseTokenAfter(src, list.End(), '>')
|
|
264
|
+
if closePos < 0 {
|
|
265
|
+
return
|
|
266
|
+
}
|
|
267
|
+
considerTrailingComma(ctx, list, closePos)
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// considerTrailingComma reports a fix when the bracket-delimited list is
|
|
272
|
+
// multi-line and missing its trailing comma. `closeBracketPos` points at
|
|
273
|
+
// the closing punctuation byte itself (e.g. the `]` of an array literal).
|
|
274
|
+
//
|
|
275
|
+
// "Multi-line" means the close bracket sits on a different line from the
|
|
276
|
+
// last element's end — not just "the list contains a newline somewhere".
|
|
277
|
+
// Prettier's `trailingComma: "all"` omits the comma whenever the close
|
|
278
|
+
// bracket is adjacent to the last element on the same line, even if the
|
|
279
|
+
// element itself is internally multi-line. The canonical shape is
|
|
280
|
+
// `JSON.stringify({ ... })` where the object argument spans many lines
|
|
281
|
+
// but `}` and `)` collapse onto one line; inserting `,)` there would be
|
|
282
|
+
// stylistically wrong and, for parameter rest-paths nearby, can shift
|
|
283
|
+
// later diagnostics.
|
|
284
|
+
func considerTrailingComma(ctx *Context, list *shimast.NodeList, closeBracketPos int) {
|
|
285
|
+
if list == nil || len(list.Nodes) == 0 {
|
|
286
|
+
return
|
|
287
|
+
}
|
|
288
|
+
last := list.Nodes[len(list.Nodes)-1]
|
|
289
|
+
if last == nil {
|
|
290
|
+
return
|
|
291
|
+
}
|
|
292
|
+
src := ctx.File.Text()
|
|
293
|
+
if closeBracketPos < 0 || closeBracketPos >= len(src) {
|
|
294
|
+
return
|
|
295
|
+
}
|
|
296
|
+
if !rangeSpansMultipleLines(src, last.End(), closeBracketPos) {
|
|
297
|
+
return
|
|
298
|
+
}
|
|
299
|
+
if rangeHasTrailingComma(src, last.End(), closeBracketPos) {
|
|
300
|
+
return
|
|
301
|
+
}
|
|
302
|
+
ctx.ReportRangeFix(
|
|
303
|
+
last.End()-1,
|
|
304
|
+
last.End(),
|
|
305
|
+
"Missing trailing comma.",
|
|
306
|
+
TextEdit{Pos: last.End(), End: last.End(), Text: ","},
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// considerFunctionParameterComma reuses the same trailing-comma logic for
|
|
311
|
+
// a parameter list. The closing `)` lives between the parameter list's
|
|
312
|
+
// End() and the next non-trivia byte; rather than carry token positions
|
|
313
|
+
// around, the scanner walks forward in source until it finds the close
|
|
314
|
+
// paren.
|
|
315
|
+
//
|
|
316
|
+
// Rest parameters short-circuit the insert: `function f(a, ...rest,)` is
|
|
317
|
+
// a TS1013 syntax error. The rule peeks the last element's
|
|
318
|
+
// DotDotDotToken and bails when set, since the rest must remain the
|
|
319
|
+
// terminal element with no following comma.
|
|
320
|
+
func considerFunctionParameterComma(ctx *Context, list *shimast.NodeList) {
|
|
321
|
+
if list == nil || len(list.Nodes) == 0 {
|
|
322
|
+
return
|
|
323
|
+
}
|
|
324
|
+
if lastParameterIsRest(list) {
|
|
325
|
+
return
|
|
326
|
+
}
|
|
327
|
+
src := ctx.File.Text()
|
|
328
|
+
closePos := findCloseTokenAfter(src, list.End(), ')')
|
|
329
|
+
if closePos < 0 {
|
|
330
|
+
return
|
|
331
|
+
}
|
|
332
|
+
considerTrailingComma(ctx, list, closePos)
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// lastParameterIsRest reports whether the parameter list ends with a
|
|
336
|
+
// rest element (`...rest`). ECMAScript syntax disallows a trailing
|
|
337
|
+
// comma after a rest element; the rule must not insert one.
|
|
338
|
+
func lastParameterIsRest(list *shimast.NodeList) bool {
|
|
339
|
+
last := list.Nodes[len(list.Nodes)-1]
|
|
340
|
+
if last == nil {
|
|
341
|
+
return false
|
|
342
|
+
}
|
|
343
|
+
param := last.AsParameterDeclaration()
|
|
344
|
+
if param == nil {
|
|
345
|
+
return false
|
|
346
|
+
}
|
|
347
|
+
return param.DotDotDotToken != nil
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// findCloseTokenAfter returns the byte offset of the first `target`
|
|
351
|
+
// punctuation byte immediately after `start`, allowing only whitespace
|
|
352
|
+
// and comments in between. Any other byte means the caller's expected
|
|
353
|
+
// close token is not the next non-trivia token, and the function returns
|
|
354
|
+
// -1.
|
|
355
|
+
//
|
|
356
|
+
// The strict "trivia-only" contract is load-bearing for the sole caller
|
|
357
|
+
// `considerFunctionParameterComma`. An unparenthesized arrow parameter
|
|
358
|
+
// `a => …` has no opening paren, so a looser scanner would walk past
|
|
359
|
+
// `=>` and the body and land on some unrelated `)` (e.g. the close of
|
|
360
|
+
// an outer call), causing the rule to insert a spurious `,` after the
|
|
361
|
+
// parameter and produce a syntax error like `a, =>`. Bailing on any
|
|
362
|
+
// non-trivia byte cleanly suppresses the rule in those cases.
|
|
363
|
+
func findCloseTokenAfter(src string, start int, target byte) int {
|
|
364
|
+
for i := start; i < len(src); i++ {
|
|
365
|
+
c := src[i]
|
|
366
|
+
if c == target {
|
|
367
|
+
return i
|
|
368
|
+
}
|
|
369
|
+
if c == ' ' || c == '\t' || c == '\r' || c == '\n' {
|
|
370
|
+
continue
|
|
371
|
+
}
|
|
372
|
+
if c == '/' && i+1 < len(src) {
|
|
373
|
+
if src[i+1] == '/' {
|
|
374
|
+
for i < len(src) && src[i] != '\n' {
|
|
375
|
+
i++
|
|
376
|
+
}
|
|
377
|
+
continue
|
|
378
|
+
}
|
|
379
|
+
if src[i+1] == '*' {
|
|
380
|
+
i += 2
|
|
381
|
+
for i+1 < len(src) && !(src[i] == '*' && src[i+1] == '/') {
|
|
382
|
+
i++
|
|
383
|
+
}
|
|
384
|
+
if i+1 < len(src) {
|
|
385
|
+
i++ // step past '*/'
|
|
386
|
+
}
|
|
387
|
+
continue
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return -1
|
|
391
|
+
}
|
|
392
|
+
return -1
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// rangeSpansMultipleLines reports whether the source between two byte
|
|
396
|
+
// offsets contains at least one `\n`.
|
|
397
|
+
func rangeSpansMultipleLines(src string, a, b int) bool {
|
|
398
|
+
if a > b {
|
|
399
|
+
a, b = b, a
|
|
400
|
+
}
|
|
401
|
+
if a < 0 {
|
|
402
|
+
a = 0
|
|
403
|
+
}
|
|
404
|
+
if b > len(src) {
|
|
405
|
+
b = len(src)
|
|
406
|
+
}
|
|
407
|
+
for i := a; i < b; i++ {
|
|
408
|
+
if src[i] == '\n' {
|
|
409
|
+
return true
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
return false
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// rangeHasTrailingComma scans the source between the last item's end and
|
|
416
|
+
// the close bracket. Returns true if a `,` is the first non-whitespace,
|
|
417
|
+
// non-comment byte. Comments after the trailing comma still count as
|
|
418
|
+
// "comma present".
|
|
419
|
+
func rangeHasTrailingComma(src string, start, end int) bool {
|
|
420
|
+
if start < 0 {
|
|
421
|
+
start = 0
|
|
422
|
+
}
|
|
423
|
+
if end > len(src) {
|
|
424
|
+
end = len(src)
|
|
425
|
+
}
|
|
426
|
+
for i := start; i < end; {
|
|
427
|
+
c := src[i]
|
|
428
|
+
if c == ',' {
|
|
429
|
+
return true
|
|
430
|
+
}
|
|
431
|
+
if c == ' ' || c == '\t' || c == '\r' || c == '\n' {
|
|
432
|
+
i++
|
|
433
|
+
continue
|
|
434
|
+
}
|
|
435
|
+
if c == '/' && i+1 < end {
|
|
436
|
+
if src[i+1] == '/' {
|
|
437
|
+
for i < end && src[i] != '\n' {
|
|
438
|
+
i++
|
|
439
|
+
}
|
|
440
|
+
continue
|
|
441
|
+
}
|
|
442
|
+
if src[i+1] == '*' {
|
|
443
|
+
i += 2
|
|
444
|
+
for i+1 < end && !(src[i] == '*' && src[i+1] == '/') {
|
|
445
|
+
i++
|
|
446
|
+
}
|
|
447
|
+
if i+1 < end {
|
|
448
|
+
i += 2
|
|
449
|
+
}
|
|
450
|
+
continue
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return false
|
|
454
|
+
}
|
|
455
|
+
return false
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
func init() {
|
|
459
|
+
Register(formatTrailingComma{})
|
|
460
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
package
|
|
1
|
+
package linthost
|
|
2
2
|
|
|
3
3
|
import shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
4
4
|
|
|
@@ -149,9 +149,22 @@ func (noUnnecessaryTypeConstraint) Check(ctx *Context, node *shimast.Node) {
|
|
|
149
149
|
if param == nil || param.Constraint == nil {
|
|
150
150
|
return
|
|
151
151
|
}
|
|
152
|
-
if param.Constraint.Kind
|
|
153
|
-
|
|
152
|
+
if param.Constraint.Kind != shimast.KindAnyKeyword && param.Constraint.Kind != shimast.KindUnknownKeyword {
|
|
153
|
+
return
|
|
154
|
+
}
|
|
155
|
+
message := "Constraining a type parameter to any or unknown is unnecessary."
|
|
156
|
+
// Delete the entire ` extends any|unknown` clause by spanning from the
|
|
157
|
+
// end of the parameter's name to the end of the constraint node.
|
|
158
|
+
name := param.Name()
|
|
159
|
+
if name == nil {
|
|
160
|
+
ctx.Report(param.Constraint, message)
|
|
161
|
+
return
|
|
154
162
|
}
|
|
163
|
+
ctx.ReportFix(
|
|
164
|
+
param.Constraint,
|
|
165
|
+
message,
|
|
166
|
+
TextEdit{Pos: name.End(), End: param.Constraint.End(), Text: ""},
|
|
167
|
+
)
|
|
155
168
|
}
|
|
156
169
|
|
|
157
170
|
// no-unsafe-function-type: `Function` accepts any callable shape.
|
|
@@ -180,10 +193,92 @@ func (noWrapperObjectTypes) Check(ctx *Context, node *shimast.Node) {
|
|
|
180
193
|
if ref == nil {
|
|
181
194
|
return
|
|
182
195
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
196
|
+
name := identifierText(ref.TypeName)
|
|
197
|
+
primitive, ok := wrapperPrimitive(name)
|
|
198
|
+
if !ok {
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
message := "Use primitive type keywords instead of wrapper object types."
|
|
202
|
+
// Shadow guard: if the user has declared `type String = …`,
|
|
203
|
+
// `interface String { … }`, or `class String { … }` at file scope,
|
|
204
|
+
// their `String` is NOT the global wrapper and a rewrite to `string`
|
|
205
|
+
// would change the type. Pre-existing detection issue, but the fix
|
|
206
|
+
// would silently corrupt the type — bail to detection-only when a
|
|
207
|
+
// shadowing declaration exists in the same file.
|
|
208
|
+
if fileShadowsWrapperName(ctx.File, name) {
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
// `Object` maps to `object` in ESLint's fix, but the semantics shift
|
|
212
|
+
// enough (boxed object vs lower-case object type) that we surface the
|
|
213
|
+
// diagnostic without a fix and leave the rewrite to the author.
|
|
214
|
+
if name == "Object" {
|
|
215
|
+
ctx.Report(node, message)
|
|
216
|
+
return
|
|
217
|
+
}
|
|
218
|
+
pos, end := tokenRange(ctx.File, ref.TypeName)
|
|
219
|
+
if pos < 0 {
|
|
220
|
+
ctx.Report(node, message)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
ctx.ReportFix(
|
|
224
|
+
node,
|
|
225
|
+
message,
|
|
226
|
+
TextEdit{Pos: pos, End: end, Text: primitive},
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// fileShadowsWrapperName reports whether the source file declares its own
|
|
231
|
+
// `String`/`Number`/`Boolean`/`Symbol`/`BigInt`/`Object` at top level via
|
|
232
|
+
// `type`, `interface`, or `class`. Walks SourceFile.Statements once; no
|
|
233
|
+
// memoization because the rule already runs once per TypeReference and
|
|
234
|
+
// the average statement count per file dominates the cost over the inner
|
|
235
|
+
// loop.
|
|
236
|
+
func fileShadowsWrapperName(file *shimast.SourceFile, name string) bool {
|
|
237
|
+
if file == nil || file.Statements == nil {
|
|
238
|
+
return false
|
|
239
|
+
}
|
|
240
|
+
for _, stmt := range file.Statements.Nodes {
|
|
241
|
+
if stmt == nil {
|
|
242
|
+
continue
|
|
243
|
+
}
|
|
244
|
+
var declName *shimast.Node
|
|
245
|
+
switch stmt.Kind {
|
|
246
|
+
case shimast.KindTypeAliasDeclaration:
|
|
247
|
+
if alias := stmt.AsTypeAliasDeclaration(); alias != nil {
|
|
248
|
+
declName = alias.Name()
|
|
249
|
+
}
|
|
250
|
+
case shimast.KindInterfaceDeclaration:
|
|
251
|
+
if iface := stmt.AsInterfaceDeclaration(); iface != nil {
|
|
252
|
+
declName = iface.Name()
|
|
253
|
+
}
|
|
254
|
+
case shimast.KindClassDeclaration:
|
|
255
|
+
if cls := stmt.AsClassDeclaration(); cls != nil {
|
|
256
|
+
declName = cls.Name()
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if declName != nil && identifierText(declName) == name {
|
|
260
|
+
return true
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
return false
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
func wrapperPrimitive(name string) (string, bool) {
|
|
267
|
+
switch name {
|
|
268
|
+
case "String":
|
|
269
|
+
return "string", true
|
|
270
|
+
case "Number":
|
|
271
|
+
return "number", true
|
|
272
|
+
case "Boolean":
|
|
273
|
+
return "boolean", true
|
|
274
|
+
case "Symbol":
|
|
275
|
+
return "symbol", true
|
|
276
|
+
case "BigInt":
|
|
277
|
+
return "bigint", true
|
|
278
|
+
case "Object":
|
|
279
|
+
return "object", true
|
|
186
280
|
}
|
|
281
|
+
return "", false
|
|
187
282
|
}
|
|
188
283
|
|
|
189
284
|
// no-useless-constructor: an empty constructor with no parameters is noise.
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
package linthost
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
5
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
6
|
+
)
|
|
7
|
+
|
|
8
|
+
// no-import-type-side-effects: an import whose every named specifier
|
|
9
|
+
// uses the inline `type` modifier is morally a type-only import; hoist
|
|
10
|
+
// the `type` to the import clause to make that intent explicit and let
|
|
11
|
+
// `verbatimModuleSyntax` elide the import entirely. typescript-eslint
|
|
12
|
+
// stylistic: https://typescript-eslint.io/rules/no-import-type-side-effects/
|
|
13
|
+
type noImportTypeSideEffects struct{}
|
|
14
|
+
|
|
15
|
+
func (noImportTypeSideEffects) Name() string { return "no-import-type-side-effects" }
|
|
16
|
+
func (noImportTypeSideEffects) Visits() []shimast.Kind {
|
|
17
|
+
return []shimast.Kind{shimast.KindImportDeclaration}
|
|
18
|
+
}
|
|
19
|
+
func (noImportTypeSideEffects) Check(ctx *Context, node *shimast.Node) {
|
|
20
|
+
decl := node.AsImportDeclaration()
|
|
21
|
+
if decl == nil || decl.ImportClause == nil {
|
|
22
|
+
return
|
|
23
|
+
}
|
|
24
|
+
clause := decl.ImportClause.AsImportClause()
|
|
25
|
+
if clause == nil {
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
// `import type { … }` already hoists the modifier; nothing to do.
|
|
29
|
+
if clause.PhaseModifier == shimast.KindTypeKeyword {
|
|
30
|
+
return
|
|
31
|
+
}
|
|
32
|
+
// The rule only applies to named imports; default imports cannot
|
|
33
|
+
// carry an inline `type` modifier.
|
|
34
|
+
if clause.NamedBindings == nil || clause.NamedBindings.Kind != shimast.KindNamedImports {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
// A clause with a default binding alongside named imports is a
|
|
38
|
+
// mixed import; hoisting `type` would change semantics for the
|
|
39
|
+
// default. Skip.
|
|
40
|
+
if clause.Name() != nil {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
named := clause.NamedBindings.AsNamedImports()
|
|
44
|
+
if named == nil || named.Elements == nil || len(named.Elements.Nodes) == 0 {
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
for _, spec := range named.Elements.Nodes {
|
|
48
|
+
s := spec.AsImportSpecifier()
|
|
49
|
+
if s == nil || !s.IsTypeOnly {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
message := "Use top-level `import type` instead of marking every specifier individually."
|
|
54
|
+
// Compute edits: insert ` type` after the leading `import` keyword,
|
|
55
|
+
// then delete each specifier's `type ` prefix. All edits must be
|
|
56
|
+
// non-overlapping; the keyword insertion is at offset 6 (after
|
|
57
|
+
// `import`), and each specifier-level delete is strictly inside the
|
|
58
|
+
// braces, so they never overlap.
|
|
59
|
+
importKwEnd := findKeyword(ctx.File, node.Pos(), node.End(), "import")
|
|
60
|
+
if importKwEnd < 0 {
|
|
61
|
+
ctx.Report(node, message)
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
importKwEnd += len("import")
|
|
65
|
+
edits := []TextEdit{
|
|
66
|
+
{Pos: importKwEnd, End: importKwEnd, Text: " type"},
|
|
67
|
+
}
|
|
68
|
+
src := ctx.File.Text()
|
|
69
|
+
for _, spec := range named.Elements.Nodes {
|
|
70
|
+
// Locate the `type` keyword token at the head of the specifier by
|
|
71
|
+
// first skipping leading trivia (whitespace + comments). A naive
|
|
72
|
+
// `findKeyword` would search the byte range linearly and could
|
|
73
|
+
// match `type` *inside* a leading block comment such as
|
|
74
|
+
// `/* type alias */`, deleting the comment text and corrupting
|
|
75
|
+
// the source. SkipTrivia honors the lexer's notion of trivia, so
|
|
76
|
+
// the post-skip position is the actual first token byte.
|
|
77
|
+
typePos := shimscanner.SkipTrivia(src, spec.Pos())
|
|
78
|
+
if typePos < 0 || typePos+len("type") > len(src) {
|
|
79
|
+
continue
|
|
80
|
+
}
|
|
81
|
+
if src[typePos:typePos+len("type")] != "type" {
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
after := typePos + len("type")
|
|
85
|
+
// Defensive: ensure the keyword is followed by a non-identifier byte
|
|
86
|
+
// — otherwise we'd be matching an identifier prefix like `typeOf`.
|
|
87
|
+
if after < len(src) && isIdentifierPart(src[after]) {
|
|
88
|
+
continue
|
|
89
|
+
}
|
|
90
|
+
deleteEnd := after
|
|
91
|
+
if deleteEnd < len(src) && (src[deleteEnd] == ' ' || src[deleteEnd] == '\t') {
|
|
92
|
+
deleteEnd++
|
|
93
|
+
}
|
|
94
|
+
edits = append(edits, TextEdit{Pos: typePos, End: deleteEnd, Text: ""})
|
|
95
|
+
}
|
|
96
|
+
ctx.ReportFix(node, message, edits...)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
func init() {
|
|
100
|
+
Register(noImportTypeSideEffects{})
|
|
101
|
+
}
|