@ttsc/lint 0.10.2 → 0.11.0-dev.20260517
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 +172 -33
- package/lib/defineConfig.d.ts +55 -0
- package/lib/defineConfig.js +39 -0
- package/lib/defineConfig.js.map +1 -0
- package/lib/index.d.ts +25 -1
- package/lib/index.js +622 -2
- package/lib/index.js.map +1 -1
- package/lib/structures/ITtscLintPlugin.d.ts +53 -0
- package/lib/structures/ITtscLintPlugin.js +3 -0
- package/lib/structures/ITtscLintPlugin.js.map +1 -0
- package/lib/structures/ITtscLintPluginConfig.d.ts +82 -5
- package/lib/structures/ITtscLintPluginMeta.d.ts +12 -0
- package/lib/structures/ITtscLintPluginMeta.js +3 -0
- package/lib/structures/ITtscLintPluginMeta.js.map +1 -0
- package/lib/structures/PluginRuleNames.d.ts +14 -0
- package/lib/structures/PluginRuleNames.js +3 -0
- package/lib/structures/PluginRuleNames.js.map +1 -0
- package/lib/structures/TtscLintConfig.d.ts +24 -8
- package/lib/structures/TtscLintConfigEntry.d.ts +39 -0
- package/lib/structures/TtscLintConfigEntry.js +3 -0
- package/lib/structures/TtscLintConfigEntry.js.map +1 -0
- package/lib/structures/TtscLintFormatConfig.d.ts +154 -0
- package/lib/structures/TtscLintFormatConfig.js +3 -0
- package/lib/structures/TtscLintFormatConfig.js.map +1 -0
- package/lib/structures/TtscLintPlugins.d.ts +9 -0
- package/lib/structures/TtscLintPlugins.js +3 -0
- package/lib/structures/TtscLintPlugins.js.map +1 -0
- package/lib/structures/TtscLintRule.d.ts +1 -1
- package/lib/structures/TtscLintRuleEntry.d.ts +22 -0
- package/lib/structures/TtscLintRuleEntry.js +3 -0
- package/lib/structures/TtscLintRuleEntry.js.map +1 -0
- package/lib/structures/TtscLintRuleMap.d.ts +44 -0
- package/lib/structures/TtscLintRuleMap.js +3 -0
- package/lib/structures/TtscLintRuleMap.js.map +1 -0
- package/lib/structures/TtscLintRuleOptions.d.ts +149 -0
- package/lib/structures/TtscLintRuleOptions.js +3 -0
- package/lib/structures/TtscLintRuleOptions.js.map +1 -0
- package/lib/structures/defaultFormat.d.ts +31 -0
- package/lib/structures/defaultFormat.js +35 -0
- package/lib/structures/defaultFormat.js.map +1 -0
- package/lib/structures/index.d.ts +10 -0
- package/lib/structures/index.js +10 -0
- package/lib/structures/index.js.map +1 -1
- package/{plugin → linthost}/ast_helpers.go +92 -1
- package/{plugin → linthost}/compile.go +1 -1
- package/{plugin → linthost}/config.go +583 -66
- package/linthost/config_format.go +360 -0
- package/linthost/contrib_adapter.go +134 -0
- package/{plugin → linthost}/directives.go +1 -1
- package/linthost/dispatch.go +62 -0
- package/{plugin → linthost}/engine.go +156 -11
- package/{plugin → linthost}/eslint_runtime.go +76 -2
- package/linthost/fix.go +249 -0
- package/linthost/format.go +107 -0
- package/{plugin → linthost}/host.go +1 -1
- package/linthost/print_dispatch.go +118 -0
- package/linthost/print_doc.go +168 -0
- package/linthost/print_engine.go +343 -0
- package/linthost/print_nodes_array.go +46 -0
- package/linthost/print_nodes_call.go +180 -0
- package/linthost/print_nodes_imports.go +171 -0
- package/linthost/print_nodes_list.go +64 -0
- package/linthost/print_nodes_object.go +51 -0
- package/{plugin → linthost}/rules_arrays.go +1 -1
- package/{plugin → linthost}/rules_console.go +1 -1
- package/{plugin → linthost}/rules_debugger.go +1 -1
- package/{plugin → linthost}/rules_dupes.go +1 -1
- package/{plugin → linthost}/rules_empty.go +1 -1
- package/linthost/rules_escape.go +238 -0
- package/{plugin → linthost}/rules_eval.go +1 -1
- package/{plugin → linthost}/rules_finally.go +1 -1
- package/linthost/rules_format_jsdoc.go +230 -0
- package/linthost/rules_format_print_width.go +332 -0
- package/linthost/rules_format_quotes.go +219 -0
- package/linthost/rules_format_semi.go +190 -0
- package/linthost/rules_format_sort_imports.go +462 -0
- package/linthost/rules_format_trailing_comma.go +460 -0
- package/{plugin → linthost}/rules_gap.go +101 -6
- package/linthost/rules_imports.go +101 -0
- package/{plugin → linthost}/rules_logic.go +55 -3
- package/{plugin → linthost}/rules_loops.go +1 -1
- package/{plugin → linthost}/rules_misc.go +1 -1
- package/linthost/rules_params.go +64 -0
- package/{plugin → linthost}/rules_problems.go +10 -2
- package/linthost/rules_promise.go +111 -0
- package/{plugin → linthost}/rules_protos.go +1 -1
- package/{plugin → linthost}/rules_self.go +1 -1
- package/{plugin → linthost}/rules_strings.go +1 -1
- package/{plugin → linthost}/rules_suggestions.go +51 -9
- package/{plugin → linthost}/rules_throw.go +1 -1
- package/{plugin → linthost}/rules_ts.go +12 -2
- package/{plugin → linthost}/rules_ts_extra.go +90 -4
- package/{plugin → linthost}/rules_var.go +36 -5
- package/package.json +8 -6
- package/plugin/main.go +14 -27
- package/rule/astutil/astutil.go +148 -0
- package/rule/rule.go +291 -0
- package/src/defineConfig.ts +69 -0
- package/src/index.ts +807 -3
- package/src/structures/ITtscLintPlugin.ts +56 -0
- package/src/structures/ITtscLintPluginConfig.ts +86 -5
- package/src/structures/ITtscLintPluginMeta.ts +14 -0
- package/src/structures/PluginRuleNames.ts +19 -0
- package/src/structures/TtscLintConfig.ts +27 -8
- package/src/structures/TtscLintConfigEntry.ts +50 -0
- package/src/structures/TtscLintFormatConfig.ts +169 -0
- package/src/structures/TtscLintPlugins.ts +10 -0
- package/src/structures/TtscLintRule.ts +14 -1
- package/src/structures/TtscLintRuleEntry.ts +28 -0
- package/src/structures/TtscLintRuleMap.ts +52 -0
- package/src/structures/TtscLintRuleOptions.ts +162 -0
- package/src/structures/defaultFormat.ts +33 -0
- package/src/structures/index.ts +10 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
package linthost
|
|
2
|
+
|
|
3
|
+
import "strings"
|
|
4
|
+
|
|
5
|
+
// Printer engine: lay out a Doc tree under a column budget.
|
|
6
|
+
//
|
|
7
|
+
// The algorithm is the same fit-or-break decision Prettier and dprint
|
|
8
|
+
// use, originally from Wadler's "A Prettier Printer" with Lindig's
|
|
9
|
+
// strictness fix:
|
|
10
|
+
//
|
|
11
|
+
// 1. Walk a stack of (indent, mode, doc) frames.
|
|
12
|
+
// 2. When a Group is popped in unknown mode, peek ahead to see whether
|
|
13
|
+
// its flat layout fits within the remaining columns on the current
|
|
14
|
+
// line. The peek uses a separate stack so the main walk is not
|
|
15
|
+
// disturbed.
|
|
16
|
+
// 3. If it fits, the group renders flat: Lines collapse to single
|
|
17
|
+
// spaces, Softlines to nothing, IfBreak picks the flat side.
|
|
18
|
+
// 4. If it does not fit, the group renders broken: Lines and Softlines
|
|
19
|
+
// emit newline+indent, IfBreak picks the break side, every nested
|
|
20
|
+
// Group is measured again under its own remaining-column budget
|
|
21
|
+
// (this is the strictness fix; a naive "broken propagates to all
|
|
22
|
+
// descendants" rule produces uglier output for nested structures).
|
|
23
|
+
// 5. Hardline always emits a newline and forces its enclosing Group
|
|
24
|
+
// into broken mode irrespective of width.
|
|
25
|
+
// 6. LineSuffix output is queued in a local buffer and flushed on the
|
|
26
|
+
// next newline emission, so trailing comments stick to their
|
|
27
|
+
// originating source line.
|
|
28
|
+
//
|
|
29
|
+
// The engine does not modify the input Doc tree, so the same tree can
|
|
30
|
+
// be printed multiple times (e.g. for testing under several widths).
|
|
31
|
+
|
|
32
|
+
// PrintOptions configures the layout pass.
|
|
33
|
+
//
|
|
34
|
+
// PrintWidth is the column budget per line (Prettier's `printWidth`).
|
|
35
|
+
// TabWidth is the indentation increment in columns (Prettier's
|
|
36
|
+
// `tabWidth`). UseTabs swaps space indentation for tab characters; when
|
|
37
|
+
// it is true the printer emits one tab per `TabWidth` columns and falls
|
|
38
|
+
// back to spaces for the remainder, matching dprint's "indent with
|
|
39
|
+
// tabs, align with spaces" convention.
|
|
40
|
+
//
|
|
41
|
+
// EndOfLine is the line terminator: "lf" (default) or "crlf".
|
|
42
|
+
//
|
|
43
|
+
// StartingColumn is the column the first character of the printed
|
|
44
|
+
// output will land in. When the printer is invoked to reflow an
|
|
45
|
+
// interior node (e.g. an object literal that begins partway through
|
|
46
|
+
// its enclosing source line), fit measurement must charge that
|
|
47
|
+
// column against the remaining budget.
|
|
48
|
+
//
|
|
49
|
+
// BaseIndent is the column continuation lines indent to. It is
|
|
50
|
+
// usually the indent of the line containing the printed node — not
|
|
51
|
+
// the column of the node itself. The two differ for "right-hand-side"
|
|
52
|
+
// reflows such as `const x = { … }`: the `{` may live at column 10
|
|
53
|
+
// but its broken-form children should indent to column 2
|
|
54
|
+
// (BaseIndent=0 + indentUnit=2), matching every JS formatter's
|
|
55
|
+
// convention of indenting relative to the surrounding line's left
|
|
56
|
+
// edge.
|
|
57
|
+
//
|
|
58
|
+
// Asymmetry on the first line: the engine never emits an indent for
|
|
59
|
+
// the first character of the printed output. The first line uses
|
|
60
|
+
// `StartingColumn` only — fit measurement charges it, but
|
|
61
|
+
// `writeIndent` is not called. Every newline after that emits
|
|
62
|
+
// `BaseIndent` plus any nested `Indent` contributions, which is what
|
|
63
|
+
// makes the close brace of a reflowed list land at `BaseIndent`
|
|
64
|
+
// while its children sit at `BaseIndent + indentUnit`.
|
|
65
|
+
//
|
|
66
|
+
// Defaults of 0 keep the engine usable for top-of-file reflow without
|
|
67
|
+
// a wrapper.
|
|
68
|
+
type PrintOptions struct {
|
|
69
|
+
PrintWidth int
|
|
70
|
+
TabWidth int
|
|
71
|
+
UseTabs bool
|
|
72
|
+
EndOfLine string
|
|
73
|
+
StartingColumn int
|
|
74
|
+
BaseIndent int
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// DefaultPrintOptions returns the Prettier defaults: 80-column lines,
|
|
78
|
+
// 2-space indentation, LF line terminators.
|
|
79
|
+
func DefaultPrintOptions() PrintOptions {
|
|
80
|
+
return PrintOptions{PrintWidth: 80, TabWidth: 2, UseTabs: false, EndOfLine: "lf"}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// printMode is the per-group choice made by the fit measurement.
|
|
84
|
+
type printMode uint8
|
|
85
|
+
|
|
86
|
+
const (
|
|
87
|
+
modeFlat printMode = iota // group fits flat
|
|
88
|
+
modeBreak // group renders broken
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
// printFrame is one entry on the layout stack.
|
|
92
|
+
type printFrame struct {
|
|
93
|
+
indent int
|
|
94
|
+
mode printMode
|
|
95
|
+
doc Doc
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Print renders the doc tree under the supplied options and returns the
|
|
99
|
+
// resulting string. The output never contains a trailing line break the
|
|
100
|
+
// doc tree did not request; trailing whitespace inside text fragments
|
|
101
|
+
// is preserved verbatim.
|
|
102
|
+
func Print(doc Doc, opts PrintOptions) string {
|
|
103
|
+
if opts.PrintWidth <= 0 {
|
|
104
|
+
opts.PrintWidth = 80
|
|
105
|
+
}
|
|
106
|
+
if opts.TabWidth <= 0 {
|
|
107
|
+
opts.TabWidth = 2
|
|
108
|
+
}
|
|
109
|
+
if opts.EndOfLine == "" {
|
|
110
|
+
opts.EndOfLine = "lf"
|
|
111
|
+
}
|
|
112
|
+
newline := "\n"
|
|
113
|
+
if opts.EndOfLine == "crlf" {
|
|
114
|
+
newline = "\r\n"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
var out strings.Builder
|
|
118
|
+
col := opts.StartingColumn
|
|
119
|
+
var lineSuffix []Doc
|
|
120
|
+
stack := []printFrame{{indent: opts.BaseIndent, mode: modeBreak, doc: doc}}
|
|
121
|
+
|
|
122
|
+
flushLineSuffix := func() {
|
|
123
|
+
if len(lineSuffix) == 0 {
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
// LineSuffix children must land *before* the newline that
|
|
127
|
+
// triggered the flush. Render them synchronously into the
|
|
128
|
+
// output buffer. LineSuffix payloads are leaf-ish by contract
|
|
129
|
+
// (the canonical use is a trailing `// comment` Text), so a
|
|
130
|
+
// recursive Print is bounded and avoids the lifecycle hazard
|
|
131
|
+
// of pushing onto the main stack — which would queue the
|
|
132
|
+
// docs after the caller has already written the newline,
|
|
133
|
+
// inverting the source order.
|
|
134
|
+
pending := lineSuffix
|
|
135
|
+
lineSuffix = nil
|
|
136
|
+
for _, d := range pending {
|
|
137
|
+
s := Print(d, opts)
|
|
138
|
+
out.WriteString(s)
|
|
139
|
+
if idx := strings.LastIndex(s, "\n"); idx >= 0 {
|
|
140
|
+
col = len(s) - idx - 1
|
|
141
|
+
} else {
|
|
142
|
+
col += len(s)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
writeIndent := func(indent int) int {
|
|
148
|
+
if opts.UseTabs {
|
|
149
|
+
tabs := indent / opts.TabWidth
|
|
150
|
+
spaces := indent - tabs*opts.TabWidth
|
|
151
|
+
for i := 0; i < tabs; i++ {
|
|
152
|
+
out.WriteByte('\t')
|
|
153
|
+
}
|
|
154
|
+
for i := 0; i < spaces; i++ {
|
|
155
|
+
out.WriteByte(' ')
|
|
156
|
+
}
|
|
157
|
+
return tabs*opts.TabWidth + spaces
|
|
158
|
+
}
|
|
159
|
+
for i := 0; i < indent; i++ {
|
|
160
|
+
out.WriteByte(' ')
|
|
161
|
+
}
|
|
162
|
+
return indent
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
for len(stack) > 0 {
|
|
166
|
+
top := stack[len(stack)-1]
|
|
167
|
+
stack = stack[:len(stack)-1]
|
|
168
|
+
|
|
169
|
+
switch top.doc.Kind {
|
|
170
|
+
case docNil:
|
|
171
|
+
// no-op
|
|
172
|
+
case docText:
|
|
173
|
+
out.WriteString(top.doc.Text)
|
|
174
|
+
// Update column. A text fragment may contain embedded
|
|
175
|
+
// newlines (verbatim slices). The column tracker counts
|
|
176
|
+
// from the last newline.
|
|
177
|
+
if idx := strings.LastIndex(top.doc.Text, "\n"); idx >= 0 {
|
|
178
|
+
col = len(top.doc.Text) - idx - 1
|
|
179
|
+
} else {
|
|
180
|
+
col += len(top.doc.Text)
|
|
181
|
+
}
|
|
182
|
+
case docConcat:
|
|
183
|
+
// Push children in reverse so they pop in source order.
|
|
184
|
+
for i := len(top.doc.Children) - 1; i >= 0; i-- {
|
|
185
|
+
stack = append(stack, printFrame{indent: top.indent, mode: top.mode, doc: top.doc.Children[i]})
|
|
186
|
+
}
|
|
187
|
+
case docIndent:
|
|
188
|
+
child := Concat(top.doc.Children...)
|
|
189
|
+
stack = append(stack, printFrame{indent: top.indent + top.doc.Width, mode: top.mode, doc: child})
|
|
190
|
+
case docAlign:
|
|
191
|
+
child := Concat(top.doc.Children...)
|
|
192
|
+
stack = append(stack, printFrame{indent: col, mode: top.mode, doc: child})
|
|
193
|
+
case docGroup:
|
|
194
|
+
// Try flat unless the group contains a hardline.
|
|
195
|
+
child := Concat(top.doc.Children...)
|
|
196
|
+
if fits(child, opts.PrintWidth-col, top.indent) {
|
|
197
|
+
stack = append(stack, printFrame{indent: top.indent, mode: modeFlat, doc: child})
|
|
198
|
+
} else {
|
|
199
|
+
stack = append(stack, printFrame{indent: top.indent, mode: modeBreak, doc: child})
|
|
200
|
+
}
|
|
201
|
+
case docIfBreak:
|
|
202
|
+
pick := top.doc.Children[1] // flat
|
|
203
|
+
if top.mode == modeBreak {
|
|
204
|
+
pick = top.doc.Children[0]
|
|
205
|
+
}
|
|
206
|
+
stack = append(stack, printFrame{indent: top.indent, mode: top.mode, doc: pick})
|
|
207
|
+
case docLineSuffix:
|
|
208
|
+
lineSuffix = append(lineSuffix, top.doc.Children...)
|
|
209
|
+
case docLine:
|
|
210
|
+
if top.mode == modeFlat {
|
|
211
|
+
out.WriteByte(' ')
|
|
212
|
+
col++
|
|
213
|
+
} else {
|
|
214
|
+
flushLineSuffix()
|
|
215
|
+
out.WriteString(newline)
|
|
216
|
+
col = writeIndent(top.indent)
|
|
217
|
+
}
|
|
218
|
+
case docSoftline:
|
|
219
|
+
if top.mode == modeFlat {
|
|
220
|
+
// renders to nothing
|
|
221
|
+
} else {
|
|
222
|
+
flushLineSuffix()
|
|
223
|
+
out.WriteString(newline)
|
|
224
|
+
col = writeIndent(top.indent)
|
|
225
|
+
}
|
|
226
|
+
case docHardline:
|
|
227
|
+
flushLineSuffix()
|
|
228
|
+
out.WriteString(newline)
|
|
229
|
+
col = writeIndent(top.indent)
|
|
230
|
+
case docLiteralline:
|
|
231
|
+
flushLineSuffix()
|
|
232
|
+
out.WriteString(newline)
|
|
233
|
+
col = 0
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Drain any line suffix that never met a break. They flow inline.
|
|
238
|
+
// Keep `col` in sync with what we emit so any future post-drain
|
|
239
|
+
// emission can rely on the invariant; today the drain is the last
|
|
240
|
+
// statement, but the bookkeeping discipline shouldn't depend on
|
|
241
|
+
// that.
|
|
242
|
+
for _, d := range lineSuffix {
|
|
243
|
+
s := Print(d, opts)
|
|
244
|
+
out.WriteString(s)
|
|
245
|
+
if idx := strings.LastIndex(s, "\n"); idx >= 0 {
|
|
246
|
+
col = len(s) - idx - 1
|
|
247
|
+
} else {
|
|
248
|
+
col += len(s)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
_ = col
|
|
252
|
+
|
|
253
|
+
return out.String()
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// fits reports whether `doc` renders flat within `remaining` columns,
|
|
257
|
+
// where the current indent is `indent`. The peek is iterative and
|
|
258
|
+
// stops at the first break-forcing event:
|
|
259
|
+
//
|
|
260
|
+
// - a Hardline / Literalline: the doc cannot render flat.
|
|
261
|
+
// - a Text that overflows: the doc does not fit.
|
|
262
|
+
// - a Line / Softline encountered in *flat* mode: counts as space
|
|
263
|
+
// (Line) or empty (Softline) and the walk continues.
|
|
264
|
+
// - a Line / Softline encountered while the engine has already
|
|
265
|
+
// committed the enclosing group to break mode: the peek returns
|
|
266
|
+
// true immediately because the upstream caller will not place this
|
|
267
|
+
// doc on the current line at all (the newline absorbs the
|
|
268
|
+
// remaining budget).
|
|
269
|
+
//
|
|
270
|
+
// The implementation is a small interpreter that mirrors the main loop,
|
|
271
|
+
// but it only tracks remaining columns and only handles the subset of
|
|
272
|
+
// kinds that matter for measurement.
|
|
273
|
+
func fits(doc Doc, remaining int, indent int) bool {
|
|
274
|
+
if remaining < 0 {
|
|
275
|
+
return false
|
|
276
|
+
}
|
|
277
|
+
type frame struct {
|
|
278
|
+
mode printMode
|
|
279
|
+
doc Doc
|
|
280
|
+
}
|
|
281
|
+
stack := []frame{{mode: modeFlat, doc: doc}}
|
|
282
|
+
for len(stack) > 0 {
|
|
283
|
+
top := stack[len(stack)-1]
|
|
284
|
+
stack = stack[:len(stack)-1]
|
|
285
|
+
switch top.doc.Kind {
|
|
286
|
+
case docNil:
|
|
287
|
+
case docText:
|
|
288
|
+
remaining -= len(top.doc.Text)
|
|
289
|
+
if remaining < 0 {
|
|
290
|
+
return false
|
|
291
|
+
}
|
|
292
|
+
// A Text whose payload spans multiple lines cannot render
|
|
293
|
+
// flat — by definition its flat form is multi-line. Returning
|
|
294
|
+
// true here would let the surrounding group commit to a flat
|
|
295
|
+
// layout that already contains a newline, defeating the entire
|
|
296
|
+
// fit-or-break decision. Force the caller to broken mode.
|
|
297
|
+
if strings.Contains(top.doc.Text, "\n") {
|
|
298
|
+
return false
|
|
299
|
+
}
|
|
300
|
+
case docConcat:
|
|
301
|
+
for i := len(top.doc.Children) - 1; i >= 0; i-- {
|
|
302
|
+
stack = append(stack, frame{mode: top.mode, doc: top.doc.Children[i]})
|
|
303
|
+
}
|
|
304
|
+
case docIndent, docAlign:
|
|
305
|
+
for i := len(top.doc.Children) - 1; i >= 0; i-- {
|
|
306
|
+
stack = append(stack, frame{mode: top.mode, doc: top.doc.Children[i]})
|
|
307
|
+
}
|
|
308
|
+
case docGroup:
|
|
309
|
+
// Measure nested groups in flat mode too — that is the
|
|
310
|
+
// standard Wadler choice: the outer group's "does my
|
|
311
|
+
// flat form fit" question is answered by treating every
|
|
312
|
+
// inner group as flat.
|
|
313
|
+
for i := len(top.doc.Children) - 1; i >= 0; i-- {
|
|
314
|
+
stack = append(stack, frame{mode: modeFlat, doc: top.doc.Children[i]})
|
|
315
|
+
}
|
|
316
|
+
case docIfBreak:
|
|
317
|
+
pick := top.doc.Children[1]
|
|
318
|
+
if top.mode == modeBreak {
|
|
319
|
+
pick = top.doc.Children[0]
|
|
320
|
+
}
|
|
321
|
+
stack = append(stack, frame{mode: top.mode, doc: pick})
|
|
322
|
+
case docLineSuffix:
|
|
323
|
+
// Line suffix never contributes to flat width — it queues
|
|
324
|
+
// for a future break. Skip.
|
|
325
|
+
case docLine:
|
|
326
|
+
if top.mode == modeFlat {
|
|
327
|
+
remaining--
|
|
328
|
+
if remaining < 0 {
|
|
329
|
+
return false
|
|
330
|
+
}
|
|
331
|
+
} else {
|
|
332
|
+
return true
|
|
333
|
+
}
|
|
334
|
+
case docSoftline:
|
|
335
|
+
if top.mode == modeBreak {
|
|
336
|
+
return true
|
|
337
|
+
}
|
|
338
|
+
case docHardline, docLiteralline:
|
|
339
|
+
return false
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return true
|
|
343
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
package linthost
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
// printArrayLiteral renders an ArrayLiteralExpression with width-aware
|
|
8
|
+
// fit-or-break. Like the object printer, elements are emitted verbatim
|
|
9
|
+
// — only the outer brackets and the inter-element commas participate
|
|
10
|
+
// in reflow.
|
|
11
|
+
//
|
|
12
|
+
// Flat: `[1, 2, 3]`
|
|
13
|
+
// Broken: `[
|
|
14
|
+
//
|
|
15
|
+
// 1,
|
|
16
|
+
// 2,
|
|
17
|
+
// 3,
|
|
18
|
+
// ]`
|
|
19
|
+
//
|
|
20
|
+
// Array literals do NOT carry a leading/trailing space inside the
|
|
21
|
+
// brackets in flat mode (`[a, b]`, not `[ a, b ]`), matching every
|
|
22
|
+
// JavaScript formatter. Empty arrays collapse to `[]`.
|
|
23
|
+
func printArrayLiteral(ctx *PrintContext, node *shimast.Node) Doc {
|
|
24
|
+
if node == nil {
|
|
25
|
+
return Doc{}
|
|
26
|
+
}
|
|
27
|
+
arr := node.AsArrayLiteralExpression()
|
|
28
|
+
if arr == nil || arr.Elements == nil {
|
|
29
|
+
return verbatim(ctx, node)
|
|
30
|
+
}
|
|
31
|
+
items := make([]Doc, 0, len(arr.Elements.Nodes))
|
|
32
|
+
for _, elem := range arr.Elements.Nodes {
|
|
33
|
+
if elem == nil {
|
|
34
|
+
return verbatim(ctx, node)
|
|
35
|
+
}
|
|
36
|
+
doc, _ := PrintNode(ctx, elem)
|
|
37
|
+
items = append(items, doc)
|
|
38
|
+
}
|
|
39
|
+
return printList(ctx, listShape{
|
|
40
|
+
OpenTok: "[",
|
|
41
|
+
CloseTok: "]",
|
|
42
|
+
Items: items,
|
|
43
|
+
Space: false,
|
|
44
|
+
AddComma: true,
|
|
45
|
+
})
|
|
46
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
package linthost
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
shimast "github.com/microsoft/typescript-go/shim/ast"
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
// printCallExpression renders a CallExpression with width-aware
|
|
8
|
+
// argument reflow. The callee, optional question token (`?.`) and
|
|
9
|
+
// optional type-argument list are emitted verbatim — only the
|
|
10
|
+
// argument list participates in reflow.
|
|
11
|
+
//
|
|
12
|
+
// Flat: `foo(a, b, c)`
|
|
13
|
+
// Broken: `foo(
|
|
14
|
+
//
|
|
15
|
+
// a,
|
|
16
|
+
// b,
|
|
17
|
+
// c,
|
|
18
|
+
// )`
|
|
19
|
+
//
|
|
20
|
+
// Type arguments (`foo<A, B>(x)`) are preserved verbatim. Trailing
|
|
21
|
+
// commas on type arguments are intentionally avoided — Prettier omits
|
|
22
|
+
// them too (see prettier#10353).
|
|
23
|
+
func printCallExpression(ctx *PrintContext, node *shimast.Node) Doc {
|
|
24
|
+
if node == nil {
|
|
25
|
+
return Doc{}
|
|
26
|
+
}
|
|
27
|
+
call := node.AsCallExpression()
|
|
28
|
+
if call == nil {
|
|
29
|
+
return verbatim(ctx, node)
|
|
30
|
+
}
|
|
31
|
+
parts := []Doc{}
|
|
32
|
+
if call.Expression != nil {
|
|
33
|
+
parts = append(parts, verbatim(ctx, call.Expression))
|
|
34
|
+
}
|
|
35
|
+
// Question-dot for optional call: `foo?.(x)`. The token byte range
|
|
36
|
+
// lives between Expression.End() and the open paren; copy
|
|
37
|
+
// verbatim if present.
|
|
38
|
+
if call.QuestionDotToken != nil {
|
|
39
|
+
parts = append(parts, verbatim(ctx, call.QuestionDotToken))
|
|
40
|
+
}
|
|
41
|
+
if call.TypeArguments != nil {
|
|
42
|
+
// Verbatim range covering `<A, B>` punctuation and members.
|
|
43
|
+
parts = append(parts, verbatimRange(ctx.Source, callTypeArgsStart(ctx, call), callTypeArgsEnd(ctx, call)))
|
|
44
|
+
}
|
|
45
|
+
if hasNilEntry(call.Arguments) {
|
|
46
|
+
return verbatim(ctx, node)
|
|
47
|
+
}
|
|
48
|
+
parts = append(parts, printArgList(ctx, call.Arguments))
|
|
49
|
+
return Concat(parts...)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// printNewExpression renders a NewExpression. It mirrors the call
|
|
53
|
+
// expression printer; the only difference is the leading `new ` keyword
|
|
54
|
+
// and the optional argument list (NewExpression may omit args entirely,
|
|
55
|
+
// e.g. `new Foo`).
|
|
56
|
+
func printNewExpression(ctx *PrintContext, node *shimast.Node) Doc {
|
|
57
|
+
if node == nil {
|
|
58
|
+
return Doc{}
|
|
59
|
+
}
|
|
60
|
+
ne := node.AsNewExpression()
|
|
61
|
+
if ne == nil {
|
|
62
|
+
return verbatim(ctx, node)
|
|
63
|
+
}
|
|
64
|
+
parts := []Doc{Text("new ")}
|
|
65
|
+
if ne.Expression != nil {
|
|
66
|
+
parts = append(parts, verbatim(ctx, ne.Expression))
|
|
67
|
+
}
|
|
68
|
+
if ne.TypeArguments != nil {
|
|
69
|
+
parts = append(parts, verbatimRange(ctx.Source, newTypeArgsStart(ctx, ne), newTypeArgsEnd(ctx, ne)))
|
|
70
|
+
}
|
|
71
|
+
if ne.Arguments != nil {
|
|
72
|
+
if hasNilEntry(ne.Arguments) {
|
|
73
|
+
return verbatim(ctx, node)
|
|
74
|
+
}
|
|
75
|
+
parts = append(parts, printArgList(ctx, ne.Arguments))
|
|
76
|
+
}
|
|
77
|
+
return Concat(parts...)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// hasNilEntry reports whether any entry of `list` is a nil pointer.
|
|
81
|
+
// Per-node printers consult this before delegating to printArgList /
|
|
82
|
+
// printList — a nil child would render as an empty Doc and produce
|
|
83
|
+
// `(a, , b)` in the output. Bailing to verbatim is byte-safe.
|
|
84
|
+
func hasNilEntry(list *shimast.NodeList) bool {
|
|
85
|
+
if list == nil {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
for _, n := range list.Nodes {
|
|
89
|
+
if n == nil {
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// printArgList renders an argument node list. The shared printList
|
|
97
|
+
// handles the open-comma-close shape; this helper just gathers the
|
|
98
|
+
// per-argument docs.
|
|
99
|
+
func printArgList(ctx *PrintContext, list *shimast.NodeList) Doc {
|
|
100
|
+
if list == nil {
|
|
101
|
+
return Text("()")
|
|
102
|
+
}
|
|
103
|
+
items := make([]Doc, 0, len(list.Nodes))
|
|
104
|
+
for _, arg := range list.Nodes {
|
|
105
|
+
doc, _ := PrintNode(ctx, arg)
|
|
106
|
+
items = append(items, doc)
|
|
107
|
+
}
|
|
108
|
+
return printList(ctx, listShape{
|
|
109
|
+
OpenTok: "(",
|
|
110
|
+
CloseTok: ")",
|
|
111
|
+
Items: items,
|
|
112
|
+
Space: false,
|
|
113
|
+
AddComma: true,
|
|
114
|
+
})
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Type-argument byte-range helpers. The shim's NodeList.End() points
|
|
118
|
+
// past the last argument; the surrounding `<` and `>` are not part of
|
|
119
|
+
// the list's range, so we have to scan around it.
|
|
120
|
+
func callTypeArgsStart(ctx *PrintContext, call *shimast.CallExpression) int {
|
|
121
|
+
if call.TypeArguments == nil || len(call.TypeArguments.Nodes) == 0 {
|
|
122
|
+
return -1
|
|
123
|
+
}
|
|
124
|
+
first := call.TypeArguments.Nodes[0]
|
|
125
|
+
if first == nil {
|
|
126
|
+
return -1
|
|
127
|
+
}
|
|
128
|
+
// `<` is the byte immediately before the first type argument
|
|
129
|
+
// (modulo whitespace).
|
|
130
|
+
pos := first.Pos()
|
|
131
|
+
for i := pos - 1; i >= 0; i-- {
|
|
132
|
+
if ctx.Source[i] == '<' {
|
|
133
|
+
return i
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return -1
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
func callTypeArgsEnd(ctx *PrintContext, call *shimast.CallExpression) int {
|
|
140
|
+
if call.TypeArguments == nil {
|
|
141
|
+
return -1
|
|
142
|
+
}
|
|
143
|
+
end := call.TypeArguments.End()
|
|
144
|
+
for i := end; i < len(ctx.Source); i++ {
|
|
145
|
+
if ctx.Source[i] == '>' {
|
|
146
|
+
return i + 1
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return end
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
func newTypeArgsStart(ctx *PrintContext, ne *shimast.NewExpression) int {
|
|
153
|
+
if ne.TypeArguments == nil || len(ne.TypeArguments.Nodes) == 0 {
|
|
154
|
+
return -1
|
|
155
|
+
}
|
|
156
|
+
first := ne.TypeArguments.Nodes[0]
|
|
157
|
+
if first == nil {
|
|
158
|
+
return -1
|
|
159
|
+
}
|
|
160
|
+
pos := first.Pos()
|
|
161
|
+
for i := pos - 1; i >= 0; i-- {
|
|
162
|
+
if ctx.Source[i] == '<' {
|
|
163
|
+
return i
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return -1
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
func newTypeArgsEnd(ctx *PrintContext, ne *shimast.NewExpression) int {
|
|
170
|
+
if ne.TypeArguments == nil {
|
|
171
|
+
return -1
|
|
172
|
+
}
|
|
173
|
+
end := ne.TypeArguments.End()
|
|
174
|
+
for i := end; i < len(ctx.Source); i++ {
|
|
175
|
+
if ctx.Source[i] == '>' {
|
|
176
|
+
return i + 1
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return end
|
|
180
|
+
}
|