@ttsc/lint 0.28.4 → 0.28.6
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/linthost/check_serve.go +7 -6
- package/linthost/compile.go +50 -44
- package/linthost/fix.go +8 -7
- package/linthost/host.go +24 -3
- package/linthost/lsp.go +16 -14
- package/linthost/rules_format_print_width.go +38 -0
- package/linthost/serve.go +8 -6
- package/package.json +2 -2
package/linthost/check_serve.go
CHANGED
|
@@ -220,12 +220,13 @@ func (s *residentCheckState) run(
|
|
|
220
220
|
opts.cwd,
|
|
221
221
|
opts.tsconfig,
|
|
222
222
|
loadProgramOptions{
|
|
223
|
-
forceNoEmit:
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
223
|
+
forceNoEmit: true,
|
|
224
|
+
semanticConfigPath: opts.semanticConfigPath,
|
|
225
|
+
needsRuleChecker: engine.NeedsTypeChecker(),
|
|
226
|
+
singleThreaded: opts.singleThreaded,
|
|
227
|
+
checkers: opts.checkers,
|
|
228
|
+
tsgoArgs: opts.tsgoArgs,
|
|
229
|
+
projectIdentity: opts.projectIdentity,
|
|
229
230
|
},
|
|
230
231
|
)
|
|
231
232
|
if err != nil {
|
package/linthost/compile.go
CHANGED
|
@@ -74,6 +74,7 @@ func RunTransform(args []string) int {
|
|
|
74
74
|
|
|
75
75
|
// RunTransformWithIO runs transform with invocation-owned output streams.
|
|
76
76
|
func RunTransformWithIO(args []string, stdout, stderr io.Writer) int {
|
|
77
|
+
semanticConfigPath := os.Getenv(semanticConfigPathEnv)
|
|
77
78
|
fs := flag.NewFlagSet("transform", flag.ContinueOnError)
|
|
78
79
|
fs.SetOutput(stderr)
|
|
79
80
|
file := fs.String("file", "", "absolute or cwd-relative path of the .ts file to transform")
|
|
@@ -122,12 +123,13 @@ func RunTransformWithIO(args []string, stdout, stderr io.Writer) int {
|
|
|
122
123
|
engine.SetSerial(*singleThreaded)
|
|
123
124
|
|
|
124
125
|
prog, parseDiags, err := loadProgram(resolvedCwd, *tsconfig, loadProgramOptions{
|
|
125
|
-
forceEmit:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
126
|
+
forceEmit: true,
|
|
127
|
+
semanticConfigPath: semanticConfigPath,
|
|
128
|
+
needsRuleChecker: engine.NeedsTypeChecker(),
|
|
129
|
+
singleThreaded: *singleThreaded,
|
|
130
|
+
checkers: *checkers,
|
|
131
|
+
tsgoArgs: tsgoArgs,
|
|
132
|
+
projectIdentity: projectIdentity,
|
|
131
133
|
})
|
|
132
134
|
if err != nil {
|
|
133
135
|
fmt.Fprintf(stderr, "@ttsc/lint: %v\n", err)
|
|
@@ -201,21 +203,22 @@ func RunTransformWithIO(args []string, stdout, stderr io.Writer) int {
|
|
|
201
203
|
}
|
|
202
204
|
|
|
203
205
|
type subcommandOpts struct {
|
|
204
|
-
cwd
|
|
205
|
-
tsconfig
|
|
206
|
-
pluginsJSON
|
|
207
|
-
emit
|
|
208
|
-
noEmit
|
|
209
|
-
quiet
|
|
210
|
-
verbose
|
|
211
|
-
diagnostics
|
|
212
|
-
outDir
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
206
|
+
cwd string
|
|
207
|
+
tsconfig string
|
|
208
|
+
pluginsJSON string
|
|
209
|
+
emit bool
|
|
210
|
+
noEmit bool
|
|
211
|
+
quiet bool
|
|
212
|
+
verbose bool
|
|
213
|
+
diagnostics bool
|
|
214
|
+
outDir string
|
|
215
|
+
semanticConfigPath string
|
|
216
|
+
singleThreaded bool
|
|
217
|
+
checkers int
|
|
218
|
+
tsgoArgs []string
|
|
219
|
+
projectIdentity publicrule.ProjectIdentity
|
|
220
|
+
stdout io.Writer
|
|
221
|
+
stderr io.Writer
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
// parseSubcommandFlags parses the shared flag set used by the `check`,
|
|
@@ -226,6 +229,7 @@ func parseSubcommandFlags(name string, args []string) (*subcommandOpts, error) {
|
|
|
226
229
|
}
|
|
227
230
|
|
|
228
231
|
func parseSubcommandFlagsWithIO(name string, args []string, stdout, stderr io.Writer) (*subcommandOpts, error) {
|
|
232
|
+
semanticConfigPath := os.Getenv(semanticConfigPathEnv)
|
|
229
233
|
if stdout == nil {
|
|
230
234
|
stdout = io.Discard
|
|
231
235
|
}
|
|
@@ -267,21 +271,22 @@ func parseSubcommandFlagsWithIO(name string, args []string, stdout, stderr io.Wr
|
|
|
267
271
|
return nil, err
|
|
268
272
|
}
|
|
269
273
|
return &subcommandOpts{
|
|
270
|
-
cwd:
|
|
271
|
-
tsconfig:
|
|
272
|
-
pluginsJSON:
|
|
273
|
-
emit:
|
|
274
|
-
noEmit:
|
|
275
|
-
quiet:
|
|
276
|
-
verbose:
|
|
277
|
-
diagnostics:
|
|
278
|
-
outDir:
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
274
|
+
cwd: resolvedCwd,
|
|
275
|
+
tsconfig: *tsconfig,
|
|
276
|
+
pluginsJSON: *pluginsJSON,
|
|
277
|
+
emit: *emit,
|
|
278
|
+
noEmit: *noEmit,
|
|
279
|
+
quiet: *quiet,
|
|
280
|
+
verbose: *verbose,
|
|
281
|
+
diagnostics: *diagnostics || *extendedDiagnostics,
|
|
282
|
+
outDir: *outDir,
|
|
283
|
+
semanticConfigPath: semanticConfigPath,
|
|
284
|
+
singleThreaded: *singleThreaded,
|
|
285
|
+
checkers: *checkers,
|
|
286
|
+
tsgoArgs: tsgoArgs,
|
|
287
|
+
projectIdentity: projectIdentity,
|
|
288
|
+
stdout: stdout,
|
|
289
|
+
stderr: stderr,
|
|
285
290
|
}, nil
|
|
286
291
|
}
|
|
287
292
|
|
|
@@ -332,14 +337,15 @@ func runProject(opts *subcommandOpts) int {
|
|
|
332
337
|
engine.SetSerial(opts.singleThreaded)
|
|
333
338
|
|
|
334
339
|
prog, parseDiags, err := loadProgram(opts.cwd, opts.tsconfig, loadProgramOptions{
|
|
335
|
-
forceEmit:
|
|
336
|
-
forceNoEmit:
|
|
337
|
-
outDir:
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
forceEmit: opts.emit,
|
|
341
|
+
forceNoEmit: opts.noEmit,
|
|
342
|
+
outDir: opts.outDir,
|
|
343
|
+
semanticConfigPath: opts.semanticConfigPath,
|
|
344
|
+
needsRuleChecker: engine.NeedsTypeChecker(),
|
|
345
|
+
singleThreaded: opts.singleThreaded,
|
|
346
|
+
checkers: opts.checkers,
|
|
347
|
+
tsgoArgs: opts.tsgoArgs,
|
|
348
|
+
projectIdentity: opts.projectIdentity,
|
|
343
349
|
})
|
|
344
350
|
if err != nil {
|
|
345
351
|
fmt.Fprintf(opts.stderr, "@ttsc/lint: %v\n", err)
|
package/linthost/fix.go
CHANGED
|
@@ -143,13 +143,14 @@ func runFix(opts *subcommandOpts) int {
|
|
|
143
143
|
// NoEmit forced on. Returns (nil, 2) when loading or config parsing fails.
|
|
144
144
|
func loadFixProgram(opts *subcommandOpts, needsRuleChecker bool) (*program, int) {
|
|
145
145
|
prog, parseDiags, err := loadProgram(opts.cwd, opts.tsconfig, loadProgramOptions{
|
|
146
|
-
forceNoEmit:
|
|
147
|
-
outDir:
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
146
|
+
forceNoEmit: true,
|
|
147
|
+
outDir: opts.outDir,
|
|
148
|
+
semanticConfigPath: opts.semanticConfigPath,
|
|
149
|
+
needsRuleChecker: needsRuleChecker,
|
|
150
|
+
singleThreaded: opts.singleThreaded,
|
|
151
|
+
checkers: opts.checkers,
|
|
152
|
+
tsgoArgs: opts.tsgoArgs,
|
|
153
|
+
projectIdentity: opts.projectIdentity,
|
|
153
154
|
})
|
|
154
155
|
if err != nil {
|
|
155
156
|
fmt.Fprintf(os.Stderr, "@ttsc/lint: %v\n", err)
|
package/linthost/host.go
CHANGED
|
@@ -33,6 +33,11 @@ import (
|
|
|
33
33
|
|
|
34
34
|
var programLifecycleSequence atomic.Uint64
|
|
35
35
|
|
|
36
|
+
// semanticConfigPathEnv mirrors `driver.SemanticConfigPathEnv`: the internal
|
|
37
|
+
// channel that identifies the user-authored config behind a generated wrapper.
|
|
38
|
+
// This module cannot import the ttsc driver; see the package boundary above.
|
|
39
|
+
const semanticConfigPathEnv = "TTSC_SEMANTIC_CONFIG_PATH"
|
|
40
|
+
|
|
36
41
|
// program bundles the tsgo Program with the parsed config and the standalone
|
|
37
42
|
// checker used only by type-aware lint rules.
|
|
38
43
|
type program struct {
|
|
@@ -54,9 +59,10 @@ type program struct {
|
|
|
54
59
|
}
|
|
55
60
|
|
|
56
61
|
type loadProgramOptions struct {
|
|
57
|
-
forceEmit
|
|
58
|
-
forceNoEmit
|
|
59
|
-
outDir
|
|
62
|
+
forceEmit bool
|
|
63
|
+
forceNoEmit bool
|
|
64
|
+
outDir string
|
|
65
|
+
semanticConfigPath string
|
|
60
66
|
// needsRuleChecker asks loadProgram to create the standalone checker that
|
|
61
67
|
// type-aware lint rules receive through Context.Checker.
|
|
62
68
|
needsRuleChecker bool
|
|
@@ -118,6 +124,9 @@ func loadProgram(cwd, tsconfigPath string, options loadProgramOptions) (*program
|
|
|
118
124
|
if len(parsed.Errors) > 0 {
|
|
119
125
|
return nil, parsed.Errors, nil
|
|
120
126
|
}
|
|
127
|
+
if err := applySemanticConfigPath(parsed, options.semanticConfigPath); err != nil {
|
|
128
|
+
return nil, nil, err
|
|
129
|
+
}
|
|
121
130
|
if options.forceNoEmit {
|
|
122
131
|
forceNoEmit(parsed)
|
|
123
132
|
}
|
|
@@ -158,6 +167,18 @@ func loadProgram(cwd, tsconfigPath string, options loadProgramOptions) (*program
|
|
|
158
167
|
}, nil, nil
|
|
159
168
|
}
|
|
160
169
|
|
|
170
|
+
func applySemanticConfigPath(parsed *tsoptions.ParsedCommandLine, semanticConfigPath string) error {
|
|
171
|
+
configured := strings.TrimSpace(semanticConfigPath)
|
|
172
|
+
if configured == "" {
|
|
173
|
+
return nil
|
|
174
|
+
}
|
|
175
|
+
if !filepath.IsAbs(configured) {
|
|
176
|
+
return fmt.Errorf("linthost: semantic config path must be absolute: %s", configured)
|
|
177
|
+
}
|
|
178
|
+
parsed.ParsedConfig.CompilerOptions.ConfigFilePath = shimtspath.ResolvePath(configured)
|
|
179
|
+
return nil
|
|
180
|
+
}
|
|
181
|
+
|
|
161
182
|
func normalizeProjectIdentity(
|
|
162
183
|
identity publicrule.ProjectIdentity,
|
|
163
184
|
cwd string,
|
package/linthost/lsp.go
CHANGED
|
@@ -145,10 +145,11 @@ type lspCommandOptions struct {
|
|
|
145
145
|
pluginsJSON string
|
|
146
146
|
// rangeJSON carries the editor selection used to limit quickfix.ttsc
|
|
147
147
|
// actions. Source fix-all and format actions remain document-wide.
|
|
148
|
-
rangeJSON
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
rangeJSON string
|
|
149
|
+
semanticConfigPath string
|
|
150
|
+
tsconfig string
|
|
151
|
+
uri string
|
|
152
|
+
projectIdentity publicrule.ProjectIdentity
|
|
152
153
|
}
|
|
153
154
|
|
|
154
155
|
// lspCommandIDs is the workspace/executeCommand ids owned by @ttsc/lint, shared
|
|
@@ -424,16 +425,17 @@ func parseLSPCommandOptions(name string, args []string) (*lspCommandOptions, boo
|
|
|
424
425
|
return nil, false
|
|
425
426
|
}
|
|
426
427
|
return &lspCommandOptions{
|
|
427
|
-
argumentsJSON:
|
|
428
|
-
command:
|
|
429
|
-
contextJSON:
|
|
430
|
-
contentStdin:
|
|
431
|
-
cwd:
|
|
432
|
-
pluginsJSON:
|
|
433
|
-
rangeJSON:
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
428
|
+
argumentsJSON: *argumentsJSON,
|
|
429
|
+
command: *command,
|
|
430
|
+
contextJSON: *contextJSON,
|
|
431
|
+
contentStdin: *contentStdin,
|
|
432
|
+
cwd: resolvedCwd,
|
|
433
|
+
pluginsJSON: *pluginsJSON,
|
|
434
|
+
rangeJSON: *rangeJSON,
|
|
435
|
+
semanticConfigPath: os.Getenv(semanticConfigPathEnv),
|
|
436
|
+
tsconfig: *tsconfig,
|
|
437
|
+
uri: *uri,
|
|
438
|
+
projectIdentity: projectIdentity,
|
|
437
439
|
}, true
|
|
438
440
|
}
|
|
439
441
|
|
|
@@ -187,6 +187,19 @@ func (formatPrintWidth) Check(ctx *Context, node *shimast.Node) {
|
|
|
187
187
|
return
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
// Abstain on any uncontrolled reflow target nested below a binary
|
|
191
|
+
// expression. The structured printer does not own BinaryExpression, so
|
|
192
|
+
// independently flattening or breaking its child calls and value literals
|
|
193
|
+
// can change whether the complete binary line fits. The next cascade pass
|
|
194
|
+
// then reverses those child decisions and `ttsc format` oscillates to the
|
|
195
|
+
// 10-pass cap. A destructuring assignment target may skip only its directly
|
|
196
|
+
// owning `=` because the literal printer owns the complete left-hand list and
|
|
197
|
+
// charges the `= value` suffix. Any binary ancestor above that assignment
|
|
198
|
+
// remains uncontrolled.
|
|
199
|
+
if hasUncontrolledBinaryExpressionAncestor(node) {
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
190
203
|
// Abstain on any node nested inside a template-literal substitution.
|
|
191
204
|
// Prettier renders `${…}` expressions at printWidth:Infinity, it
|
|
192
205
|
// never breaks an interpolation the source wrote on one line, so
|
|
@@ -505,6 +518,31 @@ func hasReflowAncestor(node *shimast.Node) bool {
|
|
|
505
518
|
return false
|
|
506
519
|
}
|
|
507
520
|
|
|
521
|
+
// hasUncontrolledBinaryExpressionAncestor reports whether `node` is a fragment
|
|
522
|
+
// of a binary expression whose layout it cannot own. A destructuring assignment
|
|
523
|
+
// target may skip its owning `=` because the literal printer owns that complete
|
|
524
|
+
// left-hand list, but an enclosing binary expression remains uncontrolled.
|
|
525
|
+
func hasUncontrolledBinaryExpressionAncestor(node *shimast.Node) bool {
|
|
526
|
+
if node == nil {
|
|
527
|
+
return false
|
|
528
|
+
}
|
|
529
|
+
skipOwningAssignment := isDestructuringAssignmentTarget(node)
|
|
530
|
+
child := node
|
|
531
|
+
for parent := node.Parent; parent != nil; parent = parent.Parent {
|
|
532
|
+
if parent.Kind == shimast.KindBinaryExpression {
|
|
533
|
+
expression := parent.AsBinaryExpression()
|
|
534
|
+
if skipOwningAssignment && expression != nil && expression.OperatorToken != nil &&
|
|
535
|
+
expression.OperatorToken.Kind == shimast.KindEqualsToken && expression.Left == child {
|
|
536
|
+
skipOwningAssignment = false
|
|
537
|
+
} else {
|
|
538
|
+
return true
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
child = parent
|
|
542
|
+
}
|
|
543
|
+
return false
|
|
544
|
+
}
|
|
545
|
+
|
|
508
546
|
// hasTemplateSubstitutionAncestor reports whether `node` sits inside a
|
|
509
547
|
// template-literal substitution (`${…}`). formatPrintWidth abstains
|
|
510
548
|
// on such nodes: Prettier prints template interpolations at infinite
|
package/linthost/serve.go
CHANGED
|
@@ -80,9 +80,10 @@ func (c *residentProgramCache) acquire(
|
|
|
80
80
|
}
|
|
81
81
|
}
|
|
82
82
|
prog, diags, err := loadProgram(opts.cwd, opts.tsconfig, loadProgramOptions{
|
|
83
|
-
forceNoEmit:
|
|
84
|
-
needsRuleChecker:
|
|
85
|
-
projectIdentity:
|
|
83
|
+
forceNoEmit: true,
|
|
84
|
+
needsRuleChecker: needsChecker,
|
|
85
|
+
projectIdentity: opts.projectIdentity,
|
|
86
|
+
semanticConfigPath: opts.semanticConfigPath,
|
|
86
87
|
})
|
|
87
88
|
if err != nil {
|
|
88
89
|
return nil, nil, noopClose, err
|
|
@@ -172,9 +173,10 @@ func acquireProgram(
|
|
|
172
173
|
return residentPrograms.acquire(opts, needsChecker)
|
|
173
174
|
}
|
|
174
175
|
prog, diags, err := loadProgram(opts.cwd, opts.tsconfig, loadProgramOptions{
|
|
175
|
-
forceNoEmit:
|
|
176
|
-
needsRuleChecker:
|
|
177
|
-
projectIdentity:
|
|
176
|
+
forceNoEmit: true,
|
|
177
|
+
needsRuleChecker: needsChecker,
|
|
178
|
+
projectIdentity: opts.projectIdentity,
|
|
179
|
+
semanticConfigPath: opts.semanticConfigPath,
|
|
178
180
|
})
|
|
179
181
|
if prog == nil {
|
|
180
182
|
return prog, diags, noopClose, err
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/lint",
|
|
3
|
-
"version": "0.28.
|
|
3
|
+
"version": "0.28.6",
|
|
4
4
|
"description": "Reference ttsc plugin: ESLint-style lint rules over the TypeScript-Go Program used by the type-check pass.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@types/node": "^25.3.0",
|
|
38
38
|
"rimraf": "^6.1.2",
|
|
39
39
|
"typescript": "^7.0.2",
|
|
40
|
-
"ttsc": "0.28.
|
|
40
|
+
"ttsc": "0.28.6"
|
|
41
41
|
},
|
|
42
42
|
"repository": {
|
|
43
43
|
"type": "git",
|