@ttsc/lint 0.25.0 → 0.26.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/structures/rules/ITtscLintFunctionalRuleOptions.d.ts +73 -16
- package/linthost/config_format.go +7 -0
- package/linthost/directives.go +4 -7
- package/linthost/fix.go +6 -4
- package/linthost/rule_codes.json +1 -0
- package/linthost/rules_format_brace_continuation.go +267 -0
- package/linthost/rules_format_clause_join.go +289 -40
- package/linthost/rules_format_parameter_properties.go +9 -21
- package/linthost/rules_format_whitespace.go +5 -1
- package/linthost/rules_functional.go +248 -25
- package/linthost/rules_ts_extra.go +7 -5
- package/package.json +2 -2
- package/rule/rule.go +21 -9
- package/src/structures/rules/ITtscLintFunctionalRuleOptions.ts +73 -16
|
@@ -13,16 +13,38 @@ import (
|
|
|
13
13
|
// if (a)
|
|
14
14
|
// b();
|
|
15
15
|
//
|
|
16
|
-
// becomes `if (a) b();` when the joined line fits printWidth. The
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
16
|
+
// becomes `if (a) b();` when the joined line fits printWidth. The covered set
|
|
17
|
+
// is Prettier's, not the set whose header happens to end in `)`: the `if`
|
|
18
|
+
// consequent and alternate, the `for`, `for-in`, `for-of`, and `while` bodies,
|
|
19
|
+
// the `do` body, the `with` body, and a labeled statement's body. A braced
|
|
20
|
+
// body, a body that already shares the header line, a multi-line body, or a
|
|
21
|
+
// join that would overflow printWidth is left untouched.
|
|
20
22
|
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// and the
|
|
23
|
+
// Two clauses join unconditionally because Prettier gives them no group of
|
|
24
|
+
// their own: an `else` whose alternate is another `if` (the `else if` chain),
|
|
25
|
+
// and a labeled statement, which prints `label: statement` on one line whatever
|
|
26
|
+
// the statement is, braces included. Those are the only clauses that can hoist a
|
|
27
|
+
// multi-line body, and their continuation lines move by the same column delta
|
|
28
|
+
// their first line travels, so the join does not settle on output Prettier would
|
|
29
|
+
// still reindent.
|
|
30
|
+
//
|
|
31
|
+
// A continuation line whose newline sits inside a string, a template, or a
|
|
32
|
+
// block comment is skipped rather than shifted: those bytes are content, and
|
|
33
|
+
// moving them would change what the program prints or what a comment says. The
|
|
34
|
+
// join itself still happens, because abandoning it leaves `format/indent` to
|
|
35
|
+
// move the body's interior anyway and settles the file on a hybrid layout
|
|
36
|
+
// Prettier never emits.
|
|
37
|
+
//
|
|
38
|
+
// The rule rewrites the whitespace gap between the clause's own header token
|
|
39
|
+
// and the controlled statement, and, when it hoists a multi-line body, the
|
|
40
|
+
// leading whitespace of that body's continuation lines. That surface overlaps
|
|
41
|
+
// `format/indent`, which owns the same column for a hoisted body that contains
|
|
42
|
+
// a block and cedes it only for a braceless control-flow body. The two agree on
|
|
43
|
+
// the target column, so a collision costs a dropped finding and a re-fire rather
|
|
44
|
+
// than a fight. Hoisting also contends with a nested join over the same bytes,
|
|
45
|
+
// so a staircase settles roughly one level per cascade pass. Idempotent: once
|
|
46
|
+
// joined the gap holds no newline, the shift compares against the text it would
|
|
47
|
+
// write, and the rule abstains.
|
|
26
48
|
type formatClauseJoin struct{ optionsRule }
|
|
27
49
|
|
|
28
50
|
// formatClauseJoinOptions mirrors the printWidth/indent keys the rule
|
|
@@ -46,6 +68,9 @@ func (formatClauseJoin) Visits() []shimast.Kind {
|
|
|
46
68
|
shimast.KindForStatement,
|
|
47
69
|
shimast.KindForInStatement,
|
|
48
70
|
shimast.KindForOfStatement,
|
|
71
|
+
shimast.KindDoStatement,
|
|
72
|
+
shimast.KindWithStatement,
|
|
73
|
+
shimast.KindLabeledStatement,
|
|
49
74
|
}
|
|
50
75
|
}
|
|
51
76
|
|
|
@@ -63,24 +88,67 @@ func (formatClauseJoin) Check(ctx *Context, node *shimast.Node) {
|
|
|
63
88
|
if opts.TabWidth != nil && *opts.TabWidth > 0 {
|
|
64
89
|
tabWidth = *opts.TabWidth
|
|
65
90
|
}
|
|
66
|
-
|
|
91
|
+
src := ctx.File.Text()
|
|
92
|
+
for _, target := range clauseControlledBodies(node) {
|
|
93
|
+
joinClauseBody(ctx, src, node, target, printWidth, tabWidth)
|
|
94
|
+
}
|
|
67
95
|
}
|
|
68
96
|
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
// the
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
|
|
97
|
+
// clauseJoinTarget is one joinable clause body plus the token that ends the
|
|
98
|
+
// clause header immediately before it. The anchor is what generalizes the rule
|
|
99
|
+
// past the `)`-headed statements: `else` and `do` end in a keyword, a labeled
|
|
100
|
+
// statement ends in `:`, and requiring the exact token still keeps a comment
|
|
101
|
+
// between header and body from being swallowed.
|
|
102
|
+
type clauseJoinTarget struct {
|
|
103
|
+
body *shimast.Node
|
|
104
|
+
anchor string
|
|
105
|
+
// alwaysJoin marks a clause Prettier keeps on the header line regardless of
|
|
106
|
+
// the body's own line count or the joined width: an `else if` chain and a
|
|
107
|
+
// labeled statement.
|
|
108
|
+
alwaysJoin bool
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// clauseControlledBodies returns every clause body of `node` that Prettier
|
|
112
|
+
// keeps on its header line, paired with the token that header ends in. The set
|
|
113
|
+
// is Prettier's, not the AST's: the `if` consequent and alternate, the loop
|
|
114
|
+
// bodies, the `do` body, the `with` body, and a labeled statement's body.
|
|
115
|
+
func clauseControlledBodies(node *shimast.Node) []clauseJoinTarget {
|
|
75
116
|
switch node.Kind {
|
|
76
117
|
case shimast.KindIfStatement:
|
|
77
|
-
|
|
118
|
+
stmt := node.AsIfStatement()
|
|
119
|
+
if stmt == nil {
|
|
120
|
+
return nil
|
|
121
|
+
}
|
|
122
|
+
targets := []clauseJoinTarget{{body: stmt.ThenStatement, anchor: ")"}}
|
|
123
|
+
if stmt.ElseStatement != nil {
|
|
124
|
+
targets = append(targets, clauseJoinTarget{
|
|
125
|
+
body: stmt.ElseStatement,
|
|
126
|
+
anchor: "else",
|
|
127
|
+
// Prettier prints an `else if` chain flat, so the alternate being an
|
|
128
|
+
// `if` joins even when that nested statement spans lines.
|
|
129
|
+
alwaysJoin: stmt.ElseStatement.Kind == shimast.KindIfStatement,
|
|
130
|
+
})
|
|
131
|
+
}
|
|
132
|
+
return targets
|
|
78
133
|
case shimast.KindWhileStatement:
|
|
79
|
-
return node.AsWhileStatement().Statement
|
|
134
|
+
return []clauseJoinTarget{{body: node.AsWhileStatement().Statement, anchor: ")"}}
|
|
80
135
|
case shimast.KindForStatement:
|
|
81
|
-
return node.AsForStatement().Statement
|
|
136
|
+
return []clauseJoinTarget{{body: node.AsForStatement().Statement, anchor: ")"}}
|
|
82
137
|
case shimast.KindForInStatement, shimast.KindForOfStatement:
|
|
83
|
-
return node.AsForInOrOfStatement().Statement
|
|
138
|
+
return []clauseJoinTarget{{body: node.AsForInOrOfStatement().Statement, anchor: ")"}}
|
|
139
|
+
case shimast.KindDoStatement:
|
|
140
|
+
return []clauseJoinTarget{{body: node.AsDoStatement().Statement, anchor: "do"}}
|
|
141
|
+
case shimast.KindWithStatement:
|
|
142
|
+
return []clauseJoinTarget{{body: node.AsWithStatement().Statement, anchor: ")"}}
|
|
143
|
+
case shimast.KindLabeledStatement:
|
|
144
|
+
// A label carries no group of its own in Prettier: `label: statement` is
|
|
145
|
+
// one line whatever the statement is, so the body may be a block and may
|
|
146
|
+
// span lines.
|
|
147
|
+
return []clauseJoinTarget{{
|
|
148
|
+
body: node.AsLabeledStatement().Statement,
|
|
149
|
+
anchor: ":",
|
|
150
|
+
alwaysJoin: true,
|
|
151
|
+
}}
|
|
84
152
|
}
|
|
85
153
|
return nil
|
|
86
154
|
}
|
|
@@ -89,18 +157,25 @@ func joinClauseBody(
|
|
|
89
157
|
ctx *Context,
|
|
90
158
|
src string,
|
|
91
159
|
node *shimast.Node,
|
|
92
|
-
|
|
160
|
+
target clauseJoinTarget,
|
|
93
161
|
printWidth int,
|
|
94
162
|
tabWidth int,
|
|
95
163
|
) {
|
|
96
|
-
|
|
164
|
+
body := target.body
|
|
165
|
+
if body == nil {
|
|
97
166
|
return
|
|
98
167
|
}
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
168
|
+
// A braced body already owns its own line layout, except behind a label,
|
|
169
|
+
// where Prettier writes `label: {`.
|
|
170
|
+
if body.Kind == shimast.KindBlock && !target.alwaysJoin {
|
|
171
|
+
return
|
|
172
|
+
}
|
|
173
|
+
// An empty-statement body glues directly to the header with NO space:
|
|
174
|
+
// Prettier's adjustClause special-cases EmptyStatement and returns the bare
|
|
175
|
+
// `;` (`while (x);`, `else;`, `do;`, `label:;`), only prepending a space when
|
|
176
|
+
// the empty statement carries a leading comment. This rule joins with a
|
|
177
|
+
// single space and deliberately does not take on the spaceless variant, so it
|
|
178
|
+
// abstains and leaves the source shape rather than emitting `while (x) ;`.
|
|
104
179
|
if body.Kind == shimast.KindEmptyStatement {
|
|
105
180
|
return
|
|
106
181
|
}
|
|
@@ -110,24 +185,29 @@ func joinClauseBody(
|
|
|
110
185
|
return
|
|
111
186
|
}
|
|
112
187
|
// The gap is the whitespace run immediately before the body. Walk back
|
|
113
|
-
// over horizontal whitespace and newlines; the
|
|
114
|
-
//
|
|
188
|
+
// over horizontal whitespace and newlines; the bytes before it must be the
|
|
189
|
+
// clause's own header token so a comment between header and body (which
|
|
115
190
|
// SkipTrivia would have stepped over) can never be swallowed.
|
|
116
191
|
gapStart := bodyStart
|
|
117
192
|
for gapStart > 0 && isClauseGapByte(src[gapStart-1]) {
|
|
118
193
|
gapStart--
|
|
119
194
|
}
|
|
120
|
-
|
|
195
|
+
anchorStart := gapStart - len(target.anchor)
|
|
196
|
+
if anchorStart < 0 || src[anchorStart:gapStart] != target.anchor {
|
|
121
197
|
return
|
|
122
198
|
}
|
|
123
199
|
gap := src[gapStart:bodyStart]
|
|
124
200
|
if !strings.Contains(gap, "\n") {
|
|
125
201
|
return // body already shares the header line
|
|
126
202
|
}
|
|
127
|
-
// The header
|
|
128
|
-
//
|
|
129
|
-
//
|
|
203
|
+
// The header line is measured from the token that opens the clause. For a
|
|
204
|
+
// `)`-headed statement and a label that is the statement's own start; for
|
|
205
|
+
// `else` and `do` it is the keyword, which is where Prettier starts the line
|
|
206
|
+
// it would print.
|
|
130
207
|
headerStart := shimscanner.SkipTrivia(src, node.Pos())
|
|
208
|
+
if isClauseAnchorWord(target.anchor) {
|
|
209
|
+
headerStart = anchorStart
|
|
210
|
+
}
|
|
131
211
|
if headerStart < 0 || headerStart > gapStart {
|
|
132
212
|
return
|
|
133
213
|
}
|
|
@@ -135,28 +215,197 @@ func joinClauseBody(
|
|
|
135
215
|
if strings.ContainsRune(src[headerLineStart:gapStart], '\n') {
|
|
136
216
|
return
|
|
137
217
|
}
|
|
138
|
-
if
|
|
139
|
-
|
|
218
|
+
if !target.alwaysJoin {
|
|
219
|
+
// The body must be single-line; a multi-line body (e.g. a nested clause
|
|
220
|
+
// not yet joined) waits for the cascade to settle its inner join first.
|
|
221
|
+
if strings.ContainsRune(src[bodyStart:bodyEnd], '\n') {
|
|
222
|
+
return
|
|
223
|
+
}
|
|
224
|
+
joined := visualWidth(src[headerLineStart:gapStart], tabWidth) + 1 +
|
|
225
|
+
visualWidth(src[bodyStart:bodyEnd], tabWidth)
|
|
226
|
+
if joined > printWidth {
|
|
227
|
+
return
|
|
228
|
+
}
|
|
140
229
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
230
|
+
edits := []TextEdit{{Pos: gapStart, End: bodyStart, Text: " "}}
|
|
231
|
+
// Hoisting the body's first line moves its base column. Every continuation
|
|
232
|
+
// line has to move with it, or the join settles on output Prettier would
|
|
233
|
+
// still reindent (#1139). Only an alwaysJoin clause can reach a multi-line
|
|
234
|
+
// body; the others already abstained above.
|
|
235
|
+
if strings.ContainsRune(src[bodyStart:bodyEnd], '\n') {
|
|
236
|
+
edits = append(edits, reindentJoinedClauseBody(
|
|
237
|
+
ctx, src, headerLineStart, bodyStart, bodyEnd, loadFormatLayout(ctx),
|
|
238
|
+
)...)
|
|
145
239
|
}
|
|
146
240
|
ctx.ReportRangeFix(
|
|
147
241
|
gapStart,
|
|
148
242
|
bodyStart,
|
|
149
243
|
"Single-statement clause body should join its header line.",
|
|
150
|
-
|
|
244
|
+
edits...,
|
|
151
245
|
)
|
|
152
246
|
}
|
|
153
247
|
|
|
248
|
+
// reindentJoinedClauseBody returns the edits that move a hoisted body's
|
|
249
|
+
// continuation lines by the same column delta its first line travels.
|
|
250
|
+
//
|
|
251
|
+
// The delta is the header line's own indent minus the indent of the line the
|
|
252
|
+
// body currently starts on, because the body's first line lands on the header
|
|
253
|
+
// line and Prettier prints its continuations relative to that. Measuring from
|
|
254
|
+
// the anchor token instead would charge the width of a `} ` prefix and shift
|
|
255
|
+
// every continuation line two columns too far.
|
|
256
|
+
//
|
|
257
|
+
// A line the shift must not touch is skipped, not made to abandon the join.
|
|
258
|
+
// Abandoning is strictly worse: `format/indent` still moves the body's interior
|
|
259
|
+
// to its post-join column, so the file settles on a hybrid layout Prettier never
|
|
260
|
+
// emits, which is the very property this shift exists to remove. Three kinds of
|
|
261
|
+
// line are skipped: one whose newline sits inside a string, template, or block
|
|
262
|
+
// comment, where the bytes are content rather than layout; a blank one, which
|
|
263
|
+
// has no column; and one whose indent is shorter than an outdent needs.
|
|
264
|
+
func reindentJoinedClauseBody(
|
|
265
|
+
ctx *Context,
|
|
266
|
+
src string,
|
|
267
|
+
headerLineStart int,
|
|
268
|
+
bodyStart int,
|
|
269
|
+
bodyEnd int,
|
|
270
|
+
layout formatLayout,
|
|
271
|
+
) []TextEdit {
|
|
272
|
+
protected := collectClauseJoinProtectedRanges(ctx.File, src)
|
|
273
|
+
headerIndentEnd := headerLineStart
|
|
274
|
+
for headerIndentEnd < len(src) &&
|
|
275
|
+
(src[headerIndentEnd] == ' ' || src[headerIndentEnd] == '\t') {
|
|
276
|
+
headerIndentEnd++
|
|
277
|
+
}
|
|
278
|
+
bodyLineStart := lineStartOffset(src, bodyStart)
|
|
279
|
+
delta := visualWidth(src[headerLineStart:headerIndentEnd], layout.tabWidth) -
|
|
280
|
+
visualWidth(src[bodyLineStart:bodyStart], layout.tabWidth)
|
|
281
|
+
if delta == 0 {
|
|
282
|
+
return nil
|
|
283
|
+
}
|
|
284
|
+
var edits []TextEdit
|
|
285
|
+
for offset := bodyStart; offset < bodyEnd; offset++ {
|
|
286
|
+
if src[offset] != '\n' {
|
|
287
|
+
continue
|
|
288
|
+
}
|
|
289
|
+
if inTemplate(protected, offset) {
|
|
290
|
+
continue
|
|
291
|
+
}
|
|
292
|
+
lineStart := offset + 1
|
|
293
|
+
indentEnd := lineStart
|
|
294
|
+
for indentEnd < bodyEnd && (src[indentEnd] == ' ' || src[indentEnd] == '\t') {
|
|
295
|
+
indentEnd++
|
|
296
|
+
}
|
|
297
|
+
if indentEnd >= bodyEnd || src[indentEnd] == '\n' || src[indentEnd] == '\r' {
|
|
298
|
+
continue // a blank line carries no column
|
|
299
|
+
}
|
|
300
|
+
width := visualWidth(src[lineStart:indentEnd], layout.tabWidth) + delta
|
|
301
|
+
if width < 0 {
|
|
302
|
+
continue
|
|
303
|
+
}
|
|
304
|
+
next := clauseJoinIndentOfWidth(layout, width)
|
|
305
|
+
if src[lineStart:indentEnd] == next {
|
|
306
|
+
continue
|
|
307
|
+
}
|
|
308
|
+
edits = append(edits, TextEdit{Pos: lineStart, End: indentEnd, Text: next})
|
|
309
|
+
}
|
|
310
|
+
return edits
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// clauseJoinIndentOfWidth renders a column count in the project's own
|
|
314
|
+
// indentation unit, so a tab-indented file keeps its tabs instead of being
|
|
315
|
+
// silently respaced by the shift.
|
|
316
|
+
func clauseJoinIndentOfWidth(layout formatLayout, width int) string {
|
|
317
|
+
if width <= 0 {
|
|
318
|
+
return ""
|
|
319
|
+
}
|
|
320
|
+
if layout.useTabs && layout.tabWidth > 0 {
|
|
321
|
+
// A width that is not a whole number of tabs keeps the remainder in spaces
|
|
322
|
+
// rather than dropping every tab, which is the respacing this render exists
|
|
323
|
+
// to prevent.
|
|
324
|
+
return layout.indent(width/layout.tabWidth) +
|
|
325
|
+
strings.Repeat(" ", width%layout.tabWidth)
|
|
326
|
+
}
|
|
327
|
+
return strings.Repeat(" ", width)
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// collectClauseJoinProtectedRanges returns every byte span whose interior
|
|
331
|
+
// newlines carry content rather than layout: template literals, string literals
|
|
332
|
+
// spanning a line continuation, and block comments. Shifting a line inside one
|
|
333
|
+
// changes what the program prints or what a comment says.
|
|
334
|
+
func collectClauseJoinProtectedRanges(file *shimast.SourceFile, src string) []byteRange {
|
|
335
|
+
ranges := collectTemplateRanges(file, src)
|
|
336
|
+
var walk func(node *shimast.Node)
|
|
337
|
+
walk = func(node *shimast.Node) {
|
|
338
|
+
if node == nil {
|
|
339
|
+
return
|
|
340
|
+
}
|
|
341
|
+
if node.Kind == shimast.KindStringLiteral {
|
|
342
|
+
pos := shimscanner.SkipTrivia(src, node.Pos())
|
|
343
|
+
end := node.End()
|
|
344
|
+
if pos >= 0 && end <= len(src) && end > pos {
|
|
345
|
+
ranges = append(ranges, byteRange{pos: pos, end: end})
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
node.ForEachChild(func(child *shimast.Node) bool {
|
|
349
|
+
walk(child)
|
|
350
|
+
return false
|
|
351
|
+
})
|
|
352
|
+
}
|
|
353
|
+
if file != nil && file.Statements != nil {
|
|
354
|
+
for _, stmt := range file.Statements.Nodes {
|
|
355
|
+
walk(stmt)
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
forEachCommentToken(file, func(kind shimast.Kind, start, end int) {
|
|
359
|
+
if kind != shimast.KindMultiLineCommentTrivia {
|
|
360
|
+
return
|
|
361
|
+
}
|
|
362
|
+
// Only a comment Prettier reprints verbatim is content. An indentable one,
|
|
363
|
+
// every continuation line starting with `*`, is realigned by Prettier to the
|
|
364
|
+
// current indentation, so protecting it would leave a well-formed JSDoc
|
|
365
|
+
// block misaligned behind a hoisted body.
|
|
366
|
+
if isIndentableBlockComment(src, start, end) {
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
ranges = append(ranges, byteRange{pos: start, end: end})
|
|
370
|
+
})
|
|
371
|
+
return ranges
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// isIndentableBlockComment reports whether every continuation line of a block
|
|
375
|
+
// comment begins with `*`, which is the shape Prettier reindents rather than
|
|
376
|
+
// reproducing byte for byte.
|
|
377
|
+
func isIndentableBlockComment(src string, start, end int) bool {
|
|
378
|
+
if start < 0 || end > len(src) || end <= start {
|
|
379
|
+
return false
|
|
380
|
+
}
|
|
381
|
+
body := src[start:end]
|
|
382
|
+
if !strings.Contains(body, "\n") {
|
|
383
|
+
return false
|
|
384
|
+
}
|
|
385
|
+
for _, line := range strings.Split(body, "\n")[1:] {
|
|
386
|
+
trimmed := strings.TrimLeft(line, " \t\r")
|
|
387
|
+
if !strings.HasPrefix(trimmed, "*") {
|
|
388
|
+
return false
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return true
|
|
392
|
+
}
|
|
393
|
+
|
|
154
394
|
// isClauseGapByte reports whether `c` is whitespace that may appear in
|
|
155
395
|
// the gap between a clause header and its controlled statement.
|
|
156
396
|
func isClauseGapByte(c byte) bool {
|
|
157
397
|
return c == ' ' || c == '\t' || c == '\r' || c == '\n'
|
|
158
398
|
}
|
|
159
399
|
|
|
400
|
+
// isClauseAnchorWord reports whether an anchor is a keyword rather than
|
|
401
|
+
// punctuation, which decides where the width budget starts measuring. The
|
|
402
|
+
// anchor needs no identifier-boundary check: it is read backward from the AST
|
|
403
|
+
// body's own position, so the token before the gap is the clause's real keyword
|
|
404
|
+
// and an identifier ending in `else` or `do` cannot reach it.
|
|
405
|
+
func isClauseAnchorWord(anchor string) bool {
|
|
406
|
+
return anchor == "else" || anchor == "do"
|
|
407
|
+
}
|
|
408
|
+
|
|
160
409
|
// visualWidth returns the display-column width of `s`: a tab expands to a flat
|
|
161
410
|
// `tabWidth` columns and everything else is charged by displayWidth, which is
|
|
162
411
|
// Prettier's own measurement. The only approximation left is the flat tab
|
|
@@ -10,9 +10,8 @@ import (
|
|
|
10
10
|
// formatParameterProperties breaks a constructor's parameter list onto
|
|
11
11
|
// one-parameter-per-line when it declares parameter properties, matching
|
|
12
12
|
// Prettier 3. Prettier forces the break whenever a constructor has more
|
|
13
|
-
// than one parameter and at least one carries
|
|
14
|
-
//
|
|
15
|
-
// flat form fits printWidth:
|
|
13
|
+
// than one parameter and at least one carries a parameter-property
|
|
14
|
+
// modifier, regardless of whether the flat form fits printWidth:
|
|
16
15
|
//
|
|
17
16
|
// constructor(
|
|
18
17
|
// private readonly repo: Repository,
|
|
@@ -155,26 +154,15 @@ func (formatParameterProperties) Check(ctx *Context, node *shimast.Node) {
|
|
|
155
154
|
)
|
|
156
155
|
}
|
|
157
156
|
|
|
158
|
-
// anyParameterProperty reports whether any parameter
|
|
159
|
-
//
|
|
160
|
-
//
|
|
157
|
+
// anyParameterProperty reports whether any parameter is a parameter property.
|
|
158
|
+
// It defers to the package's shared `isParameterProperty` so one definition
|
|
159
|
+
// answers the question for every rule that asks it; restating the modifier set
|
|
160
|
+
// here is what left `override` out and made this rule abstain on a legal
|
|
161
|
+
// parameter property Prettier breaks.
|
|
161
162
|
func anyParameterProperty(params []*shimast.Node) bool {
|
|
162
163
|
for _, p := range params {
|
|
163
|
-
if p
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
mods := p.Modifiers()
|
|
167
|
-
if mods == nil {
|
|
168
|
-
continue
|
|
169
|
-
}
|
|
170
|
-
for _, m := range mods.Nodes {
|
|
171
|
-
switch m.Kind {
|
|
172
|
-
case shimast.KindPublicKeyword,
|
|
173
|
-
shimast.KindPrivateKeyword,
|
|
174
|
-
shimast.KindProtectedKeyword,
|
|
175
|
-
shimast.KindReadonlyKeyword:
|
|
176
|
-
return true
|
|
177
|
-
}
|
|
164
|
+
if isParameterProperty(p) {
|
|
165
|
+
return true
|
|
178
166
|
}
|
|
179
167
|
}
|
|
180
168
|
return false
|
|
@@ -311,7 +311,11 @@ func collectTemplateRanges(file *shimast.SourceFile, src string) []byteRange {
|
|
|
311
311
|
return
|
|
312
312
|
}
|
|
313
313
|
switch node.Kind {
|
|
314
|
-
case shimast.KindNoSubstitutionTemplateLiteral,
|
|
314
|
+
case shimast.KindNoSubstitutionTemplateLiteral,
|
|
315
|
+
shimast.KindTemplateExpression,
|
|
316
|
+
// A template literal TYPE spans head plus spans like the expression form
|
|
317
|
+
// and its newlines are equally part of the declared type's text.
|
|
318
|
+
shimast.KindTemplateLiteralType:
|
|
315
319
|
pos := shimscanner.SkipTrivia(src, node.Pos())
|
|
316
320
|
end := node.End()
|
|
317
321
|
if pos >= 0 && end <= len(src) && end > pos {
|