@ttsc/lint 0.12.3 → 0.13.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/lib/defaultFormat.d.ts +10 -11
- package/lib/defaultFormat.js +10 -11
- package/lib/defaultFormat.js.map +1 -1
- package/lib/index.js +233 -135
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintConfig.d.ts +2 -2
- package/lib/structures/ITtscLintFormatConfig.d.ts +53 -55
- package/lib/structures/ITtscLintPluginConfig.d.ts +10 -77
- package/lib/structures/ITtscLintPluginMeta.d.ts +9 -1
- package/lib/structures/TtscLintRule.d.ts +10 -1
- package/lib/structures/TtscLintRuleMap.d.ts +2 -2
- package/lib/structures/TtscLintRuleOptions.d.ts +14 -0
- package/linthost/ast_helpers.go +80 -11
- package/linthost/compile.go +116 -112
- package/linthost/config.go +647 -687
- package/linthost/config_format.go +272 -247
- package/linthost/contrib_adapter.go +7 -0
- package/linthost/directives.go +116 -0
- package/linthost/dispatch.go +33 -33
- package/linthost/engine.go +156 -43
- package/linthost/fix.go +47 -27
- package/linthost/flags_gen.go +31 -0
- package/linthost/format.go +119 -3
- package/linthost/host.go +115 -5
- package/linthost/print_dispatch.go +128 -28
- package/linthost/print_doc.go +23 -0
- package/linthost/print_engine.go +168 -4
- package/linthost/print_nodes_array.go +17 -7
- package/linthost/print_nodes_call.go +138 -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_arrays.go +5 -2
- package/linthost/rules_debugger.go +3 -2
- package/linthost/rules_dupes.go +7 -4
- package/linthost/rules_empty.go +3 -2
- package/linthost/rules_escape.go +35 -3
- package/linthost/rules_eval.go +3 -0
- package/linthost/rules_finally.go +11 -0
- package/linthost/rules_format_jsdoc.go +7 -0
- package/linthost/rules_format_print_width.go +270 -15
- package/linthost/rules_format_quotes.go +3 -0
- package/linthost/rules_format_sort_imports.go +4 -0
- package/linthost/rules_gap.go +75 -3
- package/linthost/rules_imports.go +5 -7
- package/linthost/rules_logic.go +75 -5
- package/linthost/rules_loops.go +4 -0
- package/linthost/rules_misc.go +4 -2
- package/linthost/rules_params.go +7 -11
- package/linthost/rules_problems.go +89 -22
- package/linthost/rules_promise.go +15 -0
- package/linthost/rules_protos.go +3 -2
- package/linthost/rules_self.go +6 -0
- package/linthost/rules_strings.go +10 -0
- package/linthost/rules_suggestions.go +174 -9
- package/linthost/rules_throw.go +2 -0
- package/linthost/rules_ts.go +13 -0
- package/linthost/rules_ts_extra.go +25 -8
- package/linthost/rules_var.go +15 -2
- package/package.json +3 -3
- package/plugin/main.go +4 -4
- package/rule/astutil/astutil.go +12 -0
- package/rule/rule.go +123 -123
- package/src/defaultFormat.ts +10 -11
- package/src/index.ts +257 -168
- package/src/structures/ITtscLintConfig.ts +2 -2
- package/src/structures/ITtscLintFormatConfig.ts +53 -55
- package/src/structures/ITtscLintPluginConfig.ts +10 -83
- package/src/structures/ITtscLintPluginMeta.ts +9 -1
- package/src/structures/TtscLintRule.ts +10 -1
- package/src/structures/TtscLintRuleMap.ts +17 -18
- package/src/structures/TtscLintRuleOptions.ts +15 -0
- package/linthost/eslint_runtime.go +0 -320
|
@@ -20,56 +20,70 @@ import (
|
|
|
20
20
|
// mode, matching Prettier's `bracketSpacing: true` default for import
|
|
21
21
|
// declarations (which is hard-coded; Prettier ignores `bracketSpacing`
|
|
22
22
|
// for imports). Empty named imports `{}` collapse cleanly.
|
|
23
|
-
|
|
23
|
+
//
|
|
24
|
+
// The second return value is the `covered` flag: see PrintNode.
|
|
25
|
+
func printNamedImports(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
24
26
|
if node == nil {
|
|
25
|
-
return Doc{}
|
|
27
|
+
return Doc{}, true
|
|
26
28
|
}
|
|
27
29
|
ni := node.AsNamedImports()
|
|
28
30
|
if ni == nil || ni.Elements == nil {
|
|
29
|
-
return verbatim(ctx, node)
|
|
31
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
30
32
|
}
|
|
31
33
|
items := make([]Doc, 0, len(ni.Elements.Nodes))
|
|
34
|
+
covered := true
|
|
32
35
|
for _, spec := range ni.Elements.Nodes {
|
|
33
36
|
if spec == nil {
|
|
34
|
-
return verbatim(ctx, node)
|
|
37
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
35
38
|
}
|
|
36
|
-
doc,
|
|
39
|
+
doc, childCovered := PrintNode(ctx, spec)
|
|
40
|
+
covered = covered && childCovered
|
|
37
41
|
items = append(items, doc)
|
|
38
42
|
}
|
|
43
|
+
// AddComma honors `format.trailingComma`: named imports are an
|
|
44
|
+
// ES5-permitted comma position, so "all" and "es5" both keep the
|
|
45
|
+
// trailing comma; only "none" drops it.
|
|
39
46
|
return printList(ctx, listShape{
|
|
40
47
|
OpenTok: "{",
|
|
41
48
|
CloseTok: "}",
|
|
42
49
|
Items: items,
|
|
43
50
|
Space: true,
|
|
44
|
-
AddComma:
|
|
45
|
-
})
|
|
51
|
+
AddComma: ctx.allowsEs5TrailingComma(),
|
|
52
|
+
}), covered
|
|
46
53
|
}
|
|
47
54
|
|
|
48
55
|
// printNamedExports renders `export { a, b }`. The shape is identical
|
|
49
56
|
// to NamedImports; only the surrounding declaration differs.
|
|
50
|
-
|
|
57
|
+
//
|
|
58
|
+
// The second return value is the `covered` flag: see PrintNode.
|
|
59
|
+
func printNamedExports(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
51
60
|
if node == nil {
|
|
52
|
-
return Doc{}
|
|
61
|
+
return Doc{}, true
|
|
53
62
|
}
|
|
54
63
|
ne := node.AsNamedExports()
|
|
55
64
|
if ne == nil || ne.Elements == nil {
|
|
56
|
-
return verbatim(ctx, node)
|
|
65
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
57
66
|
}
|
|
58
67
|
items := make([]Doc, 0, len(ne.Elements.Nodes))
|
|
68
|
+
covered := true
|
|
59
69
|
for _, spec := range ne.Elements.Nodes {
|
|
60
70
|
if spec == nil {
|
|
61
|
-
return verbatim(ctx, node)
|
|
71
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
62
72
|
}
|
|
63
|
-
doc,
|
|
73
|
+
doc, childCovered := PrintNode(ctx, spec)
|
|
74
|
+
covered = covered && childCovered
|
|
64
75
|
items = append(items, doc)
|
|
65
76
|
}
|
|
77
|
+
// AddComma honors `format.trailingComma`: named exports are an
|
|
78
|
+
// ES5-permitted comma position, so "all" and "es5" both keep the
|
|
79
|
+
// trailing comma; only "none" drops it.
|
|
66
80
|
return printList(ctx, listShape{
|
|
67
81
|
OpenTok: "{",
|
|
68
82
|
CloseTok: "}",
|
|
69
83
|
Items: items,
|
|
70
84
|
Space: true,
|
|
71
|
-
AddComma:
|
|
72
|
-
})
|
|
85
|
+
AddComma: ctx.allowsEs5TrailingComma(),
|
|
86
|
+
}), covered
|
|
73
87
|
}
|
|
74
88
|
|
|
75
89
|
// printImportDeclaration renders the surrounding `import … from "x";`.
|
|
@@ -79,13 +93,15 @@ func printNamedExports(ctx *PrintContext, node *shimast.Node) Doc {
|
|
|
79
93
|
//
|
|
80
94
|
// The dispatcher hands off to the per-clause printers; the top-level
|
|
81
95
|
// frame stitches them together with the keywords and `from` token.
|
|
82
|
-
|
|
96
|
+
//
|
|
97
|
+
// The second return value is the `covered` flag: see PrintNode.
|
|
98
|
+
func printImportDeclaration(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
83
99
|
if node == nil {
|
|
84
|
-
return Doc{}
|
|
100
|
+
return Doc{}, true
|
|
85
101
|
}
|
|
86
102
|
imp := node.AsImportDeclaration()
|
|
87
103
|
if imp == nil {
|
|
88
|
-
return verbatim(ctx, node)
|
|
104
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
89
105
|
}
|
|
90
106
|
// If the declaration uses anything other than a vanilla
|
|
91
107
|
// `import { ... } from "x"` shape (default specifier, namespace
|
|
@@ -93,20 +109,26 @@ func printImportDeclaration(ctx *PrintContext, node *shimast.Node) Doc {
|
|
|
93
109
|
// canonical reflow target is the named-import body.
|
|
94
110
|
clause := imp.ImportClause
|
|
95
111
|
if clause == nil {
|
|
96
|
-
return verbatim(ctx, node)
|
|
112
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
97
113
|
}
|
|
98
114
|
clauseData := clause.AsImportClause()
|
|
99
115
|
if clauseData == nil || clauseData.NamedBindings == nil {
|
|
100
|
-
return verbatim(ctx, node)
|
|
116
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
101
117
|
}
|
|
102
118
|
if clauseData.NamedBindings.Kind != shimast.KindNamedImports {
|
|
103
119
|
// Namespace imports (`import * as ns from "x"`) have no
|
|
104
120
|
// reflow surface; leave them alone.
|
|
105
|
-
return verbatim(ctx, node)
|
|
121
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
106
122
|
}
|
|
107
123
|
if clause.Name() != nil {
|
|
108
124
|
// `import Default, { … } from "x"` — keep verbatim for v1.
|
|
109
|
-
return verbatim(ctx, node)
|
|
125
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
126
|
+
}
|
|
127
|
+
// AttributeClause (`with { ... }` / `assert { ... }`) lives after
|
|
128
|
+
// the module specifier. Fall back to verbatim when present so we
|
|
129
|
+
// don't drop attributes silently.
|
|
130
|
+
if imp.Attributes != nil {
|
|
131
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
110
132
|
}
|
|
111
133
|
|
|
112
134
|
// Bracketed clause prefix: `import ` (and optional `type `).
|
|
@@ -114,8 +136,9 @@ func printImportDeclaration(ctx *PrintContext, node *shimast.Node) Doc {
|
|
|
114
136
|
if clause.IsTypeOnly() {
|
|
115
137
|
prefix = "import type "
|
|
116
138
|
}
|
|
117
|
-
named,
|
|
139
|
+
named, covered := PrintNode(ctx, clauseData.NamedBindings)
|
|
118
140
|
moduleSpec := verbatim(ctx, imp.ModuleSpecifier)
|
|
141
|
+
covered = covered && !nodeSpansMultipleLines(ctx, imp.ModuleSpecifier)
|
|
119
142
|
parts := []Doc{Text(prefix), named, Text(" from "), moduleSpec}
|
|
120
143
|
if sourceHasStatementTerminator(ctx.Source, node.End()) {
|
|
121
144
|
// Preserve the user's terminator decision. Emitting `;`
|
|
@@ -124,13 +147,7 @@ func printImportDeclaration(ctx *PrintContext, node *shimast.Node) Doc {
|
|
|
124
147
|
// `;;` — `format/semi` owns terminator placement.
|
|
125
148
|
parts = append(parts, Text(";"))
|
|
126
149
|
}
|
|
127
|
-
|
|
128
|
-
// the module specifier. Fall back to verbatim when present so we
|
|
129
|
-
// don't drop attributes silently.
|
|
130
|
-
if imp.Attributes != nil {
|
|
131
|
-
return verbatim(ctx, node)
|
|
132
|
-
}
|
|
133
|
-
return Concat(parts...)
|
|
150
|
+
return Concat(parts...), covered
|
|
134
151
|
}
|
|
135
152
|
|
|
136
153
|
// sourceHasStatementTerminator reports whether the last non-trivia
|
|
@@ -27,15 +27,53 @@ type listShape struct {
|
|
|
27
27
|
Items []Doc
|
|
28
28
|
Space bool // emit a space after OPEN / before CLOSE in flat mode
|
|
29
29
|
AddComma bool // emit a trailing comma in broken mode
|
|
30
|
+
// HugLast keeps the final item attached to the parens instead of
|
|
31
|
+
// exploding the whole list. It is set by the call/new argument
|
|
32
|
+
// printer when the last argument is a callback or object literal —
|
|
33
|
+
// see printArgList. When true, the last item is emitted directly
|
|
34
|
+
// against OPEN…CLOSE with no leading/trailing soft break, so a
|
|
35
|
+
// multi-line callback body does not force every preceding argument
|
|
36
|
+
// onto its own line.
|
|
37
|
+
HugLast bool
|
|
38
|
+
// ForceBreak commits the list to its broken, one-item-per-line shape
|
|
39
|
+
// even when it would fit flat. The object-literal printer sets it to
|
|
40
|
+
// mirror Prettier's objectWrap:"preserve" — an object the source
|
|
41
|
+
// wrote with a newline after `{` stays expanded.
|
|
42
|
+
ForceBreak bool
|
|
30
43
|
}
|
|
31
44
|
|
|
32
45
|
// printList renders the list shape as a Doc tree. Empty lists collapse
|
|
33
|
-
// to `OPENCLOSE`.
|
|
34
|
-
//
|
|
46
|
+
// to `OPENCLOSE`. A HugLast list becomes a ConditionalGroup of up to
|
|
47
|
+
// three shapes; every other list is the plain fit-or-break Group.
|
|
35
48
|
func printList(ctx *PrintContext, shape listShape) Doc {
|
|
36
49
|
if len(shape.Items) == 0 {
|
|
37
50
|
return Text(shape.OpenTok + shape.CloseTok)
|
|
38
51
|
}
|
|
52
|
+
plain := printListPlain(ctx, shape)
|
|
53
|
+
if !shape.HugLast {
|
|
54
|
+
return plain
|
|
55
|
+
}
|
|
56
|
+
// A HugLast list offers the engine up to three shapes, in preference
|
|
57
|
+
// order:
|
|
58
|
+
// 1. allFlat — every item on one line, chosen when it fits;
|
|
59
|
+
// 2. hugged — leading items inline, the final callback or object
|
|
60
|
+
// committed to its multi-line shape, chosen when its opening
|
|
61
|
+
// line fits but the all-flat form does not;
|
|
62
|
+
// 3. plain — every item exploded onto its own indented line.
|
|
63
|
+
// The all-flat option is dropped when the list cannot render flat
|
|
64
|
+
// (a block-bodied callback argument carries hard line breaks).
|
|
65
|
+
hugged := printListHuggingLast(ctx, shape)
|
|
66
|
+
if allFlat, ok := flatten(plain); ok {
|
|
67
|
+
return ConditionalGroup(allFlat, hugged, plain)
|
|
68
|
+
}
|
|
69
|
+
return ConditionalGroup(hugged, plain)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// printListPlain renders the open-comma-close list as a single
|
|
73
|
+
// fit-or-break Group: flat (`OPEN a, b CLOSE`) when it fits the width
|
|
74
|
+
// budget, one item per indented line — with an optional trailing
|
|
75
|
+
// comma — when it does not.
|
|
76
|
+
func printListPlain(ctx *PrintContext, shape listShape) Doc {
|
|
39
77
|
sep := Concat(Text(","), Line())
|
|
40
78
|
body := Join(sep, shape.Items)
|
|
41
79
|
|
|
@@ -53,12 +91,55 @@ func printList(ctx *PrintContext, shape listShape) Doc {
|
|
|
53
91
|
closeTok := Text(shape.CloseTok)
|
|
54
92
|
|
|
55
93
|
// In flat mode: OPEN [pad] body [pad] CLOSE
|
|
56
|
-
// In broken mode: OPEN \n body, \n CLOSE
|
|
57
|
-
// flat form collapses cleanly.
|
|
94
|
+
// In broken mode: OPEN \n body, \n CLOSE.
|
|
58
95
|
leadingSep := IfBreak(Hardline(), flatPad)
|
|
59
96
|
trailingSep := IfBreak(Hardline(), flatPad)
|
|
60
97
|
|
|
61
98
|
bodyBlock := Indent(ctx.indentUnit(), leadingSep, body, trailing)
|
|
62
99
|
doc := Concat(openTok, bodyBlock, trailingSep, closeTok)
|
|
63
|
-
|
|
100
|
+
group := Group(doc)
|
|
101
|
+
// ForceBreak (object-literal newline preservation) commits the group
|
|
102
|
+
// to its broken shape regardless of fit.
|
|
103
|
+
group.Break = shape.ForceBreak
|
|
104
|
+
return group
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// printListHuggingLast renders the "last-argument hugging" shape that
|
|
108
|
+
// Prettier uses for `foo(a, b, () => { … })`: the leading items flow
|
|
109
|
+
// comma-separated and the final item stays attached to the closing
|
|
110
|
+
// paren instead of being pushed onto its own indented line.
|
|
111
|
+
//
|
|
112
|
+
// hugged: OPEN a, b, OPEN-of-last … CLOSE-of-last CLOSE
|
|
113
|
+
//
|
|
114
|
+
// The result is a plain Concat, not a Group: the hugged last argument
|
|
115
|
+
// is a callback or object literal whose own printer carries the
|
|
116
|
+
// fit-or-break decision for its body, so wrapping here would let a
|
|
117
|
+
// multi-line body force the `a, b,` prefix to break. printList offers
|
|
118
|
+
// this Concat as a ConditionalGroup option; when its opening line would
|
|
119
|
+
// overflow printWidth the engine falls back to the plain exploded list.
|
|
120
|
+
//
|
|
121
|
+
// The hugged final item is forced broken — via forceBreakFirstGroup —
|
|
122
|
+
// so the hugged option is genuinely multi-line and distinct from the
|
|
123
|
+
// all-flat option. A flat hugged object would otherwise be
|
|
124
|
+
// byte-identical to all-flat yet escape its width check. The break
|
|
125
|
+
// reaches the first Group in the item's subtree, so an object or array
|
|
126
|
+
// nested inside an arrow body (`(x) => ({ … })`) breaks too.
|
|
127
|
+
func printListHuggingLast(ctx *PrintContext, shape listShape) Doc {
|
|
128
|
+
last := shape.Items[len(shape.Items)-1]
|
|
129
|
+
lead := shape.Items[:len(shape.Items)-1]
|
|
130
|
+
// Force the hugged item's first Group broken only when the item has
|
|
131
|
+
// no hard line breaks of its own. A block-bodied callback already
|
|
132
|
+
// renders multi-line through its block's hard breaks; descending into
|
|
133
|
+
// it with forceBreakFirstGroup would instead force some unrelated
|
|
134
|
+
// nested Group — a plain statement's call-argument list — broken.
|
|
135
|
+
if _, flat := flatten(last); flat {
|
|
136
|
+
last, _ = forceBreakFirstGroup(last)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
parts := []Doc{Text(shape.OpenTok)}
|
|
140
|
+
for _, item := range lead {
|
|
141
|
+
parts = append(parts, item, Text(", "))
|
|
142
|
+
}
|
|
143
|
+
parts = append(parts, last, Text(shape.CloseTok))
|
|
144
|
+
return Concat(parts...)
|
|
64
145
|
}
|
|
@@ -2,6 +2,7 @@ package linthost
|
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
4
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
5
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
5
6
|
)
|
|
6
7
|
|
|
7
8
|
// printObjectLiteral renders an ObjectLiteralExpression with width-aware
|
|
@@ -22,30 +23,74 @@ import (
|
|
|
22
23
|
// The flat form uses a single space inside the braces, matching
|
|
23
24
|
// Prettier's `bracketSpacing: true` default. Empty object literals
|
|
24
25
|
// collapse to `{}` with no inner space, matching every formatter.
|
|
25
|
-
|
|
26
|
+
//
|
|
27
|
+
// The second return value is the `covered` flag: see PrintNode. It is
|
|
28
|
+
// the AND of every property's coverage — one multi-line verbatim
|
|
29
|
+
// member taints the whole literal.
|
|
30
|
+
func printObjectLiteral(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
26
31
|
if node == nil {
|
|
27
|
-
return Doc{}
|
|
32
|
+
return Doc{}, true
|
|
28
33
|
}
|
|
29
34
|
obj := node.AsObjectLiteralExpression()
|
|
30
35
|
if obj == nil || obj.Properties == nil {
|
|
31
|
-
return verbatim(ctx, node)
|
|
36
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
32
37
|
}
|
|
33
38
|
items := make([]Doc, 0, len(obj.Properties.Nodes))
|
|
39
|
+
covered := true
|
|
34
40
|
for _, prop := range obj.Properties.Nodes {
|
|
35
41
|
if prop == nil {
|
|
36
42
|
// A nil child entry would render as an empty Doc and surface
|
|
37
43
|
// as `a, , b` in the output. Bail to verbatim so the source
|
|
38
44
|
// bytes round-trip unchanged.
|
|
39
|
-
return verbatim(ctx, node)
|
|
45
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
40
46
|
}
|
|
41
|
-
doc,
|
|
47
|
+
doc, childCovered := PrintNode(ctx, prop)
|
|
48
|
+
covered = covered && childCovered
|
|
42
49
|
items = append(items, doc)
|
|
43
50
|
}
|
|
51
|
+
// objectWrap:"preserve" — keep a non-empty object expanded when the
|
|
52
|
+
// source wrote a newline after `{`. An empty literal has no first
|
|
53
|
+
// property to anchor the check and never preserves.
|
|
54
|
+
forceBreak := false
|
|
55
|
+
if len(obj.Properties.Nodes) > 0 {
|
|
56
|
+
forceBreak = objectHasNewlineAfterBrace(ctx.Source, node, obj.Properties.Nodes[0])
|
|
57
|
+
}
|
|
58
|
+
// AddComma honors `format.trailingComma`: object literals accept
|
|
59
|
+
// trailing commas in ES5 so both "all" and "es5" keep them; only
|
|
60
|
+
// "none" suppresses. Pairs with the call/array branches so the printer
|
|
61
|
+
// never disagrees with the trailing-comma rule on the same setting.
|
|
44
62
|
return printList(ctx, listShape{
|
|
45
|
-
OpenTok:
|
|
46
|
-
CloseTok:
|
|
47
|
-
Items:
|
|
48
|
-
Space:
|
|
49
|
-
AddComma:
|
|
50
|
-
|
|
63
|
+
OpenTok: "{",
|
|
64
|
+
CloseTok: "}",
|
|
65
|
+
Items: items,
|
|
66
|
+
Space: true,
|
|
67
|
+
AddComma: ctx.allowsEs5TrailingComma(),
|
|
68
|
+
ForceBreak: forceBreak,
|
|
69
|
+
}), covered
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// objectHasNewlineAfterBrace reports whether the source places a
|
|
73
|
+
// newline between the object literal's `{` and its first property.
|
|
74
|
+
//
|
|
75
|
+
// Prettier's objectWrap:"preserve" default keeps such an object
|
|
76
|
+
// expanded even when it would fit flat, treating the author's line
|
|
77
|
+
// break as intentional structure. format/print-width mirrors that:
|
|
78
|
+
// without it a deliberately multi-line object that happens to fit
|
|
79
|
+
// would be silently collapsed onto one line, which Prettier never
|
|
80
|
+
// does.
|
|
81
|
+
func objectHasNewlineAfterBrace(src string, node *shimast.Node, firstProp *shimast.Node) bool {
|
|
82
|
+
if node == nil || firstProp == nil {
|
|
83
|
+
return false
|
|
84
|
+
}
|
|
85
|
+
brace := shimscanner.SkipTrivia(src, node.Pos())
|
|
86
|
+
propStart := shimscanner.SkipTrivia(src, firstProp.Pos())
|
|
87
|
+
if brace < 0 || propStart <= brace || propStart > len(src) {
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
for i := brace; i < propStart; i++ {
|
|
91
|
+
if src[i] == '\n' {
|
|
92
|
+
return true
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return false
|
|
51
96
|
}
|
package/linthost/rules_arrays.go
CHANGED
|
@@ -25,8 +25,11 @@ func (noSparseArrays) Check(ctx *Context, node *shimast.Node) {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
// no-array-constructor: forbid `new Array(0)` / `Array(1, 2, 3)` (use
|
|
28
|
-
// array literals). The
|
|
29
|
-
//
|
|
28
|
+
// array literals). The single-argument form is intentionally excluded from
|
|
29
|
+
// the ban: `Array(n)` is commonly used to pre-allocate a sparse array by
|
|
30
|
+
// length, and banning it would generate noise on existing idiomatic code.
|
|
31
|
+
// The typed form `new Array<string>()` is also excluded — a type argument
|
|
32
|
+
// signals intentional use and the caller takes responsibility.
|
|
30
33
|
// https://eslint.org/docs/latest/rules/no-array-constructor
|
|
31
34
|
type noArrayConstructor struct{}
|
|
32
35
|
|
|
@@ -12,8 +12,9 @@ func (noDebugger) Check(ctx *Context, node *shimast.Node) {
|
|
|
12
12
|
ctx.Report(node, "Unexpected `debugger` statement.")
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
// no-with: forbid `with` statements
|
|
16
|
-
// but
|
|
15
|
+
// no-with: forbid `with` statements. `with` is disallowed in strict mode
|
|
16
|
+
// at the parser level, but TypeScript source files may not use strict mode
|
|
17
|
+
// explicitly; the lint rule catches it uniformly regardless of mode.
|
|
17
18
|
// https://eslint.org/docs/latest/rules/no-with
|
|
18
19
|
type noWith struct{}
|
|
19
20
|
|
package/linthost/rules_dupes.go
CHANGED
|
@@ -49,18 +49,17 @@ func (noDupeKeys) Check(ctx *Context, node *shimast.Node) {
|
|
|
49
49
|
if obj == nil || obj.Properties == nil {
|
|
50
50
|
return
|
|
51
51
|
}
|
|
52
|
-
seen := make(map[string]
|
|
52
|
+
seen := make(map[string]bool, len(obj.Properties.Nodes))
|
|
53
53
|
for _, prop := range obj.Properties.Nodes {
|
|
54
54
|
key := propertyKey(ctx.File, prop)
|
|
55
55
|
if key == "" {
|
|
56
56
|
continue
|
|
57
57
|
}
|
|
58
|
-
if
|
|
58
|
+
if seen[key] {
|
|
59
59
|
ctx.Report(prop, "Duplicate key '"+key+"'.")
|
|
60
|
-
_ = first
|
|
61
60
|
continue
|
|
62
61
|
}
|
|
63
|
-
seen[key] =
|
|
62
|
+
seen[key] = true
|
|
64
63
|
}
|
|
65
64
|
}
|
|
66
65
|
|
|
@@ -153,6 +152,10 @@ func propertyKey(file *shimast.SourceFile, prop *shimast.Node) string {
|
|
|
153
152
|
return ""
|
|
154
153
|
}
|
|
155
154
|
|
|
155
|
+
// staticPropertyKey extracts a comparable string key from a property name node.
|
|
156
|
+
// Identifiers, string/numeric literals, and computed names with a literal
|
|
157
|
+
// payload all produce a stable string. Other computed names (dynamic
|
|
158
|
+
// expressions) return "" so the caller skips them without false positives.
|
|
156
159
|
func staticPropertyKey(file *shimast.SourceFile, name *shimast.Node) string {
|
|
157
160
|
if name == nil {
|
|
158
161
|
return ""
|
package/linthost/rules_empty.go
CHANGED
|
@@ -83,8 +83,9 @@ func (noEmptyPattern) Check(ctx *Context, node *shimast.Node) {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
// isFunctionLikeKind reports whether
|
|
87
|
-
//
|
|
86
|
+
// isFunctionLikeKind reports whether n represents a function-like AST node
|
|
87
|
+
// (declaration, expression, arrow, method, accessor, or constructor). Used
|
|
88
|
+
// to detect scope boundaries by rules_empty, rules_finally, and others.
|
|
88
89
|
func isFunctionLikeKind(n *shimast.Node) bool {
|
|
89
90
|
if n == nil {
|
|
90
91
|
return false
|
package/linthost/rules_escape.go
CHANGED
|
@@ -52,12 +52,12 @@ func (noUselessEscape) Check(ctx *Context, node *shimast.Node) {
|
|
|
52
52
|
// single-char escape whitelist matches ESLint per-context.
|
|
53
53
|
switch node.Kind {
|
|
54
54
|
case shimast.KindStringLiteral:
|
|
55
|
-
reportStringEscapes(ctx, raw, pos, stringValidEscapes)
|
|
55
|
+
reportStringEscapes(ctx, raw, pos, stringValidEscapes, false)
|
|
56
56
|
case shimast.KindNoSubstitutionTemplateLiteral,
|
|
57
57
|
shimast.KindTemplateHead,
|
|
58
58
|
shimast.KindTemplateMiddle,
|
|
59
59
|
shimast.KindTemplateTail:
|
|
60
|
-
reportStringEscapes(ctx, raw, pos, templateValidEscapes)
|
|
60
|
+
reportStringEscapes(ctx, raw, pos, templateValidEscapes, true)
|
|
61
61
|
case shimast.KindRegularExpressionLiteral:
|
|
62
62
|
reportRegexEscapes(ctx, raw, pos)
|
|
63
63
|
}
|
|
@@ -73,7 +73,22 @@ const templateValidEscapes = "`'\"\\bfnrtv0xuU$\n\r"
|
|
|
73
73
|
// — it never deletes a backslash whose meaning could be context-sensitive).
|
|
74
74
|
const regexValidEscapes = "^$\\.*+?()[]{}|/-\n\r"
|
|
75
75
|
|
|
76
|
-
|
|
76
|
+
// reportStringEscapes walks the raw source bytes of a string or template
|
|
77
|
+
// literal and reports each backslash whose following character is not in
|
|
78
|
+
// `whitelist`. `base` is the source offset of `raw[0]` so reported ranges
|
|
79
|
+
// translate to absolute file positions. The function issues an autofix
|
|
80
|
+
// (delete the backslash) for ASCII escapes; multi-byte sequences are
|
|
81
|
+
// reported without a fix to avoid corrupting UTF-8.
|
|
82
|
+
//
|
|
83
|
+
// `isTemplate` is true for `NoSubstitutionTemplateLiteral` and
|
|
84
|
+
// `TemplateHead`/`Middle`/`Tail` payloads. Inside a template, `\${` escapes
|
|
85
|
+
// the interpolation opener: stripping the backslash from `\${expr}` would
|
|
86
|
+
// either turn the literal text into an interpolation (corrupting the
|
|
87
|
+
// program) or — when the surrounding template already contains a real
|
|
88
|
+
// `${expr}` — produce TS syntax that no longer parses. The explicit guard
|
|
89
|
+
// here pins that exception so future tightening of `templateValidEscapes`
|
|
90
|
+
// cannot regress the corruption.
|
|
91
|
+
func reportStringEscapes(ctx *Context, raw string, base int, whitelist string, isTemplate bool) {
|
|
77
92
|
if len(raw) < 2 {
|
|
78
93
|
return
|
|
79
94
|
}
|
|
@@ -95,6 +110,14 @@ func reportStringEscapes(ctx *Context, raw string, base int, whitelist string) {
|
|
|
95
110
|
return
|
|
96
111
|
}
|
|
97
112
|
next := raw[i+1]
|
|
113
|
+
// Template-literal exception: `\${` escapes the interpolation
|
|
114
|
+
// opener. Without the backslash the next two bytes would either
|
|
115
|
+
// start an interpolation or trigger a parse error, so the escape is
|
|
116
|
+
// load-bearing even though `\$` looks redundant in isolation.
|
|
117
|
+
if isTemplate && next == '$' && i+2 < len(raw) && raw[i+2] == '{' {
|
|
118
|
+
i++ // consume the `$` so the `{` is not re-examined as a fresh char.
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
98
121
|
if isUselessStringEscape(next, whitelist) {
|
|
99
122
|
// Only emit a fix when both surrounding bytes are plain ASCII so
|
|
100
123
|
// deleting one byte cannot corrupt a multi-byte sequence.
|
|
@@ -117,6 +140,9 @@ func reportStringEscapes(ctx *Context, raw string, base int, whitelist string) {
|
|
|
117
140
|
}
|
|
118
141
|
}
|
|
119
142
|
|
|
143
|
+
// reportRegexEscapes walks the pattern body of a regex literal and reports
|
|
144
|
+
// backslashes that escape non-special characters. `base` is the source offset
|
|
145
|
+
// of `raw[0]`. Character-class context (`[…]`) widens the legal set slightly.
|
|
120
146
|
func reportRegexEscapes(ctx *Context, raw string, base int) {
|
|
121
147
|
if len(raw) < 3 || raw[0] != '/' {
|
|
122
148
|
return
|
|
@@ -166,6 +192,9 @@ func reportRegexEscapes(ctx *Context, raw string, base int) {
|
|
|
166
192
|
}
|
|
167
193
|
}
|
|
168
194
|
|
|
195
|
+
// isUselessStringEscape reports whether a backslash before `ch` is redundant
|
|
196
|
+
// inside a string or template literal. The `whitelist` contains the characters
|
|
197
|
+
// that are valid escape targets for the specific literal kind (string vs template).
|
|
169
198
|
func isUselessStringEscape(ch byte, whitelist string) bool {
|
|
170
199
|
// Whitespace + control chars are escape sequences too.
|
|
171
200
|
if ch < 0x20 {
|
|
@@ -181,6 +210,9 @@ func isUselessStringEscape(ch byte, whitelist string) bool {
|
|
|
181
210
|
return true
|
|
182
211
|
}
|
|
183
212
|
|
|
213
|
+
// isUselessRegexEscape reports whether a backslash before `ch` is redundant
|
|
214
|
+
// in a regex pattern. `inClass` is true when the escape occurs inside a `[…]`
|
|
215
|
+
// character class, which widens the set of meaningful escapes.
|
|
184
216
|
func isUselessRegexEscape(ch byte, inClass bool) bool {
|
|
185
217
|
if ch < 0x20 {
|
|
186
218
|
return false
|
package/linthost/rules_eval.go
CHANGED
|
@@ -36,6 +36,9 @@ func (noScriptURL) Check(ctx *Context, node *shimast.Node) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// isJavaScriptURL reports whether text starts with the "javascript:" scheme
|
|
40
|
+
// (case-insensitively, ASCII only). The manual loop avoids importing strings
|
|
41
|
+
// just for strings.ToLower and keeps the hot path allocation-free.
|
|
39
42
|
func isJavaScriptURL(text string) bool {
|
|
40
43
|
const prefix = "javascript:"
|
|
41
44
|
if len(text) < len(prefix) {
|
|
@@ -25,6 +25,15 @@ func (noUnsafeFinally) Check(ctx *Context, node *shimast.Node) {
|
|
|
25
25
|
ctx.Report(node, "Unsafe usage of "+keyword+".")
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// walkToFinally walks the parent chain from node upward looking for a
|
|
29
|
+
// `finally` block. It returns the Block node that IS the finally clause when
|
|
30
|
+
// found, or nil when the search exits through a function boundary (making any
|
|
31
|
+
// control-flow transfer target something outside the finally block) or when no
|
|
32
|
+
// finally block is found at all.
|
|
33
|
+
//
|
|
34
|
+
// A `break` or `continue` that targets an inner loop or switch INSIDE the
|
|
35
|
+
// finally block is safe — it does not escape the finally — so the walk stops
|
|
36
|
+
// early and returns nil in that case.
|
|
28
37
|
func walkToFinally(node *shimast.Node) *shimast.Node {
|
|
29
38
|
cur := node.Parent
|
|
30
39
|
for cur != nil {
|
|
@@ -58,6 +67,8 @@ func walkToFinally(node *shimast.Node) *shimast.Node {
|
|
|
58
67
|
return nil
|
|
59
68
|
}
|
|
60
69
|
|
|
70
|
+
// keywordOfControl returns the control-flow keyword string for the given
|
|
71
|
+
// statement node, used to build the diagnostic message text.
|
|
61
72
|
func keywordOfControl(node *shimast.Node) string {
|
|
62
73
|
switch node.Kind {
|
|
63
74
|
case shimast.KindReturnStatement:
|
|
@@ -136,6 +136,10 @@ func findJSDocBlocks(src string) []jsdocBlock {
|
|
|
136
136
|
return out
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
// rewriteJSDocTags scans one JSDoc block and emits a fix for each tag that has
|
|
140
|
+
// a canonical synonym. Tags preceded by a byte other than `*`, whitespace, or a
|
|
141
|
+
// newline are treated as inline `@foo` references (not top-level tags) and are
|
|
142
|
+
// left alone. Tags inside `@example` bodies are also skipped.
|
|
139
143
|
func rewriteJSDocTags(ctx *Context, src string, block jsdocBlock, synonyms map[string]string) {
|
|
140
144
|
for i := block.bodyStart; i < block.bodyEnd; i++ {
|
|
141
145
|
if src[i] != '@' {
|
|
@@ -205,6 +209,9 @@ func endOfJSDocExampleBody(src string, block jsdocBlock, start int) int {
|
|
|
205
209
|
return block.bodyEnd
|
|
206
210
|
}
|
|
207
211
|
|
|
212
|
+
// isJSDocTagByte reports whether `b` is an ASCII letter that may appear in a
|
|
213
|
+
// JSDoc tag name. Tags are purely alphabetic: digits, hyphens, and underscores
|
|
214
|
+
// terminate a tag name.
|
|
208
215
|
func isJSDocTagByte(b byte) bool {
|
|
209
216
|
return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
|
|
210
217
|
}
|