@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,17 +20,23 @@ import (
|
|
|
20
20
|
// Type arguments (`foo<A, B>(x)`) are preserved verbatim. Trailing
|
|
21
21
|
// commas on type arguments are intentionally avoided — Prettier omits
|
|
22
22
|
// them too (see prettier#10353).
|
|
23
|
-
|
|
23
|
+
//
|
|
24
|
+
// The second return value is the `covered` flag: see PrintNode. The
|
|
25
|
+
// callee, optional `?.` token and type arguments are verbatim, so a
|
|
26
|
+
// multi-line callee taints coverage just as a multi-line argument does.
|
|
27
|
+
func printCallExpression(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
24
28
|
if node == nil {
|
|
25
|
-
return Doc{}
|
|
29
|
+
return Doc{}, true
|
|
26
30
|
}
|
|
27
31
|
call := node.AsCallExpression()
|
|
28
32
|
if call == nil {
|
|
29
|
-
return verbatim(ctx, node)
|
|
33
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
30
34
|
}
|
|
31
35
|
parts := []Doc{}
|
|
36
|
+
covered := true
|
|
32
37
|
if call.Expression != nil {
|
|
33
38
|
parts = append(parts, verbatim(ctx, call.Expression))
|
|
39
|
+
covered = covered && !nodeSpansMultipleLines(ctx, call.Expression)
|
|
34
40
|
}
|
|
35
41
|
// Question-dot for optional call: `foo?.(x)`. The token byte range
|
|
36
42
|
// lives between Expression.End() and the open paren; copy
|
|
@@ -43,38 +49,45 @@ func printCallExpression(ctx *PrintContext, node *shimast.Node) Doc {
|
|
|
43
49
|
parts = append(parts, verbatimRange(ctx.Source, callTypeArgsStart(ctx, call), callTypeArgsEnd(ctx, call)))
|
|
44
50
|
}
|
|
45
51
|
if hasNilEntry(call.Arguments) {
|
|
46
|
-
return verbatim(ctx, node)
|
|
52
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
47
53
|
}
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
argDoc, argCovered := printArgList(ctx, call.Arguments)
|
|
55
|
+
parts = append(parts, argDoc)
|
|
56
|
+
return Concat(parts...), covered && argCovered
|
|
50
57
|
}
|
|
51
58
|
|
|
52
59
|
// printNewExpression renders a NewExpression. It mirrors the call
|
|
53
60
|
// expression printer; the only difference is the leading `new ` keyword
|
|
54
61
|
// and the optional argument list (NewExpression may omit args entirely,
|
|
55
62
|
// e.g. `new Foo`).
|
|
56
|
-
|
|
63
|
+
//
|
|
64
|
+
// The second return value is the `covered` flag: see PrintNode.
|
|
65
|
+
func printNewExpression(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
57
66
|
if node == nil {
|
|
58
|
-
return Doc{}
|
|
67
|
+
return Doc{}, true
|
|
59
68
|
}
|
|
60
69
|
ne := node.AsNewExpression()
|
|
61
70
|
if ne == nil {
|
|
62
|
-
return verbatim(ctx, node)
|
|
71
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
63
72
|
}
|
|
64
73
|
parts := []Doc{Text("new ")}
|
|
74
|
+
covered := true
|
|
65
75
|
if ne.Expression != nil {
|
|
66
76
|
parts = append(parts, verbatim(ctx, ne.Expression))
|
|
77
|
+
covered = covered && !nodeSpansMultipleLines(ctx, ne.Expression)
|
|
67
78
|
}
|
|
68
79
|
if ne.TypeArguments != nil {
|
|
69
80
|
parts = append(parts, verbatimRange(ctx.Source, newTypeArgsStart(ctx, ne), newTypeArgsEnd(ctx, ne)))
|
|
70
81
|
}
|
|
71
82
|
if ne.Arguments != nil {
|
|
72
83
|
if hasNilEntry(ne.Arguments) {
|
|
73
|
-
return verbatim(ctx, node)
|
|
84
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
74
85
|
}
|
|
75
|
-
|
|
86
|
+
argDoc, argCovered := printArgList(ctx, ne.Arguments)
|
|
87
|
+
parts = append(parts, argDoc)
|
|
88
|
+
covered = covered && argCovered
|
|
76
89
|
}
|
|
77
|
-
return Concat(parts...)
|
|
90
|
+
return Concat(parts...), covered
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
// hasNilEntry reports whether any entry of `list` is a nil pointer.
|
|
@@ -94,29 +107,128 @@ func hasNilEntry(list *shimast.NodeList) bool {
|
|
|
94
107
|
}
|
|
95
108
|
|
|
96
109
|
// printArgList renders an argument node list. The shared printList
|
|
97
|
-
// handles the open-comma-close shape; this helper
|
|
98
|
-
// per-argument docs.
|
|
99
|
-
|
|
110
|
+
// handles the open-comma-close shape; this helper gathers the
|
|
111
|
+
// per-argument docs and threads each argument's `covered` flag up.
|
|
112
|
+
//
|
|
113
|
+
// When the final argument is a block-bodied callback or object literal,
|
|
114
|
+
// the list renders in the "last-argument hugging" shape (see
|
|
115
|
+
// printListHuggingLast): the callback's own body carries the multi-line
|
|
116
|
+
// layout, so the parens stay attached and the preceding arguments are
|
|
117
|
+
// not exploded onto their own lines. This is the Prettier behavior for
|
|
118
|
+
// `foo(x, () => { … })`.
|
|
119
|
+
func printArgList(ctx *PrintContext, list *shimast.NodeList) (Doc, bool) {
|
|
100
120
|
if list == nil {
|
|
101
|
-
return Text("()")
|
|
121
|
+
return Text("()"), true
|
|
102
122
|
}
|
|
103
123
|
items := make([]Doc, 0, len(list.Nodes))
|
|
124
|
+
covered := true
|
|
104
125
|
for _, arg := range list.Nodes {
|
|
105
|
-
doc,
|
|
126
|
+
doc, childCovered := PrintNode(ctx, arg)
|
|
127
|
+
covered = covered && childCovered
|
|
106
128
|
items = append(items, doc)
|
|
107
129
|
}
|
|
108
|
-
|
|
130
|
+
// AddComma honors `format.trailingComma`: call / new argument lists
|
|
131
|
+
// accepted trailing commas only in ES2017+, so Prettier's "es5" and
|
|
132
|
+
// "none" modes skip them here. Hardcoding `true` would oscillate
|
|
133
|
+
// against Prettier on every cascade pass on any project configured
|
|
134
|
+
// with "es5" (rxjs hit this on ajax.ts and several operators / testing
|
|
135
|
+
// helpers — the rule said "no comma needed" while the printer added
|
|
136
|
+
// one back on its own reflow).
|
|
137
|
+
shape := listShape{
|
|
109
138
|
OpenTok: "(",
|
|
110
139
|
CloseTok: ")",
|
|
111
140
|
Items: items,
|
|
112
141
|
Space: false,
|
|
113
|
-
AddComma:
|
|
114
|
-
|
|
142
|
+
AddComma: ctx.allowsCallArgumentTrailingComma(),
|
|
143
|
+
HugLast: shouldHugLastArgument(list.Nodes),
|
|
144
|
+
}
|
|
145
|
+
return printList(ctx, shape), covered
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// shouldHugLastArgument reports whether the final entry of `args` is a
|
|
149
|
+
// shape Prettier keeps hugging the closing paren: an object or array
|
|
150
|
+
// literal, a function expression, or an arrow function whose body is a
|
|
151
|
+
// block, an object literal, or an array literal. Hugging only applies
|
|
152
|
+
// when that argument is genuinely the last one; a callback in the
|
|
153
|
+
// middle of the list does not trigger the shape.
|
|
154
|
+
//
|
|
155
|
+
// An arrow with any other expression body (`(x) => x.id`) is
|
|
156
|
+
// deliberately excluded. Such a body carries no internal break point,
|
|
157
|
+
// so the hugging shape — a flat `Concat` with no Group — would pin the
|
|
158
|
+
// whole call to one line even when that line overflows printWidth.
|
|
159
|
+
// Routing it through the normal list shape instead lets the argument
|
|
160
|
+
// list explode onto its own line when the call does not fit, which is
|
|
161
|
+
// what Prettier does.
|
|
162
|
+
func shouldHugLastArgument(args []*shimast.Node) bool {
|
|
163
|
+
if len(args) == 0 {
|
|
164
|
+
return false
|
|
165
|
+
}
|
|
166
|
+
last := args[len(args)-1]
|
|
167
|
+
if last == nil {
|
|
168
|
+
return false
|
|
169
|
+
}
|
|
170
|
+
switch last.Kind {
|
|
171
|
+
case shimast.KindFunctionExpression,
|
|
172
|
+
shimast.KindObjectLiteralExpression,
|
|
173
|
+
shimast.KindArrayLiteralExpression:
|
|
174
|
+
return true
|
|
175
|
+
case shimast.KindArrowFunction:
|
|
176
|
+
arrow := last.AsArrowFunction()
|
|
177
|
+
if arrow == nil || arrow.Body == nil {
|
|
178
|
+
return false
|
|
179
|
+
}
|
|
180
|
+
body := arrow.Body
|
|
181
|
+
// `(x) => ({ … })` parenthesizes its object body; hug on the inner
|
|
182
|
+
// expression, mirroring Prettier's couldExpandArg.
|
|
183
|
+
if body.Kind == shimast.KindParenthesizedExpression {
|
|
184
|
+
if p := body.AsParenthesizedExpression(); p != nil && p.Expression != nil {
|
|
185
|
+
body = p.Expression
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
switch body.Kind {
|
|
189
|
+
case shimast.KindBlock,
|
|
190
|
+
shimast.KindObjectLiteralExpression,
|
|
191
|
+
shimast.KindArrayLiteralExpression:
|
|
192
|
+
return true
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return false
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// forceBreakFirstGroup returns `doc` with the first Group found in a
|
|
199
|
+
// left-to-right walk of its subtree forced broken, and reports whether
|
|
200
|
+
// one was found. printListHuggingLast uses it to commit a hugged
|
|
201
|
+
// argument — an object or array literal, possibly nested inside an
|
|
202
|
+
// arrow body (`(x) => ({ … })`) — to its multi-line shape. The caller
|
|
203
|
+
// guards the walk with flatten: it is only run on an item that has no
|
|
204
|
+
// hard line breaks of its own, so the first Group reached is the
|
|
205
|
+
// hugged literal itself, never an unrelated Group inside a block body.
|
|
206
|
+
func forceBreakFirstGroup(doc Doc) (Doc, bool) {
|
|
207
|
+
switch doc.Kind {
|
|
208
|
+
case docGroup:
|
|
209
|
+
doc.Break = true
|
|
210
|
+
return doc, true
|
|
211
|
+
case docConcat, docIndent, docAlign:
|
|
212
|
+
children := make([]Doc, len(doc.Children))
|
|
213
|
+
copy(children, doc.Children)
|
|
214
|
+
for i, child := range children {
|
|
215
|
+
broken, done := forceBreakFirstGroup(child)
|
|
216
|
+
if done {
|
|
217
|
+
children[i] = broken
|
|
218
|
+
doc.Children = children
|
|
219
|
+
return doc, true
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return doc, false
|
|
115
224
|
}
|
|
116
225
|
|
|
117
226
|
// Type-argument byte-range helpers. The shim's NodeList.End() points
|
|
118
227
|
// past the last argument; the surrounding `<` and `>` are not part of
|
|
119
228
|
// the list's range, so we have to scan around it.
|
|
229
|
+
|
|
230
|
+
// callTypeArgsStart returns the byte offset of the `<` that opens the
|
|
231
|
+
// type-argument list of a CallExpression. Returns -1 when absent.
|
|
120
232
|
func callTypeArgsStart(ctx *PrintContext, call *shimast.CallExpression) int {
|
|
121
233
|
if call.TypeArguments == nil || len(call.TypeArguments.Nodes) == 0 {
|
|
122
234
|
return -1
|
|
@@ -136,6 +248,8 @@ func callTypeArgsStart(ctx *PrintContext, call *shimast.CallExpression) int {
|
|
|
136
248
|
return -1
|
|
137
249
|
}
|
|
138
250
|
|
|
251
|
+
// callTypeArgsEnd returns the byte offset one past the closing `>` of a
|
|
252
|
+
// CallExpression's type-argument list. Returns -1 when absent.
|
|
139
253
|
func callTypeArgsEnd(ctx *PrintContext, call *shimast.CallExpression) int {
|
|
140
254
|
if call.TypeArguments == nil {
|
|
141
255
|
return -1
|
|
@@ -149,6 +263,8 @@ func callTypeArgsEnd(ctx *PrintContext, call *shimast.CallExpression) int {
|
|
|
149
263
|
return end
|
|
150
264
|
}
|
|
151
265
|
|
|
266
|
+
// newTypeArgsStart returns the byte offset of the `<` that opens the
|
|
267
|
+
// type-argument list of a NewExpression. Returns -1 when absent.
|
|
152
268
|
func newTypeArgsStart(ctx *PrintContext, ne *shimast.NewExpression) int {
|
|
153
269
|
if ne.TypeArguments == nil || len(ne.TypeArguments.Nodes) == 0 {
|
|
154
270
|
return -1
|
|
@@ -166,6 +282,8 @@ func newTypeArgsStart(ctx *PrintContext, ne *shimast.NewExpression) int {
|
|
|
166
282
|
return -1
|
|
167
283
|
}
|
|
168
284
|
|
|
285
|
+
// newTypeArgsEnd returns the byte offset one past the closing `>` of a
|
|
286
|
+
// NewExpression's type-argument list. Returns -1 when absent.
|
|
169
287
|
func newTypeArgsEnd(ctx *PrintContext, ne *shimast.NewExpression) int {
|
|
170
288
|
if ne.TypeArguments == nil {
|
|
171
289
|
return -1
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
package linthost
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"strings"
|
|
5
|
+
|
|
6
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
7
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
// Function-shaped and statement-body node printers: arrow functions,
|
|
11
|
+
// function expressions, parenthesized expressions, block statements,
|
|
12
|
+
// and the two leaf statements a callback body is almost always made of
|
|
13
|
+
// — expression statements and return statements.
|
|
14
|
+
//
|
|
15
|
+
// These printers exist for one job: make callback-bearing code
|
|
16
|
+
// (`new Singleton(() => { … })`, `foo(x, function () { … })`) reflow
|
|
17
|
+
// with *consistent* indentation. The headline bug they fix is the
|
|
18
|
+
// verbatim-column hazard — an un-handled multi-line node keeps the
|
|
19
|
+
// source columns its lines were written at, so when the enclosing call
|
|
20
|
+
// re-indents, the callback header and its body drift apart.
|
|
21
|
+
//
|
|
22
|
+
// Coverage discipline. The signature (parameters, `=>` / `function`
|
|
23
|
+
// keyword, return type) is emitted verbatim because it almost never
|
|
24
|
+
// contains a newline and is not a reflow target. The *body* is where
|
|
25
|
+
// the newlines live, so the body is dispatched through PrintNode and
|
|
26
|
+
// re-indented by the Doc engine. Each printer ANDs the body's `covered`
|
|
27
|
+
// flag into its own result: a body the dispatcher cannot fully control
|
|
28
|
+
// taints the whole subtree and the format/print-width rule abstains.
|
|
29
|
+
//
|
|
30
|
+
// The expression- and return-statement printers carry the same
|
|
31
|
+
// discipline one level deeper: a callback body whose statements are
|
|
32
|
+
// themselves calls (`outer(() => { inner(() => { … }); })`) only
|
|
33
|
+
// reflows when those statements dispatch to a real printer. Without
|
|
34
|
+
// them every multi-line statement would be verbatim and a nested
|
|
35
|
+
// callback would always abstain.
|
|
36
|
+
|
|
37
|
+
// printArrowFunction renders an arrow function. The portion before the
|
|
38
|
+
// body — parameters, optional return type, `=>` token — is emitted
|
|
39
|
+
// verbatim; only the body participates in reflow.
|
|
40
|
+
//
|
|
41
|
+
// verbatim prefix reflowed body
|
|
42
|
+
// ┌──────────────┐ ┌──────────┐
|
|
43
|
+
// (a, b): number => { return a; }
|
|
44
|
+
//
|
|
45
|
+
// A concise (expression) body is dispatched directly. A block body
|
|
46
|
+
// flows through printBlock, which re-indents its statements relative
|
|
47
|
+
// to the printer's current indent.
|
|
48
|
+
//
|
|
49
|
+
// The second return value is the `covered` flag: see PrintNode. The
|
|
50
|
+
// verbatim prefix is single-line in every realistic arrow, but a
|
|
51
|
+
// pathological multi-line parameter list would still taint coverage.
|
|
52
|
+
func printArrowFunction(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
53
|
+
if node == nil {
|
|
54
|
+
return Doc{}, true
|
|
55
|
+
}
|
|
56
|
+
arrow := node.AsArrowFunction()
|
|
57
|
+
if arrow == nil || arrow.Body == nil {
|
|
58
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
59
|
+
}
|
|
60
|
+
return printFunctionLike(ctx, node, arrow.Body)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// printFunctionExpression renders a `function` expression. Like the
|
|
64
|
+
// arrow printer, the signature is verbatim and only the body reflows.
|
|
65
|
+
// Function expressions always carry a block body, so the body always
|
|
66
|
+
// flows through printBlock.
|
|
67
|
+
//
|
|
68
|
+
// The second return value is the `covered` flag: see PrintNode.
|
|
69
|
+
func printFunctionExpression(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
70
|
+
if node == nil {
|
|
71
|
+
return Doc{}, true
|
|
72
|
+
}
|
|
73
|
+
fn := node.AsFunctionExpression()
|
|
74
|
+
if fn == nil || fn.Body == nil {
|
|
75
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
76
|
+
}
|
|
77
|
+
return printFunctionLike(ctx, node, fn.Body)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// printFunctionLike is the shared body of the arrow / function-expression
|
|
81
|
+
// printers. It slices the verbatim signature from the node's first byte
|
|
82
|
+
// up to the body's first byte, dispatches the body, and concatenates
|
|
83
|
+
// the two.
|
|
84
|
+
//
|
|
85
|
+
// The signature slice intentionally includes the whitespace between the
|
|
86
|
+
// signature and the body (`=> ` keeps its trailing space, `) ` before a
|
|
87
|
+
// `function` body keeps its space) so the flat form reads naturally and
|
|
88
|
+
// the body's open brace lands where the user put it.
|
|
89
|
+
func printFunctionLike(ctx *PrintContext, node, body *shimast.Node) (Doc, bool) {
|
|
90
|
+
nodeStart := shimscanner.SkipTrivia(ctx.Source, node.Pos())
|
|
91
|
+
bodyStart := shimscanner.SkipTrivia(ctx.Source, body.Pos())
|
|
92
|
+
if nodeStart < 0 || bodyStart < nodeStart || bodyStart > len(ctx.Source) {
|
|
93
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
94
|
+
}
|
|
95
|
+
prefix := verbatimRange(ctx.Source, nodeStart, bodyStart)
|
|
96
|
+
// A signature that itself spans multiple lines is a verbatim slice
|
|
97
|
+
// with frozen interior columns — taint coverage so the rule abstains.
|
|
98
|
+
prefixCovered := !strings.Contains(ctx.Source[nodeStart:bodyStart], "\n")
|
|
99
|
+
bodyDoc, bodyCovered := PrintNode(ctx, body)
|
|
100
|
+
return Concat(prefix, bodyDoc), prefixCovered && bodyCovered
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// printParenthesizedExpression renders `( expr )`. The parentheses are
|
|
104
|
+
// fixed punctuation; the inner expression is dispatched so a call or
|
|
105
|
+
// object literal wrapped in parens still reflows.
|
|
106
|
+
//
|
|
107
|
+
// The second return value is the `covered` flag: see PrintNode.
|
|
108
|
+
func printParenthesizedExpression(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
109
|
+
if node == nil {
|
|
110
|
+
return Doc{}, true
|
|
111
|
+
}
|
|
112
|
+
paren := node.AsParenthesizedExpression()
|
|
113
|
+
if paren == nil || paren.Expression == nil {
|
|
114
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
115
|
+
}
|
|
116
|
+
inner, covered := PrintNode(ctx, paren.Expression)
|
|
117
|
+
return Concat(Text("("), inner, Text(")")), covered
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// printBlock renders a `{ … }` block statement. A statement-free block
|
|
121
|
+
// collapses to `{}`. A non-empty block always renders multi-line —
|
|
122
|
+
// matching every JavaScript formatter — with each statement on its own
|
|
123
|
+
// line, indented one unit from the block's base indent and the closing
|
|
124
|
+
// brace back at the base indent.
|
|
125
|
+
//
|
|
126
|
+
// {
|
|
127
|
+
// stmt;
|
|
128
|
+
// stmt;
|
|
129
|
+
// }
|
|
130
|
+
//
|
|
131
|
+
// Each statement is dispatched through PrintNode, so a statement that
|
|
132
|
+
// is itself reflowable (a long call) gets reflowed and a plain
|
|
133
|
+
// statement falls back to verbatim. Because the engine re-applies the
|
|
134
|
+
// indent at every Hardline, even a verbatim statement lands at the
|
|
135
|
+
// correct column — the verbatim-column hazard only bites *multi-line*
|
|
136
|
+
// verbatim nodes, and those taint `covered` so the rule abstains.
|
|
137
|
+
//
|
|
138
|
+
// The second return value is the `covered` flag: see PrintNode. A block
|
|
139
|
+
// is uncovered when any statement is uncovered, or when the block
|
|
140
|
+
// carries a comment that lives outside every statement's byte range —
|
|
141
|
+
// the freshly minted Hardline separators have no carrier slot for such
|
|
142
|
+
// trivia, so reflowing would silently drop the comment. The
|
|
143
|
+
// comment check guards the statement-free path too: `{ /* note */ }`
|
|
144
|
+
// has no statements but is *not* an empty block — collapsing it to `{}`
|
|
145
|
+
// would delete the comment, so the printer emits it verbatim instead.
|
|
146
|
+
func printBlock(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
147
|
+
if node == nil {
|
|
148
|
+
return Doc{}, true
|
|
149
|
+
}
|
|
150
|
+
block := node.AsBlock()
|
|
151
|
+
if block == nil || block.Statements == nil {
|
|
152
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
153
|
+
}
|
|
154
|
+
hasComment := blockHasNonStatementComment(ctx, node, block.Statements.Nodes)
|
|
155
|
+
if len(block.Statements.Nodes) == 0 {
|
|
156
|
+
if hasComment {
|
|
157
|
+
// `{ /* … */ }` — not collapsible without dropping the comment.
|
|
158
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
159
|
+
}
|
|
160
|
+
return Text("{}"), true
|
|
161
|
+
}
|
|
162
|
+
stmts := block.Statements.Nodes
|
|
163
|
+
items := make([]Doc, 0, len(stmts))
|
|
164
|
+
covered := !hasComment
|
|
165
|
+
for _, stmt := range stmts {
|
|
166
|
+
if stmt == nil {
|
|
167
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
168
|
+
}
|
|
169
|
+
doc, childCovered := PrintNode(ctx, stmt)
|
|
170
|
+
covered = covered && childCovered
|
|
171
|
+
items = append(items, doc)
|
|
172
|
+
}
|
|
173
|
+
// Join statements with a Hardline, preserving a single user-authored
|
|
174
|
+
// blank line between consecutive statements. The block printer mints
|
|
175
|
+
// fresh separators, so a bare `Join(Hardline, …)` would silently
|
|
176
|
+
// delete every blank line in the body on the first reflow. The blank
|
|
177
|
+
// line is a Literalline — a bare newline with no indent — so the
|
|
178
|
+
// empty line carries no trailing whitespace. Two or more blank lines
|
|
179
|
+
// collapse to one, matching Prettier.
|
|
180
|
+
bodyParts := make([]Doc, 0, len(items)*2)
|
|
181
|
+
for i, item := range items {
|
|
182
|
+
if i > 0 {
|
|
183
|
+
if blankLineBetweenStatements(ctx.Source, stmts[i-1].End(), stmts[i].Pos()) {
|
|
184
|
+
bodyParts = append(bodyParts, Literalline())
|
|
185
|
+
}
|
|
186
|
+
bodyParts = append(bodyParts, Hardline())
|
|
187
|
+
}
|
|
188
|
+
bodyParts = append(bodyParts, item)
|
|
189
|
+
}
|
|
190
|
+
doc := Concat(
|
|
191
|
+
Text("{"),
|
|
192
|
+
Indent(ctx.indentUnit(), Hardline(), Concat(bodyParts...)),
|
|
193
|
+
Hardline(),
|
|
194
|
+
Text("}"),
|
|
195
|
+
)
|
|
196
|
+
return doc, covered
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// blankLineBetweenStatements reports whether the source gap between the
|
|
200
|
+
// end of one block statement and the start of the next contains a blank
|
|
201
|
+
// line — two or more newlines. printBlock uses it to keep a single
|
|
202
|
+
// user-authored blank line between statements. blockHasNonStatementComment
|
|
203
|
+
// has already guaranteed the gap holds no comment when the block is
|
|
204
|
+
// covered, so the gap is pure whitespace and counting newlines suffices.
|
|
205
|
+
func blankLineBetweenStatements(src string, prevEnd, nextPos int) bool {
|
|
206
|
+
nextStart := shimscanner.SkipTrivia(src, nextPos)
|
|
207
|
+
if prevEnd < 0 || nextStart > len(src) || nextStart <= prevEnd {
|
|
208
|
+
return false
|
|
209
|
+
}
|
|
210
|
+
newlines := 0
|
|
211
|
+
for i := prevEnd; i < nextStart; i++ {
|
|
212
|
+
if src[i] == '\n' {
|
|
213
|
+
newlines++
|
|
214
|
+
if newlines >= 2 {
|
|
215
|
+
return true
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return false
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// blockHasNonStatementComment reports whether the block's byte range
|
|
223
|
+
// holds a `//` or `/*` outside every statement's token range. The block
|
|
224
|
+
// printer joins statements with bare Hardlines that have no slot for
|
|
225
|
+
// inter-statement trivia, so a stray comment would be dropped by a
|
|
226
|
+
// reflow. Detecting it lets printBlock report the block uncovered and
|
|
227
|
+
// the format/print-width rule abstain.
|
|
228
|
+
//
|
|
229
|
+
// The scan mirrors rules_format_print_width.go::hasNonChildComments:
|
|
230
|
+
// comment-shaped bytes inside a complete statement token range (string
|
|
231
|
+
// literals, nested comments) are masked, so only genuine
|
|
232
|
+
// inter-statement comments surface.
|
|
233
|
+
func blockHasNonStatementComment(ctx *PrintContext, node *shimast.Node, stmts []*shimast.Node) bool {
|
|
234
|
+
start := shimscanner.SkipTrivia(ctx.Source, node.Pos())
|
|
235
|
+
end := node.End()
|
|
236
|
+
if start < 0 || end < start || end > len(ctx.Source) {
|
|
237
|
+
return false
|
|
238
|
+
}
|
|
239
|
+
type span struct{ pos, end int }
|
|
240
|
+
ranges := make([]span, 0, len(stmts))
|
|
241
|
+
for _, stmt := range stmts {
|
|
242
|
+
if stmt == nil {
|
|
243
|
+
continue
|
|
244
|
+
}
|
|
245
|
+
ranges = append(ranges, span{shimscanner.SkipTrivia(ctx.Source, stmt.Pos()), stmt.End()})
|
|
246
|
+
}
|
|
247
|
+
inStatement := func(i int) bool {
|
|
248
|
+
for _, r := range ranges {
|
|
249
|
+
if i >= r.pos && i < r.end {
|
|
250
|
+
return true
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return false
|
|
254
|
+
}
|
|
255
|
+
src := ctx.Source
|
|
256
|
+
for i := start; i < end-1 && i < len(src)-1; i++ {
|
|
257
|
+
if inStatement(i) {
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
if src[i] == '/' && (src[i+1] == '/' || src[i+1] == '*') {
|
|
261
|
+
return true
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return false
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// printExpressionStatement renders an `expr;` statement. The expression
|
|
268
|
+
// is dispatched so a callback-body statement that is itself a call or
|
|
269
|
+
// object literal reflows; the trailing `;` is preserved when the source
|
|
270
|
+
// carries one.
|
|
271
|
+
//
|
|
272
|
+
// The dispatcher reaches this printer through the block printer, so
|
|
273
|
+
// nested callbacks (`outer(() => { inner(() => { … }); })`) reflow at
|
|
274
|
+
// every depth instead of stalling on the verbatim fallback.
|
|
275
|
+
//
|
|
276
|
+
// The second return value is the `covered` flag: see PrintNode. The
|
|
277
|
+
// printer falls back to verbatim when the gap between the expression
|
|
278
|
+
// and the statement end holds anything other than the optional `;` and
|
|
279
|
+
// whitespace — a comment in that gap has no carrier slot and would be
|
|
280
|
+
// dropped.
|
|
281
|
+
func printExpressionStatement(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
282
|
+
if node == nil {
|
|
283
|
+
return Doc{}, true
|
|
284
|
+
}
|
|
285
|
+
stmt := node.AsExpressionStatement()
|
|
286
|
+
if stmt == nil || stmt.Expression == nil {
|
|
287
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
288
|
+
}
|
|
289
|
+
if !tailIsCleanTerminator(ctx.Source, stmt.Expression.End(), node.End()) {
|
|
290
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
291
|
+
}
|
|
292
|
+
exprDoc, covered := PrintNode(ctx, stmt.Expression)
|
|
293
|
+
parts := []Doc{exprDoc}
|
|
294
|
+
if sourceHasStatementTerminator(ctx.Source, node.End()) {
|
|
295
|
+
parts = append(parts, Text(";"))
|
|
296
|
+
}
|
|
297
|
+
return Concat(parts...), covered
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// printReturnStatement renders a `return expr;` statement. Like the
|
|
301
|
+
// expression-statement printer, the returned expression is dispatched
|
|
302
|
+
// so a returned callback or object literal reflows.
|
|
303
|
+
//
|
|
304
|
+
// A bare `return;` carries no expression and is emitted verbatim — it
|
|
305
|
+
// is a single token with nothing to reflow.
|
|
306
|
+
//
|
|
307
|
+
// The second return value is the `covered` flag: see PrintNode. The
|
|
308
|
+
// printer falls back to verbatim when the gap between the expression
|
|
309
|
+
// and the statement end holds anything but the optional `;` and
|
|
310
|
+
// whitespace.
|
|
311
|
+
func printReturnStatement(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
312
|
+
if node == nil {
|
|
313
|
+
return Doc{}, true
|
|
314
|
+
}
|
|
315
|
+
stmt := node.AsReturnStatement()
|
|
316
|
+
if stmt == nil || stmt.Expression == nil {
|
|
317
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
318
|
+
}
|
|
319
|
+
if !tailIsCleanTerminator(ctx.Source, stmt.Expression.End(), node.End()) {
|
|
320
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
321
|
+
}
|
|
322
|
+
exprDoc, covered := PrintNode(ctx, stmt.Expression)
|
|
323
|
+
parts := []Doc{Text("return "), exprDoc}
|
|
324
|
+
if sourceHasStatementTerminator(ctx.Source, node.End()) {
|
|
325
|
+
parts = append(parts, Text(";"))
|
|
326
|
+
}
|
|
327
|
+
return Concat(parts...), covered
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// tailIsCleanTerminator reports whether src[exprEnd:stmtEnd] holds only
|
|
331
|
+
// whitespace and at most one `;`. The expression- and return-statement
|
|
332
|
+
// printers consult it before re-minting the trailing `;`: a comment or
|
|
333
|
+
// any other token in that gap would be dropped by the reflow, so the
|
|
334
|
+
// printer must fall back to verbatim instead.
|
|
335
|
+
func tailIsCleanTerminator(src string, exprEnd, stmtEnd int) bool {
|
|
336
|
+
if exprEnd < 0 || stmtEnd < exprEnd || stmtEnd > len(src) {
|
|
337
|
+
return false
|
|
338
|
+
}
|
|
339
|
+
semis := 0
|
|
340
|
+
for i := exprEnd; i < stmtEnd; i++ {
|
|
341
|
+
switch src[i] {
|
|
342
|
+
case ' ', '\t', '\r', '\n':
|
|
343
|
+
case ';':
|
|
344
|
+
semis++
|
|
345
|
+
if semis > 1 {
|
|
346
|
+
return false
|
|
347
|
+
}
|
|
348
|
+
default:
|
|
349
|
+
return false
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return true
|
|
353
|
+
}
|