@ttsc/lint 0.12.4 → 0.13.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/lib/index.js +225 -135
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintPluginConfig.d.ts +10 -77
- package/lib/structures/TtscLintRuleOptions.d.ts +14 -0
- package/linthost/ast_helpers.go +68 -16
- package/linthost/compile.go +117 -119
- package/linthost/config.go +518 -707
- package/linthost/config_format.go +16 -4
- package/linthost/contrib_adapter.go +7 -0
- package/linthost/directives.go +44 -0
- package/linthost/engine.go +152 -44
- package/linthost/fix.go +24 -33
- package/linthost/flags_gen.go +33 -0
- package/linthost/format.go +96 -3
- package/linthost/host.go +144 -8
- package/linthost/print_dispatch.go +121 -23
- package/linthost/print_doc.go +19 -0
- package/linthost/print_engine.go +168 -4
- package/linthost/print_nodes_array.go +17 -7
- package/linthost/print_nodes_call.go +129 -20
- package/linthost/print_nodes_function.go +353 -0
- package/linthost/print_nodes_imports.go +46 -29
- package/linthost/print_nodes_list.go +86 -5
- package/linthost/print_nodes_object.go +56 -11
- package/linthost/rules_escape.go +20 -3
- package/linthost/rules_format_print_width.go +267 -15
- package/linthost/rules_gap.go +55 -3
- package/linthost/rules_logic.go +64 -5
- package/linthost/rules_problems.go +65 -21
- package/linthost/rules_promise.go +3 -0
- package/linthost/rules_suggestions.go +160 -5
- package/linthost/rules_var.go +7 -1
- package/package.json +3 -3
- package/src/index.ts +243 -168
- package/src/structures/ITtscLintPluginConfig.ts +10 -83
- package/src/structures/TtscLintRuleOptions.ts +15 -0
- package/linthost/eslint_runtime.go +0 -351
package/linthost/rules_logic.go
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
// AST-only, no scope analysis.
|
|
6
6
|
package linthost
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import (
|
|
9
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
10
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
11
|
+
)
|
|
9
12
|
|
|
10
13
|
// no-extra-boolean-cast: `if (!!x)`, `if (Boolean(x))`, `Boolean(!!x)` —
|
|
11
14
|
// the conversion is implicit in a boolean context.
|
|
@@ -26,9 +29,43 @@ func (noExtraBooleanCast) Check(ctx *Context, node *shimast.Node) {
|
|
|
26
29
|
if identifierText(call.Expression) != "Boolean" {
|
|
27
30
|
return
|
|
28
31
|
}
|
|
29
|
-
if
|
|
30
|
-
|
|
32
|
+
if call.QuestionDotToken != nil {
|
|
33
|
+
return
|
|
34
|
+
}
|
|
35
|
+
if !isInBooleanContext(node) {
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
message := "Redundant Boolean call."
|
|
39
|
+
// Only autofix when there is exactly one positional argument: zero
|
|
40
|
+
// arguments produce `undefined → false`, and multi-arg calls signal the
|
|
41
|
+
// author may be passing through an unrelated value as the second slot.
|
|
42
|
+
// Spread arguments hide the runtime shape, so skip those as well.
|
|
43
|
+
if call.Arguments == nil || len(call.Arguments.Nodes) != 1 {
|
|
44
|
+
ctx.Report(node, message)
|
|
45
|
+
return
|
|
46
|
+
}
|
|
47
|
+
arg := call.Arguments.Nodes[0]
|
|
48
|
+
if arg == nil || arg.Kind == shimast.KindSpreadElement {
|
|
49
|
+
ctx.Report(node, message)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
src := ctx.File.Text()
|
|
53
|
+
argStart := shimscanner.SkipTrivia(src, arg.Pos())
|
|
54
|
+
argEnd := arg.End()
|
|
55
|
+
if argStart < 0 || argStart >= argEnd || argEnd > len(src) {
|
|
56
|
+
ctx.Report(node, message)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
editPos := shimscanner.SkipTrivia(src, node.Pos())
|
|
60
|
+
if editPos < 0 || editPos >= node.End() {
|
|
61
|
+
ctx.Report(node, message)
|
|
62
|
+
return
|
|
31
63
|
}
|
|
64
|
+
ctx.ReportFix(
|
|
65
|
+
node,
|
|
66
|
+
message,
|
|
67
|
+
TextEdit{Pos: editPos, End: node.End(), Text: src[argStart:argEnd]},
|
|
68
|
+
)
|
|
32
69
|
case shimast.KindPrefixUnaryExpression:
|
|
33
70
|
outer := node.AsPrefixUnaryExpression()
|
|
34
71
|
if outer == nil || outer.Operator != shimast.KindExclamationToken {
|
|
@@ -42,9 +79,31 @@ func (noExtraBooleanCast) Check(ctx *Context, node *shimast.Node) {
|
|
|
42
79
|
if inner == nil || inner.Operator != shimast.KindExclamationToken {
|
|
43
80
|
return
|
|
44
81
|
}
|
|
45
|
-
if isInBooleanContext(node) {
|
|
46
|
-
|
|
82
|
+
if !isInBooleanContext(node) {
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
message := "Redundant double negation."
|
|
86
|
+
if inner.Operand == nil {
|
|
87
|
+
ctx.Report(node, message)
|
|
88
|
+
return
|
|
47
89
|
}
|
|
90
|
+
src := ctx.File.Text()
|
|
91
|
+
inStart := shimscanner.SkipTrivia(src, inner.Operand.Pos())
|
|
92
|
+
inEnd := inner.Operand.End()
|
|
93
|
+
if inStart < 0 || inStart >= inEnd || inEnd > len(src) {
|
|
94
|
+
ctx.Report(node, message)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
editPos := shimscanner.SkipTrivia(src, node.Pos())
|
|
98
|
+
if editPos < 0 || editPos >= node.End() {
|
|
99
|
+
ctx.Report(node, message)
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
ctx.ReportFix(
|
|
103
|
+
node,
|
|
104
|
+
message,
|
|
105
|
+
TextEdit{Pos: editPos, End: node.End(), Text: src[inStart:inEnd]},
|
|
106
|
+
)
|
|
48
107
|
}
|
|
49
108
|
}
|
|
50
109
|
|
|
@@ -74,7 +74,9 @@ func (noExAssign) Check(ctx *Context, node *shimast.Node) {
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
// walkAssignments invokes `report` on every `<name> = ...` shape inside
|
|
77
|
-
// `root`. Used by no-ex-assign
|
|
77
|
+
// `root`. Used by no-ex-assign to scan a single catch block; the
|
|
78
|
+
// file-wide no-func-assign / no-class-assign scans go through
|
|
79
|
+
// reportAssignmentsToDeclarations instead.
|
|
78
80
|
func walkAssignments(root *shimast.Node, name string, report func(*shimast.Node)) {
|
|
79
81
|
if root == nil {
|
|
80
82
|
return
|
|
@@ -230,38 +232,80 @@ func numericLiteralLosesPrecision(text string) bool {
|
|
|
230
232
|
type noClassAssign struct{}
|
|
231
233
|
|
|
232
234
|
func (noClassAssign) Name() string { return "no-class-assign" }
|
|
233
|
-
func (noClassAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.
|
|
235
|
+
func (noClassAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
234
236
|
func (noClassAssign) Check(ctx *Context, node *shimast.Node) {
|
|
235
|
-
|
|
236
|
-
if decl == nil || decl.Name() == nil {
|
|
237
|
-
return
|
|
238
|
-
}
|
|
239
|
-
name := identifierText(decl.Name())
|
|
240
|
-
if name == "" {
|
|
241
|
-
return
|
|
242
|
-
}
|
|
243
|
-
walkAssignments(ctx.File.AsNode(), name, func(target *shimast.Node) {
|
|
244
|
-
ctx.Report(target, "'"+name+"' is a class.")
|
|
245
|
-
})
|
|
237
|
+
reportAssignmentsToDeclarations(ctx, node, shimast.KindClassDeclaration, "is a class.")
|
|
246
238
|
}
|
|
247
239
|
|
|
248
240
|
// no-func-assign: same idea, but for function declarations.
|
|
249
241
|
type noFuncAssign struct{}
|
|
250
242
|
|
|
251
243
|
func (noFuncAssign) Name() string { return "no-func-assign" }
|
|
252
|
-
func (noFuncAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.
|
|
244
|
+
func (noFuncAssign) Visits() []shimast.Kind { return []shimast.Kind{shimast.KindSourceFile} }
|
|
253
245
|
func (noFuncAssign) Check(ctx *Context, node *shimast.Node) {
|
|
254
|
-
|
|
255
|
-
|
|
246
|
+
reportAssignmentsToDeclarations(ctx, node, shimast.KindFunctionDeclaration, "is a function.")
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// reportAssignmentsToDeclarations flags every `<name> = …` assignment whose
|
|
250
|
+
// target identifier names a `declKind` declaration found anywhere in the
|
|
251
|
+
// file. It walks the file exactly once — gathering declared names and
|
|
252
|
+
// assignment targets in the same pass — so the cost is linear in file size.
|
|
253
|
+
//
|
|
254
|
+
// The earlier shape registered for `declKind` directly and, on every
|
|
255
|
+
// declaration, re-scanned the whole file for assignments: O(declarations ×
|
|
256
|
+
// file size), which blows up quadratically on a file with many top-level
|
|
257
|
+
// functions. Visiting `KindSourceFile` once and cross-referencing afterward
|
|
258
|
+
// keeps the same findings without the repeated scans.
|
|
259
|
+
func reportAssignmentsToDeclarations(
|
|
260
|
+
ctx *Context,
|
|
261
|
+
file *shimast.Node,
|
|
262
|
+
declKind shimast.Kind,
|
|
263
|
+
noun string,
|
|
264
|
+
) {
|
|
265
|
+
if ctx == nil || file == nil {
|
|
256
266
|
return
|
|
257
267
|
}
|
|
258
|
-
|
|
259
|
-
|
|
268
|
+
declared := map[string]struct{}{}
|
|
269
|
+
var targets []*shimast.Node
|
|
270
|
+
walkDescendants(file, func(n *shimast.Node) {
|
|
271
|
+
switch n.Kind {
|
|
272
|
+
case declKind:
|
|
273
|
+
if name := declarationName(n); name != "" {
|
|
274
|
+
declared[name] = struct{}{}
|
|
275
|
+
}
|
|
276
|
+
case shimast.KindBinaryExpression:
|
|
277
|
+
if expr := n.AsBinaryExpression(); expr != nil &&
|
|
278
|
+
expr.OperatorToken != nil && isAssignmentOperator(expr.OperatorToken.Kind) &&
|
|
279
|
+
expr.Left != nil && expr.Left.Kind == shimast.KindIdentifier {
|
|
280
|
+
targets = append(targets, expr.Left)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
})
|
|
284
|
+
if len(declared) == 0 || len(targets) == 0 {
|
|
260
285
|
return
|
|
261
286
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
287
|
+
for _, target := range targets {
|
|
288
|
+
name := identifierText(target)
|
|
289
|
+
if _, ok := declared[name]; ok {
|
|
290
|
+
ctx.Report(target, "'"+name+"' "+noun)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// declarationName returns the bound name of a class or function declaration
|
|
296
|
+
// node, or "" when the node is neither (or is anonymous).
|
|
297
|
+
func declarationName(n *shimast.Node) string {
|
|
298
|
+
switch n.Kind {
|
|
299
|
+
case shimast.KindFunctionDeclaration:
|
|
300
|
+
if d := n.AsFunctionDeclaration(); d != nil {
|
|
301
|
+
return identifierText(d.Name())
|
|
302
|
+
}
|
|
303
|
+
case shimast.KindClassDeclaration:
|
|
304
|
+
if d := n.AsClassDeclaration(); d != nil {
|
|
305
|
+
return identifierText(d.Name())
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return ""
|
|
265
309
|
}
|
|
266
310
|
|
|
267
311
|
// no-prototype-builtins: `obj.hasOwnProperty(x)` — should be
|
|
@@ -19,6 +19,9 @@ import (
|
|
|
19
19
|
type awaitThenable struct{}
|
|
20
20
|
|
|
21
21
|
func (awaitThenable) Name() string { return "await-thenable" }
|
|
22
|
+
func (awaitThenable) NeedsTypeChecker() bool {
|
|
23
|
+
return true
|
|
24
|
+
}
|
|
22
25
|
func (awaitThenable) Visits() []shimast.Kind {
|
|
23
26
|
return []shimast.Kind{shimast.KindAwaitExpression}
|
|
24
27
|
}
|
|
@@ -7,6 +7,7 @@ import (
|
|
|
7
7
|
"strings"
|
|
8
8
|
|
|
9
9
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
10
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
10
11
|
)
|
|
11
12
|
|
|
12
13
|
// no-alert: `alert()` / `confirm()` / `prompt()`. Rarely the right
|
|
@@ -742,16 +743,72 @@ func (noUnneededTernary) Visits() []shimast.Kind {
|
|
|
742
743
|
}
|
|
743
744
|
func (noUnneededTernary) Check(ctx *Context, node *shimast.Node) {
|
|
744
745
|
cond := node.AsConditionalExpression()
|
|
745
|
-
if cond == nil {
|
|
746
|
+
if cond == nil || cond.Condition == nil {
|
|
746
747
|
return
|
|
747
748
|
}
|
|
748
749
|
t := stripParens(cond.WhenTrue)
|
|
749
750
|
f := stripParens(cond.WhenFalse)
|
|
750
751
|
tBool, tOk := isLiteralBoolean(t)
|
|
751
752
|
fBool, fOk := isLiteralBoolean(f)
|
|
752
|
-
if tOk && fOk && tBool != fBool {
|
|
753
|
-
|
|
753
|
+
if !(tOk && fOk && tBool != fBool) {
|
|
754
|
+
return
|
|
755
|
+
}
|
|
756
|
+
message := "Unnecessary use of conditional expression for boolean."
|
|
757
|
+
src := ctx.File.Text()
|
|
758
|
+
condStart := shimscanner.SkipTrivia(src, cond.Condition.Pos())
|
|
759
|
+
if condStart < 0 || condStart >= cond.Condition.End() {
|
|
760
|
+
ctx.Report(node, message)
|
|
761
|
+
return
|
|
762
|
+
}
|
|
763
|
+
condText := src[condStart:cond.Condition.End()]
|
|
764
|
+
var replacement string
|
|
765
|
+
if tBool {
|
|
766
|
+
// `cond ? true : false` → `Boolean(cond)`
|
|
767
|
+
replacement = "Boolean(" + condText + ")"
|
|
768
|
+
} else {
|
|
769
|
+
// `cond ? false : true` → `!cond`. Wrap the condition in parentheses
|
|
770
|
+
// when it is not already a primary expression so operator precedence
|
|
771
|
+
// does not flip the meaning (e.g. `a || b` must become `!(a || b)`).
|
|
772
|
+
if needsParensForUnaryNegation(cond.Condition) {
|
|
773
|
+
replacement = "!(" + condText + ")"
|
|
774
|
+
} else {
|
|
775
|
+
replacement = "!" + condText
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
editPos := shimscanner.SkipTrivia(src, node.Pos())
|
|
779
|
+
if editPos < 0 || editPos >= node.End() {
|
|
780
|
+
ctx.Report(node, message)
|
|
781
|
+
return
|
|
782
|
+
}
|
|
783
|
+
ctx.ReportFix(
|
|
784
|
+
node,
|
|
785
|
+
message,
|
|
786
|
+
TextEdit{Pos: editPos, End: node.End(), Text: replacement},
|
|
787
|
+
)
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// needsParensForUnaryNegation reports whether `cond` must be wrapped in
|
|
791
|
+
// parentheses before prefixing with `!`. Anything looser than a unary /
|
|
792
|
+
// member / primary expression flips precedence when negated. Mirrors
|
|
793
|
+
// ESLint's `no-unneeded-ternary` autofix safety check.
|
|
794
|
+
func needsParensForUnaryNegation(node *shimast.Node) bool {
|
|
795
|
+
inner := stripParens(node)
|
|
796
|
+
if inner == nil {
|
|
797
|
+
return false
|
|
798
|
+
}
|
|
799
|
+
switch inner.Kind {
|
|
800
|
+
case shimast.KindBinaryExpression,
|
|
801
|
+
shimast.KindConditionalExpression,
|
|
802
|
+
shimast.KindYieldExpression,
|
|
803
|
+
shimast.KindAwaitExpression,
|
|
804
|
+
shimast.KindArrowFunction,
|
|
805
|
+
shimast.KindFunctionExpression,
|
|
806
|
+
shimast.KindAsExpression,
|
|
807
|
+
shimast.KindSatisfiesExpression,
|
|
808
|
+
shimast.KindTypeAssertionExpression:
|
|
809
|
+
return true
|
|
754
810
|
}
|
|
811
|
+
return false
|
|
755
812
|
}
|
|
756
813
|
|
|
757
814
|
// no-unused-expressions: an expression statement whose value isn't used.
|
|
@@ -1099,9 +1156,27 @@ func (preferTemplate) Check(ctx *Context, node *shimast.Node) {
|
|
|
1099
1156
|
}
|
|
1100
1157
|
}
|
|
1101
1158
|
hasString, hasOther := concatChainShape(node)
|
|
1102
|
-
if hasString && hasOther {
|
|
1103
|
-
|
|
1159
|
+
if !(hasString && hasOther) {
|
|
1160
|
+
return
|
|
1161
|
+
}
|
|
1162
|
+
message := "Unexpected string concatenation."
|
|
1163
|
+
src := ctx.File.Text()
|
|
1164
|
+
operands := flattenConcatOperands(node)
|
|
1165
|
+
template, ok := renderConcatAsTemplate(src, operands)
|
|
1166
|
+
if !ok {
|
|
1167
|
+
ctx.Report(node, message)
|
|
1168
|
+
return
|
|
1104
1169
|
}
|
|
1170
|
+
editPos := shimscanner.SkipTrivia(src, node.Pos())
|
|
1171
|
+
if editPos < 0 || editPos >= node.End() {
|
|
1172
|
+
ctx.Report(node, message)
|
|
1173
|
+
return
|
|
1174
|
+
}
|
|
1175
|
+
ctx.ReportFix(
|
|
1176
|
+
node,
|
|
1177
|
+
message,
|
|
1178
|
+
TextEdit{Pos: editPos, End: node.End(), Text: template},
|
|
1179
|
+
)
|
|
1105
1180
|
}
|
|
1106
1181
|
|
|
1107
1182
|
func concatChainShape(node *shimast.Node) (hasString bool, hasOther bool) {
|
|
@@ -1122,6 +1197,86 @@ func concatChainShape(node *shimast.Node) (hasString bool, hasOther bool) {
|
|
|
1122
1197
|
return false, true
|
|
1123
1198
|
}
|
|
1124
1199
|
|
|
1200
|
+
// flattenConcatOperands walks a `+` chain left-to-right and returns each
|
|
1201
|
+
// leaf operand in source order. Parenthesized sub-expressions are kept as
|
|
1202
|
+
// a single operand so the rendered template literal does not lose their
|
|
1203
|
+
// grouping. Mirrors ESLint's behavior of treating `("a" + b)` as one
|
|
1204
|
+
// expression slot when it appears inside a larger chain.
|
|
1205
|
+
func flattenConcatOperands(node *shimast.Node) []*shimast.Node {
|
|
1206
|
+
if node == nil {
|
|
1207
|
+
return nil
|
|
1208
|
+
}
|
|
1209
|
+
if node.Kind == shimast.KindBinaryExpression {
|
|
1210
|
+
bin := node.AsBinaryExpression()
|
|
1211
|
+
if bin != nil && bin.OperatorToken != nil && bin.OperatorToken.Kind == shimast.KindPlusToken {
|
|
1212
|
+
out := flattenConcatOperands(bin.Left)
|
|
1213
|
+
out = append(out, flattenConcatOperands(bin.Right)...)
|
|
1214
|
+
return out
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
return []*shimast.Node{node}
|
|
1218
|
+
}
|
|
1219
|
+
|
|
1220
|
+
// renderConcatAsTemplate renders the flattened concat operands as a single
|
|
1221
|
+
// backtick template literal. String-like literals contribute their value
|
|
1222
|
+
// directly (with template-specific escaping); any other expression becomes
|
|
1223
|
+
// a `${…}` placeholder copied verbatim from the source text. Returns ok=false
|
|
1224
|
+
// when an operand cannot be rendered (typically because its source range is
|
|
1225
|
+
// unavailable), so the caller falls back to detection-only.
|
|
1226
|
+
func renderConcatAsTemplate(src string, operands []*shimast.Node) (string, bool) {
|
|
1227
|
+
if len(operands) == 0 {
|
|
1228
|
+
return "", false
|
|
1229
|
+
}
|
|
1230
|
+
var sb strings.Builder
|
|
1231
|
+
sb.WriteByte('`')
|
|
1232
|
+
for _, operand := range operands {
|
|
1233
|
+
if operand == nil {
|
|
1234
|
+
return "", false
|
|
1235
|
+
}
|
|
1236
|
+
inner := stripParens(operand)
|
|
1237
|
+
if isStringLikeLiteral(inner) {
|
|
1238
|
+
sb.WriteString(escapeTemplateLiteralBody(stringLiteralText(inner)))
|
|
1239
|
+
continue
|
|
1240
|
+
}
|
|
1241
|
+
pos := shimscanner.SkipTrivia(src, operand.Pos())
|
|
1242
|
+
end := operand.End()
|
|
1243
|
+
if pos < 0 || pos >= end || end > len(src) {
|
|
1244
|
+
return "", false
|
|
1245
|
+
}
|
|
1246
|
+
sb.WriteString("${")
|
|
1247
|
+
sb.WriteString(src[pos:end])
|
|
1248
|
+
sb.WriteByte('}')
|
|
1249
|
+
}
|
|
1250
|
+
sb.WriteByte('`')
|
|
1251
|
+
return sb.String(), true
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
// escapeTemplateLiteralBody escapes the characters that would otherwise
|
|
1255
|
+
// terminate or interpolate a template literal body: backslash, backtick,
|
|
1256
|
+
// and the `${` sequence. Matches the canonical ESLint `prefer-template`
|
|
1257
|
+
// fixer escape set.
|
|
1258
|
+
func escapeTemplateLiteralBody(text string) string {
|
|
1259
|
+
var sb strings.Builder
|
|
1260
|
+
sb.Grow(len(text))
|
|
1261
|
+
for i := 0; i < len(text); i++ {
|
|
1262
|
+
ch := text[i]
|
|
1263
|
+
switch ch {
|
|
1264
|
+
case '\\', '`':
|
|
1265
|
+
sb.WriteByte('\\')
|
|
1266
|
+
sb.WriteByte(ch)
|
|
1267
|
+
case '$':
|
|
1268
|
+
if i+1 < len(text) && text[i+1] == '{' {
|
|
1269
|
+
sb.WriteString("\\$")
|
|
1270
|
+
} else {
|
|
1271
|
+
sb.WriteByte('$')
|
|
1272
|
+
}
|
|
1273
|
+
default:
|
|
1274
|
+
sb.WriteByte(ch)
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
return sb.String()
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1125
1280
|
// require-yield: `function* gen() { return 1; }` — generators that
|
|
1126
1281
|
// never yield are usually unintended.
|
|
1127
1282
|
type requireYield struct{}
|
package/linthost/rules_var.go
CHANGED
|
@@ -13,6 +13,12 @@ func (noVar) Check(ctx *Context, node *shimast.Node) {
|
|
|
13
13
|
if stmt == nil || stmt.DeclarationList == nil {
|
|
14
14
|
return
|
|
15
15
|
}
|
|
16
|
+
if ctx.File != nil && ctx.File.IsDeclarationFile {
|
|
17
|
+
return
|
|
18
|
+
}
|
|
19
|
+
if node.ModifierFlags()&shimast.ModifierFlagsAmbient != 0 {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
16
22
|
if shimast.IsVar(stmt.DeclarationList) {
|
|
17
23
|
start := keywordStart(ctx.File, stmt.DeclarationList, "var")
|
|
18
24
|
if start >= 0 {
|
|
@@ -71,7 +77,7 @@ func (preferConst) Check(ctx *Context, node *shimast.Node) {
|
|
|
71
77
|
if expr == nil || expr.OperatorToken == nil || !isAssignmentOperator(expr.OperatorToken.Kind) {
|
|
72
78
|
return
|
|
73
79
|
}
|
|
74
|
-
for _, name := range
|
|
80
|
+
for _, name := range assignmentTargetNames(expr.Left) {
|
|
75
81
|
assigned[name] = true
|
|
76
82
|
}
|
|
77
83
|
case shimast.KindPrefixUnaryExpression:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/lint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Reference ttsc plugin: ESLint-style lint rules hosted in the same Program/Checker as the type-check pass.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -33,10 +33,10 @@
|
|
|
33
33
|
"src"
|
|
34
34
|
],
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
36
|
+
"@typescript/native-preview": "7.0.0-dev.20260522.1",
|
|
37
37
|
"@types/node": "^25.3.0",
|
|
38
38
|
"rimraf": "^6.1.2",
|
|
39
|
-
"ttsc": "0.
|
|
39
|
+
"ttsc": "0.13.1"
|
|
40
40
|
},
|
|
41
41
|
"repository": {
|
|
42
42
|
"type": "git",
|