@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
|
@@ -9,10 +9,10 @@ import (
|
|
|
9
9
|
//
|
|
10
10
|
// The dispatcher is the bridge between the TypeScript-Go AST and the
|
|
11
11
|
// printer engine. It walks one node at a time and emits a Doc tree
|
|
12
|
-
// shaped to that node's grammar. Coverage is intentionally partial
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
12
|
+
// shaped to that node's grammar. Coverage is intentionally partial;
|
|
13
|
+
// the verbatim fallback below guarantees that an un-handled node kind
|
|
14
|
+
// contributes its original source bytes verbatim, so the printer can
|
|
15
|
+
// be wired up to a rule without breaking files that happen to use
|
|
16
16
|
// shapes the per-node printers don't yet understand.
|
|
17
17
|
//
|
|
18
18
|
// The price of verbatim fallback is that reflow stops at the boundary
|
|
@@ -20,6 +20,19 @@ import (
|
|
|
20
20
|
// dispatcher doesn't recognize stays long. That trade-off is preferable
|
|
21
21
|
// to corrupting unfamiliar shapes — extension over time turns each
|
|
22
22
|
// verbatim hop into a real reflow.
|
|
23
|
+
//
|
|
24
|
+
// Coverage signal. A verbatim slice keeps its *original* source column.
|
|
25
|
+
// When an un-handled node spans multiple lines, its interior lines are
|
|
26
|
+
// frozen at the columns the user wrote while the enclosing reflow
|
|
27
|
+
// re-indents everything around it — the result is inconsistently
|
|
28
|
+
// indented, corrupt output. To prevent that, every printer reports a
|
|
29
|
+
// `covered` boolean alongside its Doc: `true` means the whole printed
|
|
30
|
+
// subtree is reflow-safe (no multi-line verbatim node), `false` means a
|
|
31
|
+
// multi-line verbatim node is buried inside. The format/print-width
|
|
32
|
+
// rule abstains entirely when `covered` is false, so `ttsc format`
|
|
33
|
+
// either reflows correctly or leaves the bytes untouched — it never
|
|
34
|
+
// emits the half-reflowed shape. Single-line verbatim is always safe:
|
|
35
|
+
// a node confined to one source line has no interior column to freeze.
|
|
23
36
|
|
|
24
37
|
// PrintContext bundles the per-file inputs every per-node printer
|
|
25
38
|
// needs. The dispatcher constructs one per top-level reflow and threads
|
|
@@ -42,46 +55,96 @@ func NewPrintContext(file *shimast.SourceFile, opts PrintOptions) *PrintContext
|
|
|
42
55
|
|
|
43
56
|
// PrintNode is the dispatcher entry. It picks a per-node printer based
|
|
44
57
|
// on `node.Kind` and falls back to the verbatim source slice when no
|
|
45
|
-
// printer is registered. Returns the printed Doc and a
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
58
|
+
// printer is registered. Returns the printed Doc and a `covered`
|
|
59
|
+
// boolean: `true` when the whole printed subtree is reflow-safe,
|
|
60
|
+
// `false` when a multi-line verbatim node is buried inside it.
|
|
61
|
+
//
|
|
62
|
+
// The format/print-width rule consults `covered` to decide whether to
|
|
63
|
+
// emit an edit at all — see the coverage-signal note at the top of this
|
|
64
|
+
// file. A `false` reading is a hard abstain, not a soft hint.
|
|
50
65
|
func PrintNode(ctx *PrintContext, node *shimast.Node) (Doc, bool) {
|
|
51
66
|
if node == nil {
|
|
52
|
-
return Doc{},
|
|
67
|
+
return Doc{}, true
|
|
53
68
|
}
|
|
54
|
-
if doc,
|
|
55
|
-
return doc,
|
|
69
|
+
if doc, covered, ok := dispatchNode(ctx, node); ok {
|
|
70
|
+
return doc, covered
|
|
56
71
|
}
|
|
57
|
-
return verbatim(ctx, node),
|
|
72
|
+
return verbatim(ctx, node), !nodeSpansMultipleLines(ctx, node)
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
// dispatchNode is the per-kind switch. Each branch returns
|
|
61
|
-
// (doc,
|
|
76
|
+
// (doc, covered, true) when it produces a structured Doc, or
|
|
62
77
|
// (zero, false, false) to let the caller fall back to verbatim. The
|
|
63
|
-
//
|
|
64
|
-
//
|
|
78
|
+
// `covered` flag is `true` only when the per-node printer guarantees
|
|
79
|
+
// the whole subtree it produced is free of multi-line verbatim slices.
|
|
65
80
|
func dispatchNode(ctx *PrintContext, node *shimast.Node) (Doc, bool, bool) {
|
|
66
81
|
switch node.Kind {
|
|
67
82
|
case shimast.KindObjectLiteralExpression:
|
|
68
|
-
|
|
83
|
+
doc, covered := printObjectLiteral(ctx, node)
|
|
84
|
+
return doc, covered, true
|
|
69
85
|
case shimast.KindArrayLiteralExpression:
|
|
70
|
-
|
|
86
|
+
doc, covered := printArrayLiteral(ctx, node)
|
|
87
|
+
return doc, covered, true
|
|
71
88
|
case shimast.KindCallExpression:
|
|
72
|
-
|
|
89
|
+
doc, covered := printCallExpression(ctx, node)
|
|
90
|
+
return doc, covered, true
|
|
73
91
|
case shimast.KindNewExpression:
|
|
74
|
-
|
|
92
|
+
doc, covered := printNewExpression(ctx, node)
|
|
93
|
+
return doc, covered, true
|
|
75
94
|
case shimast.KindNamedImports:
|
|
76
|
-
|
|
95
|
+
doc, covered := printNamedImports(ctx, node)
|
|
96
|
+
return doc, covered, true
|
|
77
97
|
case shimast.KindNamedExports:
|
|
78
|
-
|
|
98
|
+
doc, covered := printNamedExports(ctx, node)
|
|
99
|
+
return doc, covered, true
|
|
79
100
|
case shimast.KindImportDeclaration:
|
|
80
|
-
|
|
101
|
+
doc, covered := printImportDeclaration(ctx, node)
|
|
102
|
+
return doc, covered, true
|
|
103
|
+
case shimast.KindArrowFunction:
|
|
104
|
+
doc, covered := printArrowFunction(ctx, node)
|
|
105
|
+
return doc, covered, true
|
|
106
|
+
case shimast.KindFunctionExpression:
|
|
107
|
+
doc, covered := printFunctionExpression(ctx, node)
|
|
108
|
+
return doc, covered, true
|
|
109
|
+
case shimast.KindParenthesizedExpression:
|
|
110
|
+
doc, covered := printParenthesizedExpression(ctx, node)
|
|
111
|
+
return doc, covered, true
|
|
112
|
+
case shimast.KindBlock:
|
|
113
|
+
doc, covered := printBlock(ctx, node)
|
|
114
|
+
return doc, covered, true
|
|
115
|
+
case shimast.KindExpressionStatement:
|
|
116
|
+
doc, covered := printExpressionStatement(ctx, node)
|
|
117
|
+
return doc, covered, true
|
|
118
|
+
case shimast.KindReturnStatement:
|
|
119
|
+
doc, covered := printReturnStatement(ctx, node)
|
|
120
|
+
return doc, covered, true
|
|
81
121
|
}
|
|
82
122
|
return Doc{}, false, false
|
|
83
123
|
}
|
|
84
124
|
|
|
125
|
+
// nodeSpansMultipleLines reports whether `node`'s trivia-trimmed source
|
|
126
|
+
// range crosses a newline. A verbatim slice that stays on one line is
|
|
127
|
+
// always reflow-safe — there is no interior column for the enclosing
|
|
128
|
+
// re-indent to leave stranded — so the dispatcher treats single-line
|
|
129
|
+
// verbatim as `covered`. A multi-line verbatim node freezes its
|
|
130
|
+
// interior columns and is reported uncovered.
|
|
131
|
+
func nodeSpansMultipleLines(ctx *PrintContext, node *shimast.Node) bool {
|
|
132
|
+
if node == nil {
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
start := shimscanner.SkipTrivia(ctx.Source, node.Pos())
|
|
136
|
+
end := node.End()
|
|
137
|
+
if start < 0 || end < start || end > len(ctx.Source) {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
for i := start; i < end; i++ {
|
|
141
|
+
if ctx.Source[i] == '\n' {
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return false
|
|
146
|
+
}
|
|
147
|
+
|
|
85
148
|
// verbatim returns the original source bytes for `node`, leading trivia
|
|
86
149
|
// trimmed. Use this whenever a printer cannot fully cover a node — the
|
|
87
150
|
// surrounding doc tree still flows, but the verbatim slice carries
|
|
@@ -98,9 +161,10 @@ func verbatim(ctx *PrintContext, node *shimast.Node) Doc {
|
|
|
98
161
|
return Text(ctx.Source[start:end])
|
|
99
162
|
}
|
|
100
163
|
|
|
101
|
-
// verbatimRange
|
|
102
|
-
//
|
|
103
|
-
// single AST node (e.g. tokens
|
|
164
|
+
// verbatimRange returns a Text doc holding src[start:end] verbatim.
|
|
165
|
+
// It is the position-only sibling of verbatim: use it when the sub-range
|
|
166
|
+
// to copy does not correspond to a single AST node (e.g. `<T>` tokens
|
|
167
|
+
// that surround a type-argument NodeList).
|
|
104
168
|
func verbatimRange(src string, start, end int) Doc {
|
|
105
169
|
if start < 0 || end < start || end > len(src) {
|
|
106
170
|
return Doc{}
|
|
@@ -108,11 +172,47 @@ func verbatimRange(src string, start, end int) Doc {
|
|
|
108
172
|
return Text(src[start:end])
|
|
109
173
|
}
|
|
110
174
|
|
|
111
|
-
// indentUnit returns
|
|
112
|
-
//
|
|
175
|
+
// indentUnit returns the number of columns in one indentation step,
|
|
176
|
+
// derived from PrintOptions.TabWidth. Falls back to 2 when TabWidth
|
|
177
|
+
// is not set, matching the Prettier default.
|
|
113
178
|
func (ctx *PrintContext) indentUnit() int {
|
|
114
179
|
if ctx.Opts.TabWidth > 0 {
|
|
115
180
|
return ctx.Opts.TabWidth
|
|
116
181
|
}
|
|
117
182
|
return 2
|
|
118
183
|
}
|
|
184
|
+
|
|
185
|
+
// trailingCommaMode normalizes ctx.Opts.TrailingComma to one of "all",
|
|
186
|
+
// "es5", or "none". An empty value — the zero value for the field —
|
|
187
|
+
// reads as "all" to keep pre-existing callers and tests that built a
|
|
188
|
+
// PrintOptions{} without setting the field on Prettier's default
|
|
189
|
+
// behavior. Any other value also reads as "all"; the config layer
|
|
190
|
+
// rejects unknown strings before they reach the printer (see
|
|
191
|
+
// `expandFormatBlock` in config_format.go), so a stray value here is a
|
|
192
|
+
// programmer error and the safest fallback is the most-commas mode.
|
|
193
|
+
func (ctx *PrintContext) trailingCommaMode() string {
|
|
194
|
+
switch ctx.Opts.TrailingComma {
|
|
195
|
+
case "es5", "none":
|
|
196
|
+
return ctx.Opts.TrailingComma
|
|
197
|
+
}
|
|
198
|
+
return "all"
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// allowsCallArgumentTrailingComma reports whether a multi-line call /
|
|
202
|
+
// new argument list should emit a trailing comma under the current
|
|
203
|
+
// trailingComma setting. Trailing commas in call arguments arrived in
|
|
204
|
+
// ES2017, so Prettier's "es5" mode excludes them just like "none" does;
|
|
205
|
+
// only "all" keeps them. Parameter lists are not printed by the
|
|
206
|
+
// dispatcher today, but the same rule applies to them when they are.
|
|
207
|
+
func (ctx *PrintContext) allowsCallArgumentTrailingComma() bool {
|
|
208
|
+
return ctx.trailingCommaMode() == "all"
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// allowsEs5TrailingComma reports whether a multi-line ES5-permitted
|
|
212
|
+
// list — arrays, objects, named imports / exports — should emit a
|
|
213
|
+
// trailing comma. Only "none" suppresses the comma here; "es5" and
|
|
214
|
+
// "all" both keep it because ES5 has accepted trailing commas in these
|
|
215
|
+
// positions since the language's inception.
|
|
216
|
+
func (ctx *PrintContext) allowsEs5TrailingComma() bool {
|
|
217
|
+
return ctx.trailingCommaMode() != "none"
|
|
218
|
+
}
|
package/linthost/print_doc.go
CHANGED
|
@@ -52,10 +52,17 @@ package linthost
|
|
|
52
52
|
// - DocLineSuffix queues output until the next hardline/softline that
|
|
53
53
|
// actually breaks; used for trailing line comments that must stick
|
|
54
54
|
// to their source line.
|
|
55
|
+
// - DocConditionalGroup offers an ordered list of layout options; the
|
|
56
|
+
// engine renders the first whose first line fits the width budget
|
|
57
|
+
// and uses the last option as the unconditional fallback.
|
|
55
58
|
//
|
|
56
59
|
// The doc tree is built by helper constructors (Text, Line, Group, …)
|
|
57
60
|
// below. Constructors take their children as variadic or slice
|
|
58
61
|
// arguments so call sites read like a layout DSL.
|
|
62
|
+
|
|
63
|
+
// DocKind is the discriminant tag for a Doc node. Only the variant
|
|
64
|
+
// fields relevant to that kind are populated; all others stay at their
|
|
65
|
+
// zero value.
|
|
59
66
|
type DocKind uint8
|
|
60
67
|
|
|
61
68
|
const (
|
|
@@ -71,6 +78,7 @@ const (
|
|
|
71
78
|
docIfBreak
|
|
72
79
|
docConcat
|
|
73
80
|
docLineSuffix
|
|
81
|
+
docConditionalGroup
|
|
74
82
|
)
|
|
75
83
|
|
|
76
84
|
// Doc is one node in the layout tree. Only the fields relevant to the
|
|
@@ -89,6 +97,11 @@ type Doc struct {
|
|
|
89
97
|
Text string
|
|
90
98
|
Children []Doc
|
|
91
99
|
Width int
|
|
100
|
+
// Break, meaningful only on a docGroup, forces the group to render
|
|
101
|
+
// broken regardless of whether its flat form would fit. A
|
|
102
|
+
// ConditionalGroup option uses it to commit its last argument — a
|
|
103
|
+
// hugged object literal — to the multi-line shape.
|
|
104
|
+
Break bool
|
|
92
105
|
// IfBreak pairs: BreakChild stored in Children[0], FlatChild in Children[1].
|
|
93
106
|
}
|
|
94
107
|
|
|
@@ -112,6 +125,16 @@ func Literalline() Doc { return Doc{Kind: docLiteralline} }
|
|
|
112
125
|
// concatenated.
|
|
113
126
|
func Group(parts ...Doc) Doc { return Doc{Kind: docGroup, Children: parts} }
|
|
114
127
|
|
|
128
|
+
// ConditionalGroup picks the first option whose first line fits the
|
|
129
|
+
// remaining width budget, falling back to the last option when none
|
|
130
|
+
// fit. Where Group makes a single flat-or-break decision, a
|
|
131
|
+
// ConditionalGroup lets a printer offer several distinct shapes — a
|
|
132
|
+
// call's hugged vs. exploded argument list — and have the engine choose
|
|
133
|
+
// between them. The last option must always be a safe fallback.
|
|
134
|
+
func ConditionalGroup(options ...Doc) Doc {
|
|
135
|
+
return Doc{Kind: docConditionalGroup, Children: options}
|
|
136
|
+
}
|
|
137
|
+
|
|
115
138
|
// Indent adds `width` columns of indentation to every newline emitted by
|
|
116
139
|
// the child doc. Nesting composes.
|
|
117
140
|
func Indent(width int, parts ...Doc) Doc {
|
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
|
}
|