@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
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
package linthost
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"strings"
|
|
5
|
+
|
|
4
6
|
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
7
|
+
shimscanner "github.com/microsoft/typescript-go/shim/scanner"
|
|
5
8
|
)
|
|
6
9
|
|
|
7
10
|
// printArrayLiteral renders an ArrayLiteralExpression with width-aware
|
|
@@ -127,19 +130,18 @@ func arrayForcesBreak(node *shimast.Node) bool {
|
|
|
127
130
|
return arrayShouldForceBreak(arr.Elements.Nodes)
|
|
128
131
|
}
|
|
129
132
|
|
|
130
|
-
// fastPathForcesBreak reports whether `node`,
|
|
131
|
-
// within the subtree
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
func fastPathForcesBreak(node *shimast.Node) bool {
|
|
133
|
+
// fastPathForcesBreak reports whether `node`, or a force-breaking node nested
|
|
134
|
+
// within the subtree its structured printer reaches, must explode even though
|
|
135
|
+
// the outer source fits flat. It follows call/new arguments, array elements,
|
|
136
|
+
// function bodies, conditional parts, parenthesized expressions, and printable
|
|
137
|
+
// object-member bodies. The forced descendant can be a same-kind composite
|
|
138
|
+
// array, a function-composition call, or a non-empty block. Without the walk,
|
|
139
|
+
// the outer node's fast path would return before the descendant could deny it.
|
|
140
|
+
func fastPathForcesBreak(node *shimast.Node, src string) bool {
|
|
139
141
|
if node == nil {
|
|
140
142
|
return false
|
|
141
143
|
}
|
|
142
|
-
if callForcesFunctionBreak(node) || arrayForcesBreak(node) {
|
|
144
|
+
if callForcesFunctionBreak(node) || arrayForcesBreak(node) || blockForcesBreak(node, src) {
|
|
143
145
|
return true
|
|
144
146
|
}
|
|
145
147
|
var children []*shimast.Node
|
|
@@ -156,6 +158,16 @@ func fastPathForcesBreak(node *shimast.Node) bool {
|
|
|
156
158
|
if a := node.AsArrayLiteralExpression(); a != nil && a.Elements != nil {
|
|
157
159
|
children = a.Elements.Nodes
|
|
158
160
|
}
|
|
161
|
+
case shimast.KindArrowFunction, shimast.KindFunctionExpression:
|
|
162
|
+
children = append(children, functionLikeBody(node))
|
|
163
|
+
case shimast.KindConditionalExpression:
|
|
164
|
+
if conditional := node.AsConditionalExpression(); conditional != nil {
|
|
165
|
+
children = append(children, conditional.Condition, conditional.WhenTrue, conditional.WhenFalse)
|
|
166
|
+
}
|
|
167
|
+
case shimast.KindParenthesizedExpression:
|
|
168
|
+
if parenthesized := node.AsParenthesizedExpression(); parenthesized != nil {
|
|
169
|
+
children = append(children, parenthesized.Expression)
|
|
170
|
+
}
|
|
159
171
|
case shimast.KindObjectLiteralExpression:
|
|
160
172
|
// A force-breaking array/object nested in an object PROPERTY value
|
|
161
173
|
// (`{ m: [[1, 2], [3, 4]] }`) must also deny the fast path: the value
|
|
@@ -164,22 +176,83 @@ func fastPathForcesBreak(node *shimast.Node) bool {
|
|
|
164
176
|
// would stay flat where Prettier breaks them.
|
|
165
177
|
if o := node.AsObjectLiteralExpression(); o != nil && o.Properties != nil {
|
|
166
178
|
for _, p := range o.Properties.Nodes {
|
|
167
|
-
if p
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
179
|
+
if p == nil {
|
|
180
|
+
continue
|
|
181
|
+
}
|
|
182
|
+
switch p.Kind {
|
|
183
|
+
case shimast.KindPropertyAssignment,
|
|
184
|
+
shimast.KindMethodDeclaration,
|
|
185
|
+
shimast.KindGetAccessor,
|
|
186
|
+
shimast.KindSetAccessor:
|
|
187
|
+
children = append(children, functionLikeBody(p))
|
|
171
188
|
}
|
|
172
189
|
}
|
|
173
190
|
}
|
|
174
191
|
}
|
|
175
192
|
for _, ch := range children {
|
|
176
|
-
if fastPathForcesBreak(ch) {
|
|
193
|
+
if fastPathForcesBreak(ch, src) {
|
|
177
194
|
return true
|
|
178
195
|
}
|
|
179
196
|
}
|
|
180
197
|
return false
|
|
181
198
|
}
|
|
182
199
|
|
|
200
|
+
// blockForcesBreak reports whether `node` is a block Prettier expands even
|
|
201
|
+
// when it fits: one carrying a statement or a comment. Whitespace-only `{}` is
|
|
202
|
+
// the empty negative twin and remains flat.
|
|
203
|
+
func blockForcesBreak(node *shimast.Node, src string) bool {
|
|
204
|
+
if node == nil || node.Kind != shimast.KindBlock {
|
|
205
|
+
return false
|
|
206
|
+
}
|
|
207
|
+
block := node.AsBlock()
|
|
208
|
+
if block == nil || block.Statements == nil {
|
|
209
|
+
return false
|
|
210
|
+
}
|
|
211
|
+
if len(block.Statements.Nodes) > 0 {
|
|
212
|
+
return true
|
|
213
|
+
}
|
|
214
|
+
start := shimscanner.SkipTrivia(src, node.Pos())
|
|
215
|
+
end := node.End()
|
|
216
|
+
return start >= 0 && end > start+1 && end <= len(src) &&
|
|
217
|
+
src[start] == '{' && src[end-1] == '}' &&
|
|
218
|
+
strings.TrimSpace(src[start+1:end-1]) != ""
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// functionLikeBody returns the part of an arrow, function expression, or
|
|
222
|
+
// object member that an enclosing reflow prints structurally.
|
|
223
|
+
func functionLikeBody(node *shimast.Node) *shimast.Node {
|
|
224
|
+
if node == nil {
|
|
225
|
+
return nil
|
|
226
|
+
}
|
|
227
|
+
switch node.Kind {
|
|
228
|
+
case shimast.KindArrowFunction:
|
|
229
|
+
if arrow := node.AsArrowFunction(); arrow != nil {
|
|
230
|
+
return arrow.Body
|
|
231
|
+
}
|
|
232
|
+
case shimast.KindFunctionExpression:
|
|
233
|
+
if function := node.AsFunctionExpression(); function != nil {
|
|
234
|
+
return function.Body
|
|
235
|
+
}
|
|
236
|
+
case shimast.KindPropertyAssignment:
|
|
237
|
+
if property := node.AsPropertyAssignment(); property != nil {
|
|
238
|
+
return property.Initializer
|
|
239
|
+
}
|
|
240
|
+
case shimast.KindMethodDeclaration:
|
|
241
|
+
if method := node.AsMethodDeclaration(); method != nil {
|
|
242
|
+
return method.Body
|
|
243
|
+
}
|
|
244
|
+
case shimast.KindGetAccessor:
|
|
245
|
+
if accessor := node.AsGetAccessorDeclaration(); accessor != nil {
|
|
246
|
+
return accessor.Body
|
|
247
|
+
}
|
|
248
|
+
case shimast.KindSetAccessor:
|
|
249
|
+
if accessor := node.AsSetAccessorDeclaration(); accessor != nil {
|
|
250
|
+
return accessor.Body
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return nil
|
|
254
|
+
}
|
|
255
|
+
|
|
183
256
|
// isConciselyPrintedArray reports whether an array should use Prettier's
|
|
184
257
|
// concise "fill" layout: at least one element and every element a numeric
|
|
185
258
|
// literal (optionally a `+`/`-` signed numeric). Prettier packs such arrays
|
|
@@ -206,15 +206,17 @@ func printArgList(ctx *PrintContext, list *shimast.NodeList, addComma bool, deco
|
|
|
206
206
|
forceFnBreak = false
|
|
207
207
|
}
|
|
208
208
|
hugFirst := !hugLast && !forceFnBreak && shouldHugFirstArgument(ctx, list.Nodes)
|
|
209
|
+
hugLastBreakClose := hugLast && lastArgumentBreaksAfterArrow(list.Nodes)
|
|
209
210
|
shape := listShape{
|
|
210
|
-
OpenTok:
|
|
211
|
-
CloseTok:
|
|
212
|
-
Items:
|
|
213
|
-
Space:
|
|
214
|
-
AddComma:
|
|
215
|
-
HugLast:
|
|
216
|
-
|
|
217
|
-
|
|
211
|
+
OpenTok: "(",
|
|
212
|
+
CloseTok: ")",
|
|
213
|
+
Items: items,
|
|
214
|
+
Space: false,
|
|
215
|
+
AddComma: addComma,
|
|
216
|
+
HugLast: hugLast,
|
|
217
|
+
HugLastBreakClose: hugLastBreakClose,
|
|
218
|
+
HugLastForce: forceHugLast,
|
|
219
|
+
HugFirst: hugFirst,
|
|
218
220
|
// A non-empty array second argument reaches HugFirst only via the
|
|
219
221
|
// React-hook deps branch of shouldHugFirstArgument; let that deps array
|
|
220
222
|
// break one-per-line instead of being pinned flat on the close line.
|
|
@@ -228,6 +230,18 @@ func printArgList(ctx *PrintContext, list *shimast.NodeList, addComma bool, deco
|
|
|
228
230
|
return printList(ctx, shape), covered
|
|
229
231
|
}
|
|
230
232
|
|
|
233
|
+
// lastArgumentBreaksAfterArrow reports the trailing arrow shape whose hugged
|
|
234
|
+
// layout keeps the opening line through `=>` but leaves the call's closing
|
|
235
|
+
// parenthesis on its own line after the expression body.
|
|
236
|
+
func lastArgumentBreaksAfterArrow(args []*shimast.Node) bool {
|
|
237
|
+
if len(args) == 0 || args[len(args)-1] == nil ||
|
|
238
|
+
args[len(args)-1].Kind != shimast.KindArrowFunction {
|
|
239
|
+
return false
|
|
240
|
+
}
|
|
241
|
+
arrow := args[len(args)-1].AsArrowFunction()
|
|
242
|
+
return arrow != nil && arrowBodyBreaksAfterArrow(arrow.Body)
|
|
243
|
+
}
|
|
244
|
+
|
|
231
245
|
// testCalleePatterns is Prettier's exact testCallCalleePatterns set (the dotted
|
|
232
246
|
// callee chains its isTestCallCallee matches). Matching the precise set, rather
|
|
233
247
|
// than "any base with a {only,skip,…} tail", avoids both over-matching
|
|
@@ -500,20 +514,17 @@ func anyLeadingItemBreaks(items []Doc) bool {
|
|
|
500
514
|
return false
|
|
501
515
|
}
|
|
502
516
|
|
|
503
|
-
// shouldHugLastArgument reports whether the final entry of `args` is a
|
|
504
|
-
//
|
|
505
|
-
// literal, a function expression, or an arrow function whose body
|
|
506
|
-
//
|
|
507
|
-
//
|
|
508
|
-
//
|
|
517
|
+
// shouldHugLastArgument reports whether the final entry of `args` is a shape
|
|
518
|
+
// Prettier keeps attached to the call's opening line: an object or array
|
|
519
|
+
// literal, a function expression, or an arrow function whose body has an
|
|
520
|
+
// expandable layout. A block/object/array body also hugs the closing paren; a
|
|
521
|
+
// call/conditional/JSX body breaks after `=>` and closes the outer list on its
|
|
522
|
+
// own line. Hugging only applies when the argument is genuinely last.
|
|
509
523
|
//
|
|
510
|
-
// An arrow with any other expression body (`(x) => x.id`) is
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
// Routing it through the normal list shape instead lets the argument
|
|
515
|
-
// list explode onto its own line when the call does not fit, which is
|
|
516
|
-
// what Prettier does.
|
|
524
|
+
// An arrow with any other expression body (`(x) => x.id`) is deliberately
|
|
525
|
+
// excluded. It carries no internal break point, so the hugging shape would pin
|
|
526
|
+
// the whole call to one line even when it overflows printWidth. The normal list
|
|
527
|
+
// shape can instead explode that argument onto its own line.
|
|
517
528
|
func shouldHugLastArgument(args []*shimast.Node) bool {
|
|
518
529
|
if len(args) == 0 {
|
|
519
530
|
return false
|
|
@@ -544,11 +555,11 @@ func shouldHugLastArgument(args []*shimast.Node) bool {
|
|
|
544
555
|
return true
|
|
545
556
|
}
|
|
546
557
|
|
|
547
|
-
// lastArgHuggableShape reports whether `node`
|
|
548
|
-
//
|
|
549
|
-
//
|
|
550
|
-
//
|
|
551
|
-
//
|
|
558
|
+
// lastArgHuggableShape reports whether `node` has a Prettier-style expandable
|
|
559
|
+
// last-argument layout. Objects, arrays, and functions expand internally; an
|
|
560
|
+
// arrow with a call/conditional/JSX body can break immediately after `=>`.
|
|
561
|
+
// Other concise arrows (`(x) => x.id`) have no internal break point and are
|
|
562
|
+
// excluded so an overflowing call can use the ordinary exploded list.
|
|
552
563
|
func lastArgHuggableShape(last *shimast.Node) bool {
|
|
553
564
|
switch last.Kind {
|
|
554
565
|
case shimast.KindFunctionExpression:
|
|
@@ -595,14 +606,10 @@ func lastArgHuggableShape(last *shimast.Node) bool {
|
|
|
595
606
|
// explodes the whole list. A block body hugs regardless (handled above).
|
|
596
607
|
return arrow.Type == nil || arrow.Type.Kind != shimast.KindTypeReference
|
|
597
608
|
}
|
|
609
|
+
return arrowBodyBreaksAfterArrow(body)
|
|
598
610
|
}
|
|
599
611
|
// DEFERRED (architectural, not a one-line predicate fix; do not re-add to the
|
|
600
612
|
// switch above without the supporting layout work):
|
|
601
|
-
// - A call/conditional/JSX-expression arrow body. Prettier's couldExpandArg
|
|
602
|
-
// hugs it, but ttsc emits the arrow signature (`=> `) verbatim with no
|
|
603
|
-
// break point after `=>`, so hugging here would force the FIRST group
|
|
604
|
-
// inside the call instead of breaking after `=>` — a different shape, not
|
|
605
|
-
// parity. Needs a break-after-`=>` arrow-body layout variant first.
|
|
606
613
|
// - A trailing `as`/`satisfies` cast wrapping a huggable last argument.
|
|
607
614
|
// Prettier's couldExpandArg recurses into the cast, but ttsc does not
|
|
608
615
|
// dispatch KindAsExpression (it prints verbatim), so forceBreakFirstGroup
|
|
@@ -0,0 +1,319 @@
|
|
|
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
|
+
// printControlFlowStatement renders a loop, `if`, `try`, or `switch` by
|
|
11
|
+
// preserving its headers and clause keywords exactly as written while laying
|
|
12
|
+
// out the blocks and statements they control.
|
|
13
|
+
//
|
|
14
|
+
// The preserved gaps keep `format/clause-join` as the owner of header spacing
|
|
15
|
+
// and retain comments without minting new `else`, `catch`, or `finally`
|
|
16
|
+
// tokens. A multi-line preserved gap taints coverage because its original
|
|
17
|
+
// interior columns cannot be re-indented safely.
|
|
18
|
+
//
|
|
19
|
+
// A BRACELESS body is left alone entirely. Prettier indents it one level past
|
|
20
|
+
// the header, which is layout the block-depth model has no frame for, and
|
|
21
|
+
// `format/indent` cedes it for exactly that reason (`cededUnderBracelessBody`).
|
|
22
|
+
//
|
|
23
|
+
// The second return value is the `covered` flag: see PrintNode.
|
|
24
|
+
func printControlFlowStatement(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
25
|
+
if node == nil {
|
|
26
|
+
return Doc{}, true
|
|
27
|
+
}
|
|
28
|
+
switch node.Kind {
|
|
29
|
+
case shimast.KindForStatement,
|
|
30
|
+
shimast.KindForOfStatement,
|
|
31
|
+
shimast.KindForInStatement,
|
|
32
|
+
shimast.KindWhileStatement:
|
|
33
|
+
body := singleControlFlowBody(node)
|
|
34
|
+
if body == nil || body.Kind != shimast.KindBlock {
|
|
35
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
36
|
+
}
|
|
37
|
+
return printNodeAroundChildren(ctx, node, []*shimast.Node{body})
|
|
38
|
+
case shimast.KindIfStatement:
|
|
39
|
+
children, ok := printableIfChildren(node)
|
|
40
|
+
if !ok {
|
|
41
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
42
|
+
}
|
|
43
|
+
return printNodeAroundChildren(ctx, node, children)
|
|
44
|
+
case shimast.KindTryStatement:
|
|
45
|
+
children, ok := printableTryChildren(node)
|
|
46
|
+
if !ok {
|
|
47
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
48
|
+
}
|
|
49
|
+
return printNodeAroundChildren(ctx, node, children)
|
|
50
|
+
case shimast.KindSwitchStatement:
|
|
51
|
+
stmt := node.AsSwitchStatement()
|
|
52
|
+
if stmt == nil || stmt.CaseBlock == nil {
|
|
53
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
54
|
+
}
|
|
55
|
+
return printNodeAroundChildren(ctx, node, []*shimast.Node{stmt.CaseBlock})
|
|
56
|
+
}
|
|
57
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// singleControlFlowBody returns the one statement a loop controls.
|
|
61
|
+
func singleControlFlowBody(node *shimast.Node) *shimast.Node {
|
|
62
|
+
switch node.Kind {
|
|
63
|
+
case shimast.KindForStatement:
|
|
64
|
+
if stmt := node.AsForStatement(); stmt != nil {
|
|
65
|
+
return stmt.Statement
|
|
66
|
+
}
|
|
67
|
+
case shimast.KindForOfStatement, shimast.KindForInStatement:
|
|
68
|
+
if stmt := node.AsForInOrOfStatement(); stmt != nil {
|
|
69
|
+
return stmt.Statement
|
|
70
|
+
}
|
|
71
|
+
case shimast.KindWhileStatement:
|
|
72
|
+
if stmt := node.AsWhileStatement(); stmt != nil {
|
|
73
|
+
return stmt.Statement
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return nil
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// printableIfChildren returns the branches an if-printer may lay out. Every
|
|
80
|
+
// concrete branch must be a block; an `else if` is accepted recursively because
|
|
81
|
+
// its own printer applies the same invariant. A braceless branch makes the
|
|
82
|
+
// whole statement fall back to verbatim rather than formatting only one half.
|
|
83
|
+
func printableIfChildren(node *shimast.Node) ([]*shimast.Node, bool) {
|
|
84
|
+
if node == nil {
|
|
85
|
+
return nil, false
|
|
86
|
+
}
|
|
87
|
+
stmt := node.AsIfStatement()
|
|
88
|
+
if stmt == nil || stmt.ThenStatement == nil || stmt.ThenStatement.Kind != shimast.KindBlock {
|
|
89
|
+
return nil, false
|
|
90
|
+
}
|
|
91
|
+
children := []*shimast.Node{stmt.ThenStatement}
|
|
92
|
+
if stmt.ElseStatement == nil {
|
|
93
|
+
return children, true
|
|
94
|
+
}
|
|
95
|
+
if stmt.ElseStatement.Kind == shimast.KindBlock {
|
|
96
|
+
return append(children, stmt.ElseStatement), true
|
|
97
|
+
}
|
|
98
|
+
if stmt.ElseStatement.Kind == shimast.KindIfStatement {
|
|
99
|
+
if _, ok := printableIfChildren(stmt.ElseStatement); ok {
|
|
100
|
+
return append(children, stmt.ElseStatement), true
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return nil, false
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// printableTryChildren returns the try, catch, and finally blocks in source
|
|
107
|
+
// order. The catch declaration stays in the preserved gap before its block.
|
|
108
|
+
func printableTryChildren(node *shimast.Node) ([]*shimast.Node, bool) {
|
|
109
|
+
stmt := node.AsTryStatement()
|
|
110
|
+
if stmt == nil || stmt.TryBlock == nil {
|
|
111
|
+
return nil, false
|
|
112
|
+
}
|
|
113
|
+
children := []*shimast.Node{stmt.TryBlock}
|
|
114
|
+
if stmt.CatchClause != nil {
|
|
115
|
+
clause := stmt.CatchClause.AsCatchClause()
|
|
116
|
+
if clause == nil || clause.Block == nil {
|
|
117
|
+
return nil, false
|
|
118
|
+
}
|
|
119
|
+
children = append(children, clause.Block)
|
|
120
|
+
}
|
|
121
|
+
if stmt.FinallyBlock != nil {
|
|
122
|
+
children = append(children, stmt.FinallyBlock)
|
|
123
|
+
}
|
|
124
|
+
return children, true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// printNodeAroundChildren preserves the source gaps around selected children
|
|
128
|
+
// and dispatches those children through the structured printer. The selected
|
|
129
|
+
// ranges must be non-overlapping and in source order.
|
|
130
|
+
func printNodeAroundChildren(ctx *PrintContext, node *shimast.Node, children []*shimast.Node) (Doc, bool) {
|
|
131
|
+
start := shimscanner.SkipTrivia(ctx.Source, node.Pos())
|
|
132
|
+
end := node.End()
|
|
133
|
+
if start < 0 || end < start || end > len(ctx.Source) {
|
|
134
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
135
|
+
}
|
|
136
|
+
cursor := start
|
|
137
|
+
covered := true
|
|
138
|
+
parts := make([]Doc, 0, len(children)*2+1)
|
|
139
|
+
for _, child := range children {
|
|
140
|
+
if child == nil {
|
|
141
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
142
|
+
}
|
|
143
|
+
childStart := shimscanner.SkipTrivia(ctx.Source, child.Pos())
|
|
144
|
+
childEnd := child.End()
|
|
145
|
+
if childStart < cursor || childEnd < childStart || childEnd > end {
|
|
146
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
147
|
+
}
|
|
148
|
+
gap := ctx.Source[cursor:childStart]
|
|
149
|
+
covered = covered && !strings.Contains(gap, "\n")
|
|
150
|
+
parts = append(parts, Text(gap))
|
|
151
|
+
childDoc, childCovered := PrintNode(ctx, child)
|
|
152
|
+
covered = covered && childCovered
|
|
153
|
+
parts = append(parts, childDoc)
|
|
154
|
+
cursor = childEnd
|
|
155
|
+
}
|
|
156
|
+
suffix := ctx.Source[cursor:end]
|
|
157
|
+
covered = covered && !strings.Contains(suffix, "\n")
|
|
158
|
+
parts = append(parts, Text(suffix))
|
|
159
|
+
return Concat(parts...), covered
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// printSwitchCaseBlock renders the braces and clauses of a switch. Clauses are
|
|
163
|
+
// indented once beneath the switch; their statements are indented once more.
|
|
164
|
+
func printSwitchCaseBlock(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
165
|
+
if node == nil {
|
|
166
|
+
return Doc{}, true
|
|
167
|
+
}
|
|
168
|
+
block := node.AsCaseBlock()
|
|
169
|
+
start := shimscanner.SkipTrivia(ctx.Source, node.Pos())
|
|
170
|
+
end := node.End()
|
|
171
|
+
if block == nil || block.Clauses == nil || start < 0 || end <= start || end > len(ctx.Source) ||
|
|
172
|
+
ctx.Source[start] != '{' || ctx.Source[end-1] != '}' {
|
|
173
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
174
|
+
}
|
|
175
|
+
if nodeHasNonItemComment(ctx, node, block.Clauses.Nodes) {
|
|
176
|
+
return verbatim(ctx, node), false
|
|
177
|
+
}
|
|
178
|
+
clauses := block.Clauses.Nodes
|
|
179
|
+
if len(clauses) == 0 {
|
|
180
|
+
return Text("{}"), true
|
|
181
|
+
}
|
|
182
|
+
docs := make([]Doc, 0, len(clauses))
|
|
183
|
+
covered := true
|
|
184
|
+
for _, clause := range clauses {
|
|
185
|
+
if clause == nil {
|
|
186
|
+
return verbatim(ctx, node), false
|
|
187
|
+
}
|
|
188
|
+
doc, childCovered := printSwitchClause(ctx, clause)
|
|
189
|
+
covered = covered && childCovered
|
|
190
|
+
docs = append(docs, doc)
|
|
191
|
+
}
|
|
192
|
+
body := joinPrintedNodes(ctx, clauses, docs)
|
|
193
|
+
return Concat(
|
|
194
|
+
Text("{"),
|
|
195
|
+
Indent(ctx.indentUnit(), Hardline(), body),
|
|
196
|
+
Hardline(),
|
|
197
|
+
Text("}"),
|
|
198
|
+
), covered
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// printSwitchClause renders `case` or `default` plus its statement list. A
|
|
202
|
+
// block written on the clause header line stays there, matching Prettier's
|
|
203
|
+
// `case X: {` layout; ordinary statements begin on the next indented line.
|
|
204
|
+
func printSwitchClause(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
205
|
+
if node == nil {
|
|
206
|
+
return Doc{}, true
|
|
207
|
+
}
|
|
208
|
+
clause := node.AsCaseOrDefaultClause()
|
|
209
|
+
start := shimscanner.SkipTrivia(ctx.Source, node.Pos())
|
|
210
|
+
end := node.End()
|
|
211
|
+
if clause == nil || clause.Statements == nil || start < 0 || end < start || end > len(ctx.Source) {
|
|
212
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
213
|
+
}
|
|
214
|
+
if nodeHasNonItemComment(ctx, node, clause.Statements.Nodes) {
|
|
215
|
+
return verbatim(ctx, node), false
|
|
216
|
+
}
|
|
217
|
+
statements := clause.Statements.Nodes
|
|
218
|
+
if len(statements) == 0 {
|
|
219
|
+
source := strings.TrimRight(ctx.Source[start:end], " \t\r\n")
|
|
220
|
+
return Text(source), !strings.Contains(source, "\n")
|
|
221
|
+
}
|
|
222
|
+
if statements[0] == nil {
|
|
223
|
+
return verbatim(ctx, node), false
|
|
224
|
+
}
|
|
225
|
+
firstStart := shimscanner.SkipTrivia(ctx.Source, statements[0].Pos())
|
|
226
|
+
if firstStart < start || firstStart > end {
|
|
227
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
228
|
+
}
|
|
229
|
+
header := strings.TrimRight(ctx.Source[start:firstStart], " \t\r\n")
|
|
230
|
+
covered := !strings.Contains(header, "\n")
|
|
231
|
+
docs := make([]Doc, 0, len(statements))
|
|
232
|
+
for _, statement := range statements {
|
|
233
|
+
if statement == nil {
|
|
234
|
+
return verbatim(ctx, node), false
|
|
235
|
+
}
|
|
236
|
+
doc, childCovered := PrintNode(ctx, statement)
|
|
237
|
+
covered = covered && childCovered
|
|
238
|
+
docs = append(docs, doc)
|
|
239
|
+
}
|
|
240
|
+
if statements[0] != nil && statements[0].Kind == shimast.KindBlock &&
|
|
241
|
+
lineStartOffset(ctx.Source, start) == lineStartOffset(ctx.Source, firstStart) {
|
|
242
|
+
parts := []Doc{Text(header), Text(" "), docs[0]}
|
|
243
|
+
if len(statements) > 1 {
|
|
244
|
+
rest := joinPrintedNodes(ctx, statements[1:], docs[1:])
|
|
245
|
+
parts = append(parts, Indent(ctx.indentUnit(), Hardline(), rest))
|
|
246
|
+
}
|
|
247
|
+
return Concat(parts...), covered
|
|
248
|
+
}
|
|
249
|
+
return Concat(
|
|
250
|
+
Text(header),
|
|
251
|
+
Indent(ctx.indentUnit(), Hardline(), joinPrintedNodes(ctx, statements, docs)),
|
|
252
|
+
), covered
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// printVariableStatement dispatches every initializer while preserving names,
|
|
256
|
+
// type annotations, commas, modifiers, and the terminator verbatim.
|
|
257
|
+
func printVariableStatement(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
258
|
+
if node == nil {
|
|
259
|
+
return Doc{}, true
|
|
260
|
+
}
|
|
261
|
+
stmt := node.AsVariableStatement()
|
|
262
|
+
if stmt == nil || stmt.DeclarationList == nil {
|
|
263
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
264
|
+
}
|
|
265
|
+
list := stmt.DeclarationList.AsVariableDeclarationList()
|
|
266
|
+
if list == nil || list.Declarations == nil {
|
|
267
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
268
|
+
}
|
|
269
|
+
children := make([]*shimast.Node, 0, len(list.Declarations.Nodes))
|
|
270
|
+
for _, declaration := range list.Declarations.Nodes {
|
|
271
|
+
if declaration == nil {
|
|
272
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
273
|
+
}
|
|
274
|
+
value := declaration.AsVariableDeclaration()
|
|
275
|
+
if value != nil && value.Initializer != nil {
|
|
276
|
+
children = append(children, value.Initializer)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if len(children) == 0 {
|
|
280
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
281
|
+
}
|
|
282
|
+
return printNodeAroundChildren(ctx, node, children)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// printThrowStatement preserves `throw` and its terminator while dispatching
|
|
286
|
+
// the thrown expression so a nested callback or literal can reflow.
|
|
287
|
+
func printThrowStatement(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
288
|
+
if node == nil {
|
|
289
|
+
return Doc{}, true
|
|
290
|
+
}
|
|
291
|
+
stmt := node.AsThrowStatement()
|
|
292
|
+
if stmt == nil || stmt.Expression == nil {
|
|
293
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
294
|
+
}
|
|
295
|
+
return printNodeAroundChildren(ctx, node, []*shimast.Node{stmt.Expression})
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// joinPrintedNodes joins source-ordered nodes with hard lines while retaining
|
|
299
|
+
// one author-written blank line. An empty-statement ASI guard remains glued to
|
|
300
|
+
// the statement it protects, mirroring printBlock's established behavior.
|
|
301
|
+
func joinPrintedNodes(ctx *PrintContext, nodes []*shimast.Node, docs []Doc) Doc {
|
|
302
|
+
parts := make([]Doc, 0, len(docs)*2)
|
|
303
|
+
for i, doc := range docs {
|
|
304
|
+
if i > 0 {
|
|
305
|
+
if nodes[i-1] != nil && nodes[i] != nil &&
|
|
306
|
+
nodes[i-1].Kind == shimast.KindEmptyStatement &&
|
|
307
|
+
!rangeHasNewline(ctx.Source, nodes[i-1].End(), shimscanner.SkipTrivia(ctx.Source, nodes[i].Pos())) {
|
|
308
|
+
parts = append(parts, doc)
|
|
309
|
+
continue
|
|
310
|
+
}
|
|
311
|
+
if nodes[i-1] != nil && nodes[i] != nil && blankLineBetweenStatements(ctx.Source, nodes[i-1].End(), nodes[i].Pos()) {
|
|
312
|
+
parts = append(parts, Literalline())
|
|
313
|
+
}
|
|
314
|
+
parts = append(parts, Hardline())
|
|
315
|
+
}
|
|
316
|
+
parts = append(parts, doc)
|
|
317
|
+
}
|
|
318
|
+
return Concat(parts...)
|
|
319
|
+
}
|