@ttsc/lint 0.12.4 → 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/index.js +227 -135
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintPluginConfig.d.ts +10 -77
- package/lib/structures/TtscLintRuleOptions.d.ts +14 -0
- package/linthost/ast_helpers.go +68 -16
- package/linthost/compile.go +91 -124
- package/linthost/config.go +518 -707
- package/linthost/config_format.go +16 -4
- package/linthost/contrib_adapter.go +7 -0
- package/linthost/directives.go +44 -0
- package/linthost/engine.go +152 -43
- package/linthost/fix.go +24 -33
- package/linthost/flags_gen.go +31 -0
- package/linthost/format.go +96 -3
- package/linthost/host.go +104 -5
- package/linthost/print_dispatch.go +121 -23
- package/linthost/print_doc.go +19 -0
- package/linthost/print_engine.go +168 -4
- package/linthost/print_nodes_array.go +17 -7
- package/linthost/print_nodes_call.go +129 -20
- package/linthost/print_nodes_function.go +353 -0
- package/linthost/print_nodes_imports.go +46 -29
- package/linthost/print_nodes_list.go +86 -5
- package/linthost/print_nodes_object.go +56 -11
- package/linthost/rules_escape.go +20 -3
- package/linthost/rules_format_print_width.go +267 -15
- package/linthost/rules_gap.go +55 -3
- package/linthost/rules_logic.go +64 -5
- package/linthost/rules_problems.go +65 -21
- package/linthost/rules_promise.go +3 -0
- package/linthost/rules_suggestions.go +160 -5
- package/linthost/rules_var.go +1 -1
- package/package.json +3 -3
- package/src/index.ts +245 -168
- package/src/structures/ITtscLintPluginConfig.ts +10 -83
- package/src/structures/TtscLintRuleOptions.ts +15 -0
- package/linthost/eslint_runtime.go +0 -351
package/linthost/print_engine.go
CHANGED
|
@@ -63,6 +63,21 @@ import "strings"
|
|
|
63
63
|
// makes the close brace of a reflowed list land at `BaseIndent`
|
|
64
64
|
// while its children sit at `BaseIndent + indentUnit`.
|
|
65
65
|
//
|
|
66
|
+
// TrailingComma mirrors Prettier's `trailingComma` setting and controls
|
|
67
|
+
// which broken-list shapes the printer emits a trailing comma on:
|
|
68
|
+
//
|
|
69
|
+
// - "all" (default) every multi-line list gets one.
|
|
70
|
+
// - "es5" arrays, objects, named imports / exports get one;
|
|
71
|
+
// call arguments, parameter lists, and type-level
|
|
72
|
+
// lists do not — those positions accepted trailing
|
|
73
|
+
// commas only in ES2017+, so es5 mode skips them
|
|
74
|
+
// to match Prettier and avoid oscillating against
|
|
75
|
+
// the formatter on every cascade pass.
|
|
76
|
+
// - "none" no list gets one.
|
|
77
|
+
//
|
|
78
|
+
// An empty string is treated as "all", which keeps `DefaultPrintOptions()`
|
|
79
|
+
// callers and tests that pre-date this field on their original behavior.
|
|
80
|
+
//
|
|
66
81
|
// Defaults of 0 keep the engine usable for top-of-file reflow without
|
|
67
82
|
// a wrapper.
|
|
68
83
|
type PrintOptions struct {
|
|
@@ -70,14 +85,17 @@ type PrintOptions struct {
|
|
|
70
85
|
TabWidth int
|
|
71
86
|
UseTabs bool
|
|
72
87
|
EndOfLine string
|
|
88
|
+
TrailingComma string
|
|
73
89
|
StartingColumn int
|
|
74
90
|
BaseIndent int
|
|
75
91
|
}
|
|
76
92
|
|
|
77
93
|
// DefaultPrintOptions returns the Prettier defaults: 80-column lines,
|
|
78
|
-
// 2-space indentation, LF line terminators
|
|
94
|
+
// 2-space indentation, LF line terminators, trailing commas on every
|
|
95
|
+
// multi-line list (the `trailingComma: "all"` default Prettier adopted
|
|
96
|
+
// in v2).
|
|
79
97
|
func DefaultPrintOptions() PrintOptions {
|
|
80
|
-
return PrintOptions{PrintWidth: 80, TabWidth: 2, UseTabs: false, EndOfLine: "lf"}
|
|
98
|
+
return PrintOptions{PrintWidth: 80, TabWidth: 2, UseTabs: false, EndOfLine: "lf", TrailingComma: "all"}
|
|
81
99
|
}
|
|
82
100
|
|
|
83
101
|
// printMode is the per-group choice made by the fit measurement.
|
|
@@ -191,13 +209,30 @@ func Print(doc Doc, opts PrintOptions) string {
|
|
|
191
209
|
child := Concat(top.doc.Children...)
|
|
192
210
|
stack = append(stack, printFrame{indent: col, mode: top.mode, doc: child})
|
|
193
211
|
case docGroup:
|
|
194
|
-
// Try flat unless the group
|
|
212
|
+
// Try flat unless the group is forced broken or its flat form
|
|
213
|
+
// would overflow the remaining width.
|
|
195
214
|
child := Concat(top.doc.Children...)
|
|
196
|
-
if fits(child, opts.PrintWidth-col, top.indent) {
|
|
215
|
+
if !top.doc.Break && fits(child, opts.PrintWidth-col, top.indent) {
|
|
197
216
|
stack = append(stack, printFrame{indent: top.indent, mode: modeFlat, doc: child})
|
|
198
217
|
} else {
|
|
199
218
|
stack = append(stack, printFrame{indent: top.indent, mode: modeBreak, doc: child})
|
|
200
219
|
}
|
|
220
|
+
case docConditionalGroup:
|
|
221
|
+
// Render the first option whose first line fits the remaining
|
|
222
|
+
// width; fall back to the last option when none do. fitsFirstLine
|
|
223
|
+
// measures only up to an option's first break, so an option whose
|
|
224
|
+
// later lines wrap — a hugged callback body — is still eligible.
|
|
225
|
+
options := top.doc.Children
|
|
226
|
+
if len(options) > 0 {
|
|
227
|
+
chosen := options[len(options)-1]
|
|
228
|
+
for i := 0; i < len(options)-1; i++ {
|
|
229
|
+
if fitsFirstLine(options[i], opts.PrintWidth-col) {
|
|
230
|
+
chosen = options[i]
|
|
231
|
+
break
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
stack = append(stack, printFrame{indent: top.indent, mode: top.mode, doc: chosen})
|
|
235
|
+
}
|
|
201
236
|
case docIfBreak:
|
|
202
237
|
pick := top.doc.Children[1] // flat
|
|
203
238
|
if top.mode == modeBreak {
|
|
@@ -306,6 +341,11 @@ func fits(doc Doc, remaining int, indent int) bool {
|
|
|
306
341
|
stack = append(stack, frame{mode: top.mode, doc: top.doc.Children[i]})
|
|
307
342
|
}
|
|
308
343
|
case docGroup:
|
|
344
|
+
// A forced-broken group cannot contribute a flat layout; treat it
|
|
345
|
+
// like a hard line break for the enclosing measurement.
|
|
346
|
+
if top.doc.Break {
|
|
347
|
+
return false
|
|
348
|
+
}
|
|
309
349
|
// Measure nested groups in flat mode too — that is the
|
|
310
350
|
// standard Wadler choice: the outer group's "does my
|
|
311
351
|
// flat form fit" question is answered by treating every
|
|
@@ -313,6 +353,11 @@ func fits(doc Doc, remaining int, indent int) bool {
|
|
|
313
353
|
for i := len(top.doc.Children) - 1; i >= 0; i-- {
|
|
314
354
|
stack = append(stack, frame{mode: modeFlat, doc: top.doc.Children[i]})
|
|
315
355
|
}
|
|
356
|
+
case docConditionalGroup:
|
|
357
|
+
// A conditional group's flat form is its first (flattest) option.
|
|
358
|
+
if len(top.doc.Children) > 0 {
|
|
359
|
+
stack = append(stack, frame{mode: top.mode, doc: top.doc.Children[0]})
|
|
360
|
+
}
|
|
316
361
|
case docIfBreak:
|
|
317
362
|
pick := top.doc.Children[1]
|
|
318
363
|
if top.mode == modeBreak {
|
|
@@ -341,3 +386,122 @@ func fits(doc Doc, remaining int, indent int) bool {
|
|
|
341
386
|
}
|
|
342
387
|
return true
|
|
343
388
|
}
|
|
389
|
+
|
|
390
|
+
// fitsFirstLine reports whether the first line of `doc` — every column
|
|
391
|
+
// up to its first line break — renders within `remaining` columns. It
|
|
392
|
+
// drives ConditionalGroup option selection: an option is eligible when
|
|
393
|
+
// its opening line fits, even if its later lines wrap.
|
|
394
|
+
//
|
|
395
|
+
// The walk treats every top-level break point as broken — an IfBreak
|
|
396
|
+
// takes its break branch, and a Line / Softline / Hardline ends the
|
|
397
|
+
// measurement — so it counts exactly the columns the option would place
|
|
398
|
+
// on the line the group starts on. A nested ConditionalGroup
|
|
399
|
+
// contributes its own first option.
|
|
400
|
+
//
|
|
401
|
+
// A nested Group is the exception: a Group with no hard break renders
|
|
402
|
+
// flat when it fits, so its Line separators collapse to spaces and stay
|
|
403
|
+
// on the first line. The walk measures such a Group's flattened width
|
|
404
|
+
// rather than stopping at its first Line; only a Group that carries a
|
|
405
|
+
// Hardline (flatten reports it cannot render flat) ends the first line
|
|
406
|
+
// at its break.
|
|
407
|
+
func fitsFirstLine(doc Doc, remaining int) bool {
|
|
408
|
+
if remaining < 0 {
|
|
409
|
+
return false
|
|
410
|
+
}
|
|
411
|
+
stack := []Doc{doc}
|
|
412
|
+
for len(stack) > 0 {
|
|
413
|
+
top := stack[len(stack)-1]
|
|
414
|
+
stack = stack[:len(stack)-1]
|
|
415
|
+
switch top.Kind {
|
|
416
|
+
case docText:
|
|
417
|
+
if idx := strings.IndexByte(top.Text, '\n'); idx >= 0 {
|
|
418
|
+
// A multi-line Text ends the first line at its first newline.
|
|
419
|
+
return remaining-idx >= 0
|
|
420
|
+
}
|
|
421
|
+
remaining -= len(top.Text)
|
|
422
|
+
if remaining < 0 {
|
|
423
|
+
return false
|
|
424
|
+
}
|
|
425
|
+
case docGroup:
|
|
426
|
+
// A Group that can render flat keeps its Lines on the first line
|
|
427
|
+
// as spaces — measure the flattened form. One that cannot (a
|
|
428
|
+
// Hardline or forced break inside) breaks, so descend and let the
|
|
429
|
+
// Line/Hardline case end the first line at that break.
|
|
430
|
+
if flat, ok := flatten(top); ok {
|
|
431
|
+
stack = append(stack, flat)
|
|
432
|
+
} else {
|
|
433
|
+
for i := len(top.Children) - 1; i >= 0; i-- {
|
|
434
|
+
stack = append(stack, top.Children[i])
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
case docConcat, docIndent, docAlign:
|
|
438
|
+
for i := len(top.Children) - 1; i >= 0; i-- {
|
|
439
|
+
stack = append(stack, top.Children[i])
|
|
440
|
+
}
|
|
441
|
+
case docConditionalGroup:
|
|
442
|
+
if len(top.Children) > 0 {
|
|
443
|
+
stack = append(stack, top.Children[0])
|
|
444
|
+
}
|
|
445
|
+
case docIfBreak:
|
|
446
|
+
// Measuring in break mode: take the broken branch.
|
|
447
|
+
stack = append(stack, top.Children[0])
|
|
448
|
+
case docLine, docSoftline, docHardline, docLiteralline:
|
|
449
|
+
// The first break ends the first line; what fit so far fits.
|
|
450
|
+
return true
|
|
451
|
+
case docNil, docLineSuffix:
|
|
452
|
+
// No first-line width contribution.
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return true
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// flatten returns the all-flat rendering of `doc`: every Group rendered
|
|
459
|
+
// flat, every IfBreak resolved to its flat branch, every Line collapsed
|
|
460
|
+
// to a single space and every Softline to nothing. The second result is
|
|
461
|
+
// false when the doc cannot render flat at all — it carries a Hardline,
|
|
462
|
+
// a Literalline, a forced-broken Group, a multi-line Text or a queued
|
|
463
|
+
// LineSuffix — and the caller must then drop the flat layout option.
|
|
464
|
+
func flatten(doc Doc) (Doc, bool) {
|
|
465
|
+
switch doc.Kind {
|
|
466
|
+
case docText:
|
|
467
|
+
if strings.Contains(doc.Text, "\n") {
|
|
468
|
+
return Doc{}, false
|
|
469
|
+
}
|
|
470
|
+
return doc, true
|
|
471
|
+
case docLine:
|
|
472
|
+
return Text(" "), true
|
|
473
|
+
case docNil, docSoftline:
|
|
474
|
+
return Doc{Kind: docNil}, true
|
|
475
|
+
case docHardline, docLiteralline, docLineSuffix:
|
|
476
|
+
return Doc{}, false
|
|
477
|
+
case docIfBreak:
|
|
478
|
+
return flatten(doc.Children[1])
|
|
479
|
+
case docConditionalGroup:
|
|
480
|
+
if len(doc.Children) == 0 {
|
|
481
|
+
return Doc{Kind: docNil}, true
|
|
482
|
+
}
|
|
483
|
+
return flatten(doc.Children[0])
|
|
484
|
+
case docGroup:
|
|
485
|
+
if doc.Break {
|
|
486
|
+
return Doc{}, false
|
|
487
|
+
}
|
|
488
|
+
return flattenChildren(doc.Children)
|
|
489
|
+
case docConcat, docIndent, docAlign:
|
|
490
|
+
return flattenChildren(doc.Children)
|
|
491
|
+
}
|
|
492
|
+
return doc, true
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// flattenChildren flattens each child and concatenates the results,
|
|
496
|
+
// short-circuiting to (zero, false) when any child cannot render flat.
|
|
497
|
+
func flattenChildren(children []Doc) (Doc, bool) {
|
|
498
|
+
out := make([]Doc, 0, len(children))
|
|
499
|
+
for _, c := range children {
|
|
500
|
+
fc, ok := flatten(c)
|
|
501
|
+
if !ok {
|
|
502
|
+
return Doc{}, false
|
|
503
|
+
}
|
|
504
|
+
out = append(out, fc)
|
|
505
|
+
}
|
|
506
|
+
return Concat(out...), true
|
|
507
|
+
}
|
|
@@ -20,27 +20,37 @@ import (
|
|
|
20
20
|
// Array literals do NOT carry a leading/trailing space inside the
|
|
21
21
|
// brackets in flat mode (`[a, b]`, not `[ a, b ]`), matching every
|
|
22
22
|
// JavaScript formatter. Empty arrays collapse to `[]`.
|
|
23
|
-
|
|
23
|
+
//
|
|
24
|
+
// The second return value is the `covered` flag: see PrintNode. It is
|
|
25
|
+
// the AND of every element's coverage.
|
|
26
|
+
func printArrayLiteral(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
24
27
|
if node == nil {
|
|
25
|
-
return Doc{}
|
|
28
|
+
return Doc{}, true
|
|
26
29
|
}
|
|
27
30
|
arr := node.AsArrayLiteralExpression()
|
|
28
31
|
if arr == nil || arr.Elements == nil {
|
|
29
|
-
return verbatim(ctx, node)
|
|
32
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
30
33
|
}
|
|
31
34
|
items := make([]Doc, 0, len(arr.Elements.Nodes))
|
|
35
|
+
covered := true
|
|
32
36
|
for _, elem := range arr.Elements.Nodes {
|
|
33
37
|
if elem == nil {
|
|
34
|
-
return verbatim(ctx, node)
|
|
38
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
35
39
|
}
|
|
36
|
-
doc,
|
|
40
|
+
doc, childCovered := PrintNode(ctx, elem)
|
|
41
|
+
covered = covered && childCovered
|
|
37
42
|
items = append(items, doc)
|
|
38
43
|
}
|
|
44
|
+
// AddComma honors `format.trailingComma`: arrays accept trailing
|
|
45
|
+
// commas in ES5 so both "all" and "es5" keep them; only "none"
|
|
46
|
+
// suppresses. Hardcoding `true` would oscillate against Prettier on
|
|
47
|
+
// every `none` project (the trailing-comma rule wouldn't insert one
|
|
48
|
+
// and the printer would put one back).
|
|
39
49
|
return printList(ctx, listShape{
|
|
40
50
|
OpenTok: "[",
|
|
41
51
|
CloseTok: "]",
|
|
42
52
|
Items: items,
|
|
43
53
|
Space: false,
|
|
44
|
-
AddComma:
|
|
45
|
-
})
|
|
54
|
+
AddComma: ctx.allowsEs5TrailingComma(),
|
|
55
|
+
}), covered
|
|
46
56
|
}
|
|
@@ -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,24 +107,120 @@ 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
|