@ttsc/lint 0.19.3 → 0.20.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/README.md +1 -1
- package/lib/index.js +26 -4
- package/lib/index.js.map +1 -1
- package/lib/structures/format/ITtscLintFormat.d.ts +14 -10
- package/lib/structures/rules/ITtscLintRegexpRules.d.ts +24 -9
- package/lib/structures/rules/ITtscLintSecurityRules.d.ts +18 -0
- package/lib/structures/rules/ITtscLintSolidRules.d.ts +22 -2
- package/lib/structures/rules/ITtscLintStorybookRules.d.ts +21 -0
- package/lib/structures/rules/ITtscLintTypeScriptRuleOptions.d.ts +10 -1
- package/lib/structures/rules/ITtscLintTypeScriptRules.d.ts +13 -2
- package/linthost/compile.go +9 -4
- package/linthost/config.go +18 -14
- package/linthost/contrib_adapter.go +57 -0
- package/linthost/dispatch.go +5 -1
- package/linthost/display_width.go +179 -41
- package/linthost/engine.go +120 -4
- package/linthost/flags_gen.go +4 -4
- package/linthost/hints.go +207 -0
- package/linthost/host.go +68 -0
- package/linthost/lsp.go +148 -29
- package/linthost/print_dispatch.go +24 -0
- package/linthost/print_nodes_array.go +88 -15
- package/linthost/print_nodes_call.go +38 -31
- package/linthost/print_nodes_control_flow.go +319 -0
- package/linthost/print_nodes_function.go +140 -13
- package/linthost/print_nodes_list.go +22 -10
- package/linthost/project_engine.go +18 -1
- package/linthost/project_rules.go +47 -0
- package/linthost/rule_docs.go +114 -0
- package/linthost/rules_boundaries.go +80 -2
- package/linthost/rules_format_bracket_spacing.go +18 -6
- package/linthost/rules_format_clause_join.go +11 -8
- package/linthost/rules_format_indent.go +155 -4
- package/linthost/rules_format_print_width.go +12 -35
- package/linthost/rules_format_quote_props.go +88 -27
- package/linthost/rules_format_statement_split.go +81 -0
- package/linthost/rules_format_trailing_comma.go +62 -94
- package/linthost/rules_gap.go +26 -17
- package/linthost/rules_jsdoc.go +54 -0
- package/linthost/rules_logic.go +30 -15
- package/linthost/rules_no_redeclare.go +12 -3
- package/linthost/rules_promise.go +37 -15
- package/linthost/rules_regexp.go +443 -49
- package/linthost/rules_security.go +243 -4
- package/linthost/rules_solid.go +430 -10
- package/linthost/rules_storybook.go +255 -10
- package/linthost/rules_suggestions.go +60 -39
- package/linthost/rules_ts.go +7 -0
- package/linthost/rules_ts_async.go +80 -2
- package/linthost/rules_ts_extra.go +21 -7
- package/linthost/serve.go +318 -0
- package/linthost/width_tables_gen.go +219 -0
- package/package.json +2 -2
- package/rule/hint.go +142 -0
- package/rule/rule.go +197 -1
- package/src/index.ts +35 -4
- package/src/structures/format/ITtscLintFormat.ts +14 -10
- package/src/structures/rules/ITtscLintRegexpRules.ts +24 -9
- package/src/structures/rules/ITtscLintSecurityRules.ts +18 -0
- package/src/structures/rules/ITtscLintSolidRules.ts +22 -2
- package/src/structures/rules/ITtscLintStorybookRules.ts +21 -0
- package/src/structures/rules/ITtscLintTypeScriptRuleOptions.ts +10 -1
- package/src/structures/rules/ITtscLintTypeScriptRules.ts +13 -2
|
@@ -4,8 +4,9 @@ import (
|
|
|
4
4
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
5
5
|
)
|
|
6
6
|
|
|
7
|
-
// formatTrailingComma
|
|
8
|
-
//
|
|
7
|
+
// formatTrailingComma normalizes trailing commas on governed lists. It adds a
|
|
8
|
+
// comma to a multi-line list when the configured mode permits one and removes
|
|
9
|
+
// an existing comma when that mode forbids one.
|
|
9
10
|
//
|
|
10
11
|
// Scope (intentionally narrower than the closing-brace surface of TS):
|
|
11
12
|
//
|
|
@@ -14,6 +15,7 @@ import (
|
|
|
14
15
|
// - CallExpression / NewExpression `foo(a, b)` / `new Foo(a, b)`
|
|
15
16
|
// - NamedImports / NamedExports `import { a, b } from "x"`
|
|
16
17
|
// - TupleType `[A, B]` at the type level
|
|
18
|
+
// - EnumDeclaration `enum E { A, B }`
|
|
17
19
|
// - Function parameter lists `function foo(a, b) {}` etc.,
|
|
18
20
|
// including interface call/construct/method signatures and
|
|
19
21
|
// `(a, b) => …` / `new (a, b) => …` function/constructor type
|
|
@@ -29,11 +31,11 @@ import (
|
|
|
29
31
|
// - JSX attribute lists. Prettier does not apply trailing commas there
|
|
30
32
|
// either.
|
|
31
33
|
//
|
|
32
|
-
// Rest parameters (`...rest`) are explicitly skipped
|
|
33
|
-
// a trailing comma after a rest element (TS1013)
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// the rule does not visit at all.
|
|
34
|
+
// Rest parameters (`...rest`) are explicitly skipped only when adding a
|
|
35
|
+
// comma: ECMAScript forbids a trailing comma after a rest element (TS1013).
|
|
36
|
+
// A legacy comma is not syntactically valid, so there is nothing for the
|
|
37
|
+
// removal path to preserve. The same restriction applies to rest binding
|
|
38
|
+
// patterns, which the rule does not visit at all.
|
|
37
39
|
//
|
|
38
40
|
// Destructuring assignment TARGETS are the one array/object-literal shape
|
|
39
41
|
// where the same rest restriction bites: `({ a, ...rest } = obj)` and
|
|
@@ -69,6 +71,7 @@ func (formatTrailingComma) Visits() []shimast.Kind {
|
|
|
69
71
|
shimast.KindNamedImports,
|
|
70
72
|
shimast.KindNamedExports,
|
|
71
73
|
shimast.KindTupleType,
|
|
74
|
+
shimast.KindEnumDeclaration,
|
|
72
75
|
shimast.KindFunctionDeclaration,
|
|
73
76
|
shimast.KindFunctionExpression,
|
|
74
77
|
shimast.KindArrowFunction,
|
|
@@ -95,9 +98,6 @@ func (formatTrailingComma) Check(ctx *Context, node *shimast.Node) {
|
|
|
95
98
|
if mode == "" {
|
|
96
99
|
mode = "all"
|
|
97
100
|
}
|
|
98
|
-
if mode == "none" {
|
|
99
|
-
return
|
|
100
|
-
}
|
|
101
101
|
switch node.Kind {
|
|
102
102
|
case shimast.KindArrayLiteralExpression:
|
|
103
103
|
arr := node.AsArrayLiteralExpression()
|
|
@@ -107,7 +107,7 @@ func (formatTrailingComma) Check(ctx *Context, node *shimast.Node) {
|
|
|
107
107
|
if isRestAssignmentTargetLiteral(node) {
|
|
108
108
|
return
|
|
109
109
|
}
|
|
110
|
-
|
|
110
|
+
normalizeTrailingComma(ctx, arr.Elements, node.End()-1, mode != "none")
|
|
111
111
|
case shimast.KindObjectLiteralExpression:
|
|
112
112
|
obj := node.AsObjectLiteralExpression()
|
|
113
113
|
if obj == nil {
|
|
@@ -116,11 +116,8 @@ func (formatTrailingComma) Check(ctx *Context, node *shimast.Node) {
|
|
|
116
116
|
if isRestAssignmentTargetLiteral(node) {
|
|
117
117
|
return
|
|
118
118
|
}
|
|
119
|
-
|
|
119
|
+
normalizeTrailingComma(ctx, obj.Properties, node.End()-1, mode != "none")
|
|
120
120
|
case shimast.KindCallExpression:
|
|
121
|
-
if mode == "es5" {
|
|
122
|
-
return // prettier's es5 mode skips call arguments
|
|
123
|
-
}
|
|
124
121
|
call := node.AsCallExpression()
|
|
125
122
|
if call == nil {
|
|
126
123
|
return
|
|
@@ -133,149 +130,110 @@ func (formatTrailingComma) Check(ctx *Context, node *shimast.Node) {
|
|
|
133
130
|
// same exception so the two rules agree on the reflowed shape.
|
|
134
131
|
return
|
|
135
132
|
}
|
|
136
|
-
|
|
133
|
+
normalizeTrailingComma(ctx, call.Arguments, node.End()-1, mode == "all")
|
|
137
134
|
case shimast.KindNewExpression:
|
|
138
|
-
if mode == "es5" {
|
|
139
|
-
return
|
|
140
|
-
}
|
|
141
135
|
ne := node.AsNewExpression()
|
|
142
136
|
if ne == nil || ne.Arguments == nil {
|
|
143
137
|
return
|
|
144
138
|
}
|
|
145
|
-
|
|
139
|
+
normalizeTrailingComma(ctx, ne.Arguments, node.End()-1, mode == "all")
|
|
146
140
|
case shimast.KindNamedImports:
|
|
147
141
|
named := node.AsNamedImports()
|
|
148
142
|
if named == nil {
|
|
149
143
|
return
|
|
150
144
|
}
|
|
151
|
-
|
|
145
|
+
normalizeTrailingComma(ctx, named.Elements, node.End()-1, mode != "none")
|
|
152
146
|
case shimast.KindNamedExports:
|
|
153
147
|
named := node.AsNamedExports()
|
|
154
148
|
if named == nil {
|
|
155
149
|
return
|
|
156
150
|
}
|
|
157
|
-
|
|
151
|
+
normalizeTrailingComma(ctx, named.Elements, node.End()-1, mode != "none")
|
|
158
152
|
case shimast.KindTupleType:
|
|
159
|
-
if mode == "es5" {
|
|
160
|
-
return // tuple types are type-level; ES5 mode is runtime-only
|
|
161
|
-
}
|
|
162
153
|
tup := node.AsTupleTypeNode()
|
|
163
154
|
if tup == nil {
|
|
164
155
|
return
|
|
165
156
|
}
|
|
166
|
-
|
|
167
|
-
case shimast.
|
|
168
|
-
|
|
157
|
+
normalizeTrailingComma(ctx, tup.Elements, node.End()-1, mode != "none")
|
|
158
|
+
case shimast.KindEnumDeclaration:
|
|
159
|
+
decl := node.AsEnumDeclaration()
|
|
160
|
+
if decl == nil {
|
|
169
161
|
return
|
|
170
162
|
}
|
|
163
|
+
normalizeTrailingComma(ctx, decl.Members, node.End()-1, mode != "none")
|
|
164
|
+
case shimast.KindFunctionDeclaration:
|
|
171
165
|
fn := node.AsFunctionDeclaration()
|
|
172
166
|
if fn == nil {
|
|
173
167
|
return
|
|
174
168
|
}
|
|
175
|
-
|
|
169
|
+
normalizeFunctionParameterComma(ctx, fn.Parameters, mode == "all")
|
|
176
170
|
case shimast.KindFunctionExpression:
|
|
177
|
-
if mode == "es5" {
|
|
178
|
-
return
|
|
179
|
-
}
|
|
180
171
|
fn := node.AsFunctionExpression()
|
|
181
172
|
if fn == nil {
|
|
182
173
|
return
|
|
183
174
|
}
|
|
184
|
-
|
|
175
|
+
normalizeFunctionParameterComma(ctx, fn.Parameters, mode == "all")
|
|
185
176
|
case shimast.KindArrowFunction:
|
|
186
|
-
if mode == "es5" {
|
|
187
|
-
return
|
|
188
|
-
}
|
|
189
177
|
fn := node.AsArrowFunction()
|
|
190
178
|
if fn == nil {
|
|
191
179
|
return
|
|
192
180
|
}
|
|
193
|
-
|
|
181
|
+
normalizeFunctionParameterComma(ctx, fn.Parameters, mode == "all")
|
|
194
182
|
case shimast.KindMethodDeclaration:
|
|
195
|
-
if mode == "es5" {
|
|
196
|
-
return
|
|
197
|
-
}
|
|
198
183
|
fn := node.AsMethodDeclaration()
|
|
199
184
|
if fn == nil {
|
|
200
185
|
return
|
|
201
186
|
}
|
|
202
|
-
|
|
187
|
+
normalizeFunctionParameterComma(ctx, fn.Parameters, mode == "all")
|
|
203
188
|
case shimast.KindConstructor:
|
|
204
|
-
if mode == "es5" {
|
|
205
|
-
return
|
|
206
|
-
}
|
|
207
189
|
fn := node.AsConstructorDeclaration()
|
|
208
190
|
if fn == nil {
|
|
209
191
|
return
|
|
210
192
|
}
|
|
211
|
-
|
|
193
|
+
normalizeFunctionParameterComma(ctx, fn.Parameters, mode == "all")
|
|
212
194
|
case shimast.KindGetAccessor:
|
|
213
|
-
if mode == "es5" {
|
|
214
|
-
return
|
|
215
|
-
}
|
|
216
195
|
fn := node.AsGetAccessorDeclaration()
|
|
217
196
|
if fn == nil {
|
|
218
197
|
return
|
|
219
198
|
}
|
|
220
|
-
|
|
199
|
+
normalizeFunctionParameterComma(ctx, fn.Parameters, mode == "all")
|
|
221
200
|
case shimast.KindSetAccessor:
|
|
222
|
-
if mode == "es5" {
|
|
223
|
-
return
|
|
224
|
-
}
|
|
225
201
|
fn := node.AsSetAccessorDeclaration()
|
|
226
202
|
if fn == nil {
|
|
227
203
|
return
|
|
228
204
|
}
|
|
229
|
-
|
|
205
|
+
normalizeFunctionParameterComma(ctx, fn.Parameters, mode == "all")
|
|
230
206
|
case shimast.KindMethodSignature:
|
|
231
|
-
if mode == "es5" {
|
|
232
|
-
return
|
|
233
|
-
}
|
|
234
207
|
sig := node.AsMethodSignatureDeclaration()
|
|
235
208
|
if sig == nil {
|
|
236
209
|
return
|
|
237
210
|
}
|
|
238
|
-
|
|
211
|
+
normalizeFunctionParameterComma(ctx, sig.Parameters, mode == "all")
|
|
239
212
|
case shimast.KindCallSignature:
|
|
240
|
-
if mode == "es5" {
|
|
241
|
-
return
|
|
242
|
-
}
|
|
243
213
|
sig := node.AsCallSignatureDeclaration()
|
|
244
214
|
if sig == nil {
|
|
245
215
|
return
|
|
246
216
|
}
|
|
247
|
-
|
|
217
|
+
normalizeFunctionParameterComma(ctx, sig.Parameters, mode == "all")
|
|
248
218
|
case shimast.KindConstructSignature:
|
|
249
|
-
if mode == "es5" {
|
|
250
|
-
return
|
|
251
|
-
}
|
|
252
219
|
sig := node.AsConstructSignatureDeclaration()
|
|
253
220
|
if sig == nil {
|
|
254
221
|
return
|
|
255
222
|
}
|
|
256
|
-
|
|
223
|
+
normalizeFunctionParameterComma(ctx, sig.Parameters, mode == "all")
|
|
257
224
|
case shimast.KindFunctionType:
|
|
258
|
-
if mode == "es5" {
|
|
259
|
-
return
|
|
260
|
-
}
|
|
261
225
|
ft := node.AsFunctionTypeNode()
|
|
262
226
|
if ft == nil {
|
|
263
227
|
return
|
|
264
228
|
}
|
|
265
|
-
|
|
229
|
+
normalizeFunctionParameterComma(ctx, ft.Parameters, mode == "all")
|
|
266
230
|
case shimast.KindConstructorType:
|
|
267
|
-
if mode == "es5" {
|
|
268
|
-
return
|
|
269
|
-
}
|
|
270
231
|
ct := node.AsConstructorTypeNode()
|
|
271
232
|
if ct == nil {
|
|
272
233
|
return
|
|
273
234
|
}
|
|
274
|
-
|
|
235
|
+
normalizeFunctionParameterComma(ctx, ct.Parameters, mode == "all")
|
|
275
236
|
case shimast.KindTypeParameter:
|
|
276
|
-
if mode == "es5" {
|
|
277
|
-
return // type parameters postdate ES5; prettier es5 skips them
|
|
278
|
-
}
|
|
279
237
|
if node.Parent == nil {
|
|
280
238
|
return
|
|
281
239
|
}
|
|
@@ -288,7 +246,7 @@ func (formatTrailingComma) Check(ctx *Context, node *shimast.Node) {
|
|
|
288
246
|
if closePos < 0 {
|
|
289
247
|
return
|
|
290
248
|
}
|
|
291
|
-
|
|
249
|
+
normalizeTrailingComma(ctx, list, closePos, mode != "none")
|
|
292
250
|
}
|
|
293
251
|
}
|
|
294
252
|
|
|
@@ -303,9 +261,10 @@ func isDynamicImportCall(call *shimast.CallExpression) bool {
|
|
|
303
261
|
call.Expression.Kind == shimast.KindImportKeyword
|
|
304
262
|
}
|
|
305
263
|
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
264
|
+
// normalizeTrailingComma aligns one bracket-delimited list with the configured
|
|
265
|
+
// policy. `closeBracketPos` points at its closing punctuation (for example the
|
|
266
|
+
// `]` of an array literal). Addition stays limited to multi-line lists, as
|
|
267
|
+
// Prettier does, while removal also handles a pre-existing single-line comma.
|
|
309
268
|
//
|
|
310
269
|
// "Multi-line" means the close bracket sits on a different line from the
|
|
311
270
|
// last element's end, not just "the list contains a newline somewhere".
|
|
@@ -316,7 +275,7 @@ func isDynamicImportCall(call *shimast.CallExpression) bool {
|
|
|
316
275
|
// but `}` and `)` collapse onto one line; inserting `,)` there would be
|
|
317
276
|
// stylistically wrong and, for parameter rest-paths nearby, can shift
|
|
318
277
|
// later diagnostics.
|
|
319
|
-
func
|
|
278
|
+
func normalizeTrailingComma(ctx *Context, list *shimast.NodeList, closeBracketPos int, want bool) {
|
|
320
279
|
if list == nil || len(list.Nodes) == 0 {
|
|
321
280
|
return
|
|
322
281
|
}
|
|
@@ -328,10 +287,20 @@ func considerTrailingComma(ctx *Context, list *shimast.NodeList, closeBracketPos
|
|
|
328
287
|
if closeBracketPos < 0 || closeBracketPos >= len(src) {
|
|
329
288
|
return
|
|
330
289
|
}
|
|
331
|
-
|
|
290
|
+
comma := trailingCommaPos(src, last.End(), closeBracketPos)
|
|
291
|
+
if comma >= 0 {
|
|
292
|
+
if want {
|
|
293
|
+
return
|
|
294
|
+
}
|
|
295
|
+
ctx.ReportRangeFix(
|
|
296
|
+
comma,
|
|
297
|
+
comma+1,
|
|
298
|
+
"Trailing comma is not allowed by this trailingComma mode.",
|
|
299
|
+
TextEdit{Pos: comma, End: comma + 1, Text: ""},
|
|
300
|
+
)
|
|
332
301
|
return
|
|
333
302
|
}
|
|
334
|
-
if
|
|
303
|
+
if !want || !rangeSpansMultipleLines(src, last.End(), closeBracketPos) {
|
|
335
304
|
return
|
|
336
305
|
}
|
|
337
306
|
ctx.ReportRangeFix(
|
|
@@ -352,11 +321,11 @@ func considerTrailingComma(ctx *Context, list *shimast.NodeList, closeBracketPos
|
|
|
352
321
|
// a TS1013 syntax error. The rule peeks the last element's
|
|
353
322
|
// DotDotDotToken and bails when set, since the rest must remain the
|
|
354
323
|
// terminal element with no following comma.
|
|
355
|
-
func
|
|
324
|
+
func normalizeFunctionParameterComma(ctx *Context, list *shimast.NodeList, want bool) {
|
|
356
325
|
if list == nil || len(list.Nodes) == 0 {
|
|
357
326
|
return
|
|
358
327
|
}
|
|
359
|
-
if lastParameterIsRest(list) {
|
|
328
|
+
if want && lastParameterIsRest(list) {
|
|
360
329
|
return
|
|
361
330
|
}
|
|
362
331
|
src := ctx.File.Text()
|
|
@@ -364,7 +333,7 @@ func considerFunctionParameterComma(ctx *Context, list *shimast.NodeList) {
|
|
|
364
333
|
if closePos < 0 {
|
|
365
334
|
return
|
|
366
335
|
}
|
|
367
|
-
|
|
336
|
+
normalizeTrailingComma(ctx, list, closePos, want)
|
|
368
337
|
}
|
|
369
338
|
|
|
370
339
|
// isRestAssignmentTargetLiteral reports whether node is an object- or
|
|
@@ -494,11 +463,10 @@ func rangeSpansMultipleLines(src string, a, b int) bool {
|
|
|
494
463
|
return false
|
|
495
464
|
}
|
|
496
465
|
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
|
|
501
|
-
func rangeHasTrailingComma(src string, start, end int) bool {
|
|
466
|
+
// trailingCommaPos returns the offset of the comma immediately following a
|
|
467
|
+
// list's final item, skipping whitespace and comments. It deliberately does
|
|
468
|
+
// not scan through another token, so it cannot remove an item separator.
|
|
469
|
+
func trailingCommaPos(src string, start, end int) int {
|
|
502
470
|
if start < 0 {
|
|
503
471
|
start = 0
|
|
504
472
|
}
|
|
@@ -508,7 +476,7 @@ func rangeHasTrailingComma(src string, start, end int) bool {
|
|
|
508
476
|
for i := start; i < end; {
|
|
509
477
|
c := src[i]
|
|
510
478
|
if c == ',' {
|
|
511
|
-
return
|
|
479
|
+
return i
|
|
512
480
|
}
|
|
513
481
|
if c == ' ' || c == '\t' || c == '\r' || c == '\n' {
|
|
514
482
|
i++
|
|
@@ -532,9 +500,9 @@ func rangeHasTrailingComma(src string, start, end int) bool {
|
|
|
532
500
|
continue
|
|
533
501
|
}
|
|
534
502
|
}
|
|
535
|
-
return
|
|
503
|
+
return -1
|
|
536
504
|
}
|
|
537
|
-
return
|
|
505
|
+
return -1
|
|
538
506
|
}
|
|
539
507
|
|
|
540
508
|
func init() {
|
package/linthost/rules_gap.go
CHANGED
|
@@ -540,13 +540,6 @@ func (dotNotation) Check(ctx *Context, node *shimast.Node) {
|
|
|
540
540
|
return
|
|
541
541
|
}
|
|
542
542
|
message := "Use dot notation instead of a string literal property access."
|
|
543
|
-
// Conservative: keep reserved words as bracket access. Modern JS accepts
|
|
544
|
-
// `obj.class`/`obj.if`/etc. syntactically but mixing them with member
|
|
545
|
-
// expression syntax is jarring and can confuse minifiers and older runtimes.
|
|
546
|
-
if isReservedWord(key) {
|
|
547
|
-
ctx.Report(node, message)
|
|
548
|
-
return
|
|
549
|
-
}
|
|
550
543
|
src := ctx.File.Text()
|
|
551
544
|
// Determine where the bracket access begins. With an optional chain
|
|
552
545
|
// (`obj?.["foo"]`) the `?.` token already separates object from access, so
|
|
@@ -575,18 +568,34 @@ func (dotNotation) Check(ctx *Context, node *shimast.Node) {
|
|
|
575
568
|
ctx.Report(node, message)
|
|
576
569
|
return
|
|
577
570
|
}
|
|
571
|
+
edit := TextEdit{Pos: replaceFrom, End: node.End(), Text: replacement}
|
|
572
|
+
switch {
|
|
578
573
|
// A comment inside the replaced `[…]` tail (`p1 /* keep */ ["foo"]`) would be
|
|
579
|
-
// silently deleted by the splice
|
|
580
|
-
//
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
574
|
+
// silently deleted by the splice, so it is never imposed — matching ESLint
|
|
575
|
+
// dot-notation's `commentsExistBetween` guard. The rewrite itself is still
|
|
576
|
+
// correct, so it is offered as an opt-in suggestion that names the loss.
|
|
577
|
+
case hasCommentBetween(src, replaceFrom, node.End()):
|
|
578
|
+
ctx.ReportSuggestion(
|
|
579
|
+
node,
|
|
580
|
+
message,
|
|
581
|
+
"Use dot notation, discarding the comment inside the brackets.",
|
|
582
|
+
edit,
|
|
583
|
+
)
|
|
584
|
+
// Conservative: keep reserved words as bracket access. Modern JS accepts
|
|
585
|
+
// `obj.class`/`obj.if`/etc. syntactically but mixing them with member
|
|
586
|
+
// expression syntax is jarring and can confuse minifiers and older runtimes.
|
|
587
|
+
// ESLint's default `allowKeywords: true` does rewrite them, so the edit is
|
|
588
|
+
// offered as an opt-in suggestion rather than withheld outright.
|
|
589
|
+
case isReservedWord(key):
|
|
590
|
+
ctx.ReportSuggestion(
|
|
591
|
+
node,
|
|
592
|
+
message,
|
|
593
|
+
"Use dot notation for the reserved word `"+key+"`.",
|
|
594
|
+
edit,
|
|
595
|
+
)
|
|
596
|
+
default:
|
|
597
|
+
ctx.ReportFix(node, message, edit)
|
|
584
598
|
}
|
|
585
|
-
ctx.ReportFix(
|
|
586
|
-
node,
|
|
587
|
-
message,
|
|
588
|
-
TextEdit{Pos: replaceFrom, End: node.End(), Text: replacement},
|
|
589
|
-
)
|
|
590
599
|
}
|
|
591
600
|
|
|
592
601
|
// isDecimalIntegerLiteralText reports whether raw is a numeric literal written
|
package/linthost/rules_jsdoc.go
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
package linthost
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"sort"
|
|
4
5
|
"strings"
|
|
5
6
|
|
|
6
7
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
8
|
+
|
|
9
|
+
publicrule "github.com/samchon/ttsc/packages/lint/rule"
|
|
7
10
|
)
|
|
8
11
|
|
|
9
12
|
type jsdocLintRule struct {
|
|
@@ -11,6 +14,56 @@ type jsdocLintRule struct {
|
|
|
11
14
|
check func(*Context, string, parsedJSDocBlock)
|
|
12
15
|
}
|
|
13
16
|
|
|
17
|
+
type jsdocCheckTagNamesHintRule struct{}
|
|
18
|
+
|
|
19
|
+
type jsdocCheckTagNamesHintState struct{}
|
|
20
|
+
|
|
21
|
+
func (jsdocCheckTagNamesHintRule) Name() string { return "jsdoc/check-tag-names" }
|
|
22
|
+
func (jsdocCheckTagNamesHintRule) Check(ctx *publicrule.ProjectContext) {
|
|
23
|
+
ctx.SetState(jsdocCheckTagNamesHintState{})
|
|
24
|
+
}
|
|
25
|
+
func (jsdocCheckTagNamesHintRule) AcceptsTtscLintOptions() bool { return false }
|
|
26
|
+
func (jsdocCheckTagNamesHintRule) NeedsTypeChecker() bool { return false }
|
|
27
|
+
func (jsdocCheckTagNamesHintRule) Hints(ctx *publicrule.HintContext) []publicrule.Hint {
|
|
28
|
+
if ctx == nil {
|
|
29
|
+
return nil
|
|
30
|
+
}
|
|
31
|
+
if _, ok := ctx.State.(jsdocCheckTagNamesHintState); !ok {
|
|
32
|
+
return nil
|
|
33
|
+
}
|
|
34
|
+
tags := make([]string, 0, len(knownJSDocTags))
|
|
35
|
+
for tag := range knownJSDocTags {
|
|
36
|
+
tags = append(tags, tag)
|
|
37
|
+
}
|
|
38
|
+
sort.Strings(tags)
|
|
39
|
+
hints := make([]publicrule.Hint, 0, len(tags))
|
|
40
|
+
for _, tag := range tags {
|
|
41
|
+
hints = append(hints, publicrule.Hint{
|
|
42
|
+
Insert: tag,
|
|
43
|
+
Detail: jsdocTagHintDetail(tag),
|
|
44
|
+
Trigger: publicrule.HintTrigger{
|
|
45
|
+
Scope: publicrule.HintScopeJSDoc,
|
|
46
|
+
After: "@",
|
|
47
|
+
},
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
return hints
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
func jsdocTagHintDetail(tag string) string {
|
|
54
|
+
normalized := strings.ToLower(tag)
|
|
55
|
+
if canonical, alias := jsdocTagSynonyms[normalized]; alias {
|
|
56
|
+
return "alias for @" + canonical
|
|
57
|
+
}
|
|
58
|
+
if _, typed := jsdocTagsWithType[normalized]; typed {
|
|
59
|
+
return "accepts a type"
|
|
60
|
+
}
|
|
61
|
+
if _, empty := emptyJSDocTags[normalized]; empty {
|
|
62
|
+
return "no content"
|
|
63
|
+
}
|
|
64
|
+
return "JSDoc tag"
|
|
65
|
+
}
|
|
66
|
+
|
|
14
67
|
func (r jsdocLintRule) Name() string { return r.name }
|
|
15
68
|
func (r jsdocLintRule) Visits() []shimast.Kind {
|
|
16
69
|
return []shimast.Kind{shimast.KindSourceFile}
|
|
@@ -488,6 +541,7 @@ func checkJSDocEmptyTags(ctx *Context, _ string, block parsedJSDocBlock) {
|
|
|
488
541
|
|
|
489
542
|
func init() {
|
|
490
543
|
Register(jsdocLintRule{name: "jsdoc/check-tag-names", check: checkJSDocTagNames})
|
|
544
|
+
registerBuiltInProjectCompanion(jsdocCheckTagNamesHintRule{})
|
|
491
545
|
Register(jsdocLintRule{name: "jsdoc/check-values", check: checkJSDocCheckValues})
|
|
492
546
|
Register(jsdocLintRule{name: "jsdoc/empty-tags", check: checkJSDocEmptyTags})
|
|
493
547
|
Register(jsdocLintRule{name: "jsdoc/no-types", check: checkJSDocNoTypes})
|
package/linthost/rules_logic.go
CHANGED
|
@@ -86,8 +86,9 @@ func (noExtraBooleanCast) Check(ctx *Context, node *shimast.Node) {
|
|
|
86
86
|
// result, as do explicit parentheses around the cast itself.
|
|
87
87
|
// - Comments. The splice keeps only the inner expression's text, so a
|
|
88
88
|
// comment anywhere else in the replaced span (`!Boolean(/* why */ ok)`)
|
|
89
|
-
// would be silently deleted.
|
|
90
|
-
//
|
|
89
|
+
// would be silently deleted. Imposing that loss is unacceptable, so the
|
|
90
|
+
// same edit is offered as an opt-in suggestion instead: the author sees
|
|
91
|
+
// the title, chooses it, and reviews the result.
|
|
91
92
|
func reportBooleanCastFix(ctx *Context, node, inner *shimast.Node, message string) {
|
|
92
93
|
if inner == nil {
|
|
93
94
|
ctx.Report(node, message)
|
|
@@ -105,17 +106,23 @@ func reportBooleanCastFix(ctx *Context, node, inner *shimast.Node, message strin
|
|
|
105
106
|
ctx.Report(node, message)
|
|
106
107
|
return
|
|
107
108
|
}
|
|
108
|
-
if hasCommentBetween(src, editPos, keepStart) ||
|
|
109
|
-
hasCommentBetween(src, keepEnd, node.End()) {
|
|
110
|
-
ctx.Report(node, message)
|
|
111
|
-
return
|
|
112
|
-
}
|
|
113
109
|
text := src[keepStart:keepEnd]
|
|
114
110
|
if floor, bounded := booleanContextPrecedenceFloor(node); bounded &&
|
|
115
111
|
shimast.GetExpressionPrecedence(inner) <= floor {
|
|
116
112
|
text = "(" + text + ")"
|
|
117
113
|
}
|
|
118
|
-
|
|
114
|
+
edit := TextEdit{Pos: editPos, End: node.End(), Text: text}
|
|
115
|
+
if hasCommentBetween(src, editPos, keepStart) ||
|
|
116
|
+
hasCommentBetween(src, keepEnd, node.End()) {
|
|
117
|
+
ctx.ReportSuggestion(
|
|
118
|
+
node,
|
|
119
|
+
message,
|
|
120
|
+
"Remove the redundant cast, discarding the comment inside it.",
|
|
121
|
+
edit,
|
|
122
|
+
)
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
ctx.ReportFix(node, message, edit)
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
// booleanContextPrecedenceFloor returns the precedence at or below which a
|
|
@@ -239,21 +246,29 @@ func (eqeqeq) Check(ctx *Context, node *shimast.Node) {
|
|
|
239
246
|
}
|
|
240
247
|
}
|
|
241
248
|
|
|
249
|
+
// reportEqeqeq reports the loose operator and rewrites it to `replacement`.
|
|
250
|
+
// The rewrite is imposed only when `isEqeqeqAutoFixSafe` proves both operands
|
|
251
|
+
// share a type, because tightening a genuinely mixed-type comparison changes
|
|
252
|
+
// what the program computes. That is a judgement only the author can make, so
|
|
253
|
+
// the same edit is still offered as an opt-in suggestion whose title names the
|
|
254
|
+
// behavior change rather than being discarded.
|
|
242
255
|
func reportEqeqeq(ctx *Context, operator *shimast.Node, expr *shimast.BinaryExpression, message, replacement string) {
|
|
243
256
|
pos, end := tokenRange(ctx.File, operator)
|
|
244
257
|
if pos < 0 {
|
|
245
258
|
pos, end = operator.Pos(), operator.End()
|
|
246
259
|
}
|
|
260
|
+
edit := TextEdit{Pos: pos, End: end, Text: replacement}
|
|
247
261
|
if isEqeqeqAutoFixSafe(expr) {
|
|
248
|
-
ctx.ReportRangeFix(
|
|
249
|
-
pos,
|
|
250
|
-
end,
|
|
251
|
-
message,
|
|
252
|
-
TextEdit{Pos: pos, End: end, Text: replacement},
|
|
253
|
-
)
|
|
262
|
+
ctx.ReportRangeFix(pos, end, message, edit)
|
|
254
263
|
return
|
|
255
264
|
}
|
|
256
|
-
ctx.
|
|
265
|
+
ctx.ReportRangeSuggestion(
|
|
266
|
+
pos,
|
|
267
|
+
end,
|
|
268
|
+
message,
|
|
269
|
+
"Replace with `"+replacement+"`, which changes the result when the operands differ in type.",
|
|
270
|
+
edit,
|
|
271
|
+
)
|
|
257
272
|
}
|
|
258
273
|
|
|
259
274
|
func isEqeqeqAutoFixSafe(expr *shimast.BinaryExpression) bool {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
package linthost
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import (
|
|
4
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
5
|
+
|
|
6
|
+
publicrule "github.com/samchon/ttsc/packages/lint/rule"
|
|
7
|
+
)
|
|
4
8
|
|
|
5
9
|
// noRedeclare: reject declaring the same binding more than once in the
|
|
6
10
|
// same scope. The second declaration silently overwrites the first;
|
|
@@ -32,8 +36,13 @@ func (noRedeclare) Check(ctx *Context, node *shimast.Node) {
|
|
|
32
36
|
|
|
33
37
|
report := func(name string, declNode *shimast.Node) {
|
|
34
38
|
if prior, exists := seen[name]; exists {
|
|
35
|
-
|
|
36
|
-
ctx.
|
|
39
|
+
priorPos, priorEnd := ctx.nodeFindingRange(prior)
|
|
40
|
+
ctx.ReportRelated(declNode, "'"+name+"' is already defined.",
|
|
41
|
+
publicrule.RelatedInformation{
|
|
42
|
+
Pos: priorPos,
|
|
43
|
+
End: priorEnd,
|
|
44
|
+
Message: "'" + name + "' was first defined here.",
|
|
45
|
+
})
|
|
37
46
|
return
|
|
38
47
|
}
|
|
39
48
|
seen[name] = declNode
|