@ttsc/lint 0.25.0 → 0.26.1
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/index.js +62 -2
- package/lib/index.js.map +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/index.ts +70 -2
- package/src/structures/rules/ITtscLintFunctionalRuleOptions.ts +73 -16
|
@@ -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 {
|
|
@@ -143,6 +143,34 @@ type functionalNoTryOptions struct {
|
|
|
143
143
|
AllowFinally bool `json:"allowFinally"`
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
// functionalNoMixedTypesOptions gates the two container kinds the rule visits.
|
|
147
|
+
// Both default to true (an absent key leaves the container checked), so the
|
|
148
|
+
// pointers distinguish "not configured" from an explicit false.
|
|
149
|
+
type functionalNoMixedTypesOptions struct {
|
|
150
|
+
CheckInterfaces *bool `json:"checkInterfaces"`
|
|
151
|
+
CheckTypeLiterals *bool `json:"checkTypeLiterals"`
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// functionalNoReturnVoidOptions narrows what counts as a rejected return.
|
|
155
|
+
//
|
|
156
|
+
// `allowNull` and `allowUndefined` default to true, matching the rule's
|
|
157
|
+
// existing behavior: only a declared `void` return type is rejected. Setting
|
|
158
|
+
// either to false extends the rejection to that declared return type.
|
|
159
|
+
// `ignoreInferredTypes` defaults to false and, when true, spares a bare
|
|
160
|
+
// `return;` whose enclosing function declares no return type, which is the one
|
|
161
|
+
// place the rule reports a void-ness it inferred rather than read.
|
|
162
|
+
type functionalNoReturnVoidOptions struct {
|
|
163
|
+
AllowNull *bool `json:"allowNull"`
|
|
164
|
+
AllowUndefined *bool `json:"allowUndefined"`
|
|
165
|
+
IgnoreInferredTypes bool `json:"ignoreInferredTypes"`
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// functionalPreferTacitOptions gates the member-expression callee shape.
|
|
169
|
+
// Defaults to true, keeping `x => service.handler(x)` reported as before.
|
|
170
|
+
type functionalPreferTacitOptions struct {
|
|
171
|
+
CheckMemberExpressions *bool `json:"checkMemberExpressions"`
|
|
172
|
+
}
|
|
173
|
+
|
|
146
174
|
type functionalImmutableDataOptions struct {
|
|
147
175
|
functionalPatternOptions
|
|
148
176
|
IgnoreMapsAndSets bool `json:"ignoreMapsAndSets"`
|
|
@@ -159,8 +187,15 @@ type functionalPreferImmutableTypesOptions struct {
|
|
|
159
187
|
functionalPatternOptions
|
|
160
188
|
}
|
|
161
189
|
|
|
190
|
+
// functionalPreferReadonlyTypeOptions narrows which positions the rule polices.
|
|
191
|
+
// Both gates default to off, so an absent key leaves every visited position
|
|
192
|
+
// checked exactly as before.
|
|
162
193
|
type functionalPreferReadonlyTypeOptions struct {
|
|
163
194
|
functionalPatternOptions
|
|
195
|
+
AllowMutableReturnType bool `json:"allowMutableReturnType"`
|
|
196
|
+
IgnoreClass interface{} `json:"ignoreClass"`
|
|
197
|
+
IgnoreCollections bool `json:"ignoreCollections"`
|
|
198
|
+
IgnoreInterface bool `json:"ignoreInterface"`
|
|
164
199
|
}
|
|
165
200
|
|
|
166
201
|
type functionalReadonlyTypeOptions struct {
|
|
@@ -332,6 +367,18 @@ func (functionalNoLoopStatements) Check(ctx *Context, node *shimast.Node) {
|
|
|
332
367
|
}
|
|
333
368
|
|
|
334
369
|
func (functionalNoMixedTypes) Check(ctx *Context, node *shimast.Node) {
|
|
370
|
+
var opts functionalNoMixedTypesOptions
|
|
371
|
+
_ = ctx.DecodeOptions(&opts)
|
|
372
|
+
switch node.Kind {
|
|
373
|
+
case shimast.KindInterfaceDeclaration:
|
|
374
|
+
if opts.CheckInterfaces != nil && !*opts.CheckInterfaces {
|
|
375
|
+
return
|
|
376
|
+
}
|
|
377
|
+
case shimast.KindTypeLiteral:
|
|
378
|
+
if opts.CheckTypeLiterals != nil && !*opts.CheckTypeLiterals {
|
|
379
|
+
return
|
|
380
|
+
}
|
|
381
|
+
}
|
|
335
382
|
members := containerMembers(node)
|
|
336
383
|
if len(members) < 2 {
|
|
337
384
|
return
|
|
@@ -361,16 +408,51 @@ func (functionalNoPromiseReject) Check(ctx *Context, node *shimast.Node) {
|
|
|
361
408
|
}
|
|
362
409
|
|
|
363
410
|
func (functionalNoReturnVoid) Check(ctx *Context, node *shimast.Node) {
|
|
411
|
+
var opts functionalNoReturnVoidOptions
|
|
412
|
+
_ = ctx.DecodeOptions(&opts)
|
|
364
413
|
if node.Kind == shimast.KindReturnStatement {
|
|
365
414
|
ret := node.AsReturnStatement()
|
|
366
|
-
if ret
|
|
367
|
-
|
|
415
|
+
if ret == nil || ret.Expression != nil {
|
|
416
|
+
return
|
|
417
|
+
}
|
|
418
|
+
if opts.IgnoreInferredTypes && functionalEnclosingReturnTypeText(ctx, node) == "" {
|
|
419
|
+
return
|
|
368
420
|
}
|
|
421
|
+
ctx.Report(node, "Function must return a value.")
|
|
369
422
|
return
|
|
370
423
|
}
|
|
371
|
-
|
|
424
|
+
switch functionalReturnTypeText(ctx, node) {
|
|
425
|
+
case "void":
|
|
372
426
|
ctx.Report(node, "Function must return a value.")
|
|
427
|
+
case "null":
|
|
428
|
+
if opts.AllowNull != nil && !*opts.AllowNull {
|
|
429
|
+
ctx.Report(node, "Function must return a value.")
|
|
430
|
+
}
|
|
431
|
+
case "undefined":
|
|
432
|
+
if opts.AllowUndefined != nil && !*opts.AllowUndefined {
|
|
433
|
+
ctx.Report(node, "Function must return a value.")
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// functionalEnclosingReturnTypeText returns the declared return type text of the
|
|
439
|
+
// nearest function-like ancestor of `node`, or "" when that function declares
|
|
440
|
+
// none. A bare `return;` there is the only place the rule reports a void-ness it
|
|
441
|
+
// inferred instead of reading, which is what `ignoreInferredTypes` spares.
|
|
442
|
+
//
|
|
443
|
+
// The walk stops at the nearest function-like of ANY kind. Skipping a
|
|
444
|
+
// constructor or a set accessor, neither of which may annotate a return type,
|
|
445
|
+
// would attribute an enclosing function's annotation to a `return;` that has
|
|
446
|
+
// nothing to do with it. A get accessor may annotate one, and
|
|
447
|
+
// functionalReturnTypeText reads it.
|
|
448
|
+
func functionalEnclosingReturnTypeText(ctx *Context, node *shimast.Node) string {
|
|
449
|
+
for parent := node.Parent; parent != nil; parent = parent.Parent {
|
|
450
|
+
if !isFunctionLikeKind(parent) {
|
|
451
|
+
continue
|
|
452
|
+
}
|
|
453
|
+
return functionalReturnTypeText(ctx, parent)
|
|
373
454
|
}
|
|
455
|
+
return ""
|
|
374
456
|
}
|
|
375
457
|
|
|
376
458
|
func (functionalNoThisExpressions) Check(ctx *Context, node *shimast.Node) {
|
|
@@ -418,6 +500,20 @@ func (functionalPreferPropertySignatures) Check(ctx *Context, node *shimast.Node
|
|
|
418
500
|
func (functionalPreferReadonlyType) Check(ctx *Context, node *shimast.Node) {
|
|
419
501
|
var opts functionalPreferReadonlyTypeOptions
|
|
420
502
|
_ = ctx.DecodeOptions(&opts)
|
|
503
|
+
if opts.IgnoreInterface && hasAncestor(node, func(ancestor *shimast.Node) bool {
|
|
504
|
+
return ancestor.Kind == shimast.KindInterfaceDeclaration
|
|
505
|
+
}) {
|
|
506
|
+
return
|
|
507
|
+
}
|
|
508
|
+
if functionalIgnoreClassSkips(opts.IgnoreClass, node) {
|
|
509
|
+
return
|
|
510
|
+
}
|
|
511
|
+
if opts.AllowMutableReturnType && isFunctionLikeReturnTypePosition(node) {
|
|
512
|
+
return
|
|
513
|
+
}
|
|
514
|
+
if opts.IgnoreCollections && isFunctionalCollectionTypeNode(node) {
|
|
515
|
+
return
|
|
516
|
+
}
|
|
421
517
|
switch node.Kind {
|
|
422
518
|
case shimast.KindArrayType:
|
|
423
519
|
if !isReadonlyTypeNode(node) {
|
|
@@ -451,10 +547,33 @@ func (functionalPreferReadonlyType) Check(ctx *Context, node *shimast.Node) {
|
|
|
451
547
|
}
|
|
452
548
|
|
|
453
549
|
func (functionalPreferTacit) Check(ctx *Context, node *shimast.Node) {
|
|
550
|
+
var opts functionalPreferTacitOptions
|
|
551
|
+
_ = ctx.DecodeOptions(&opts)
|
|
454
552
|
text := compactFunctionalWhitespace(nodeText(ctx.File, node))
|
|
455
|
-
if isTacitWrapperText(text) {
|
|
456
|
-
|
|
553
|
+
if !isTacitWrapperText(text) {
|
|
554
|
+
return
|
|
555
|
+
}
|
|
556
|
+
if opts.CheckMemberExpressions != nil && !*opts.CheckMemberExpressions &&
|
|
557
|
+
isTacitWrapperMemberCallee(text) {
|
|
558
|
+
return
|
|
457
559
|
}
|
|
560
|
+
ctx.Report(node, "Potentially unnecessary function wrapper.")
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// isTacitWrapperMemberCallee reports whether the wrapper's callee is a member
|
|
564
|
+
// expression (`service.handler`) rather than a bare identifier. `text` has
|
|
565
|
+
// already satisfied isTacitWrapperText, so the split and the `(` are present.
|
|
566
|
+
func isTacitWrapperMemberCallee(text string) bool {
|
|
567
|
+
parts := strings.Split(text, "=>")
|
|
568
|
+
if len(parts) != 2 {
|
|
569
|
+
return false
|
|
570
|
+
}
|
|
571
|
+
call := parts[1]
|
|
572
|
+
open := strings.LastIndex(call, "(")
|
|
573
|
+
if open <= 0 {
|
|
574
|
+
return false
|
|
575
|
+
}
|
|
576
|
+
return strings.Contains(call[:open], ".")
|
|
458
577
|
}
|
|
459
578
|
|
|
460
579
|
func (functionalReadonlyType) Check(ctx *Context, node *shimast.Node) {
|
|
@@ -595,27 +714,12 @@ func isDeclarationName(node *shimast.Node) bool {
|
|
|
595
714
|
}
|
|
596
715
|
}
|
|
597
716
|
|
|
717
|
+
// functionalReturnTypeText returns the declared return-type text of a
|
|
718
|
+
// signature-bearing node, or "" when it declares none. It reads the same
|
|
719
|
+
// signature table isFunctionLikeReturnTypePosition uses, so a get accessor is
|
|
720
|
+
// not mistaken for a declaration that cannot annotate its return type.
|
|
598
721
|
func functionalReturnTypeText(ctx *Context, node *shimast.Node) string {
|
|
599
|
-
|
|
600
|
-
switch node.Kind {
|
|
601
|
-
case shimast.KindFunctionDeclaration:
|
|
602
|
-
if decl := node.AsFunctionDeclaration(); decl != nil {
|
|
603
|
-
typeNode = decl.Type
|
|
604
|
-
}
|
|
605
|
-
case shimast.KindFunctionExpression:
|
|
606
|
-
if decl := node.AsFunctionExpression(); decl != nil {
|
|
607
|
-
typeNode = decl.Type
|
|
608
|
-
}
|
|
609
|
-
case shimast.KindArrowFunction:
|
|
610
|
-
if decl := node.AsArrowFunction(); decl != nil {
|
|
611
|
-
typeNode = decl.Type
|
|
612
|
-
}
|
|
613
|
-
case shimast.KindMethodDeclaration:
|
|
614
|
-
if decl := node.AsMethodDeclaration(); decl != nil {
|
|
615
|
-
typeNode = decl.Type
|
|
616
|
-
}
|
|
617
|
-
}
|
|
618
|
-
return strings.TrimSpace(nodeText(ctx.File, typeNode))
|
|
722
|
+
return strings.TrimSpace(nodeText(ctx.File, signatureReturnTypeNode(node)))
|
|
619
723
|
}
|
|
620
724
|
|
|
621
725
|
func functionalFunctionLikeName(node *shimast.Node) string {
|
|
@@ -757,6 +861,125 @@ func nilSafeFile(node *shimast.Node) *shimast.SourceFile {
|
|
|
757
861
|
return nil
|
|
758
862
|
}
|
|
759
863
|
|
|
864
|
+
// isFunctionLikeReturnTypePosition reports whether `node` is the return-type
|
|
865
|
+
// annotation of any signature-bearing declaration, or sits inside one. The rule
|
|
866
|
+
// visits type nodes, so a mutable array nested in the return type is the same
|
|
867
|
+
// position as the return type itself.
|
|
868
|
+
//
|
|
869
|
+
// Every kind that can carry a return-type annotation is listed: leaving call
|
|
870
|
+
// signatures, construct signatures, constructor types, or a get accessor out
|
|
871
|
+
// would let the same option answer differently for the same position depending
|
|
872
|
+
// on how the signature was spelled.
|
|
873
|
+
func isFunctionLikeReturnTypePosition(node *shimast.Node) bool {
|
|
874
|
+
for current := node; current != nil && current.Parent != nil; current = current.Parent {
|
|
875
|
+
typeNode := signatureReturnTypeNode(current.Parent)
|
|
876
|
+
if typeNode != nil && typeNode == current {
|
|
877
|
+
return true
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
return false
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// signatureReturnTypeNode returns the declared return-type node of `node`, or
|
|
884
|
+
// nil when `node` carries no signature or declares no return type.
|
|
885
|
+
//
|
|
886
|
+
// Do not replace this table with typescript-go's own FunctionLikeData().Type:
|
|
887
|
+
// an index signature embeds the same base, so that accessor also returns the
|
|
888
|
+
// value type of `{ [k: string]: string[] }`, which is not a return type. The
|
|
889
|
+
// three kinds this table omits, a constructor, a set accessor, and an index
|
|
890
|
+
// signature, are exactly the ones whose Type field means something else.
|
|
891
|
+
func signatureReturnTypeNode(node *shimast.Node) *shimast.Node {
|
|
892
|
+
switch node.Kind {
|
|
893
|
+
case shimast.KindFunctionDeclaration:
|
|
894
|
+
if decl := node.AsFunctionDeclaration(); decl != nil {
|
|
895
|
+
return decl.Type
|
|
896
|
+
}
|
|
897
|
+
case shimast.KindFunctionExpression:
|
|
898
|
+
if decl := node.AsFunctionExpression(); decl != nil {
|
|
899
|
+
return decl.Type
|
|
900
|
+
}
|
|
901
|
+
case shimast.KindArrowFunction:
|
|
902
|
+
if decl := node.AsArrowFunction(); decl != nil {
|
|
903
|
+
return decl.Type
|
|
904
|
+
}
|
|
905
|
+
case shimast.KindMethodDeclaration:
|
|
906
|
+
if decl := node.AsMethodDeclaration(); decl != nil {
|
|
907
|
+
return decl.Type
|
|
908
|
+
}
|
|
909
|
+
case shimast.KindMethodSignature:
|
|
910
|
+
if decl := node.AsMethodSignatureDeclaration(); decl != nil {
|
|
911
|
+
return decl.Type
|
|
912
|
+
}
|
|
913
|
+
case shimast.KindGetAccessor:
|
|
914
|
+
if decl := node.AsGetAccessorDeclaration(); decl != nil {
|
|
915
|
+
return decl.Type
|
|
916
|
+
}
|
|
917
|
+
case shimast.KindFunctionType:
|
|
918
|
+
if decl := node.AsFunctionTypeNode(); decl != nil {
|
|
919
|
+
return decl.Type
|
|
920
|
+
}
|
|
921
|
+
case shimast.KindConstructorType:
|
|
922
|
+
if decl := node.AsConstructorTypeNode(); decl != nil {
|
|
923
|
+
return decl.Type
|
|
924
|
+
}
|
|
925
|
+
case shimast.KindCallSignature:
|
|
926
|
+
if decl := node.AsCallSignatureDeclaration(); decl != nil {
|
|
927
|
+
return decl.Type
|
|
928
|
+
}
|
|
929
|
+
case shimast.KindConstructSignature:
|
|
930
|
+
if decl := node.AsConstructSignatureDeclaration(); decl != nil {
|
|
931
|
+
return decl.Type
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
return nil
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// isFunctionalClassMemberPosition reports whether `node` sits inside a class,
|
|
938
|
+
// and whether that position is a field. `ignoreClass: true` skips anything
|
|
939
|
+
// under a class, its heritage clause and type parameters included, the same
|
|
940
|
+
// ancestor test `ignoreInterface` applies. `"fieldsOnly"` narrows that to field
|
|
941
|
+
// declarations, leaving methods, accessors, and constructor parameters checked.
|
|
942
|
+
func isFunctionalClassMemberPosition(node *shimast.Node) (inClass bool, inField bool) {
|
|
943
|
+
for parent := node.Parent; parent != nil; parent = parent.Parent {
|
|
944
|
+
switch parent.Kind {
|
|
945
|
+
case shimast.KindPropertyDeclaration:
|
|
946
|
+
inField = true
|
|
947
|
+
case shimast.KindClassDeclaration, shimast.KindClassExpression:
|
|
948
|
+
return true, inField
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return false, false
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// functionalIgnoreClassSkips resolves the `boolean | "fieldsOnly"` option
|
|
955
|
+
// against a visited node's class position.
|
|
956
|
+
func functionalIgnoreClassSkips(option interface{}, node *shimast.Node) bool {
|
|
957
|
+
if option == nil || option == false {
|
|
958
|
+
return false
|
|
959
|
+
}
|
|
960
|
+
inClass, inField := isFunctionalClassMemberPosition(node)
|
|
961
|
+
if !inClass {
|
|
962
|
+
return false
|
|
963
|
+
}
|
|
964
|
+
if option == "fieldsOnly" {
|
|
965
|
+
return inField
|
|
966
|
+
}
|
|
967
|
+
return option == true
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// isFunctionalCollectionTypeNode reports whether `node` is an array, tuple, or
|
|
971
|
+
// mutable built-in collection reference, the set `ignoreCollections` spares.
|
|
972
|
+
func isFunctionalCollectionTypeNode(node *shimast.Node) bool {
|
|
973
|
+
switch node.Kind {
|
|
974
|
+
case shimast.KindArrayType, shimast.KindTupleType:
|
|
975
|
+
return true
|
|
976
|
+
case shimast.KindTypeReference:
|
|
977
|
+
return isMutableTypeReference(node)
|
|
978
|
+
default:
|
|
979
|
+
return false
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
760
983
|
func isMutableTypeReference(node *shimast.Node) bool {
|
|
761
984
|
ref := node.AsTypeReferenceNode()
|
|
762
985
|
if ref == nil || ref.TypeName == nil {
|
|
@@ -317,12 +317,14 @@ func parameterPropertyName(param *shimast.Node) (string, bool) {
|
|
|
317
317
|
return name, name != ""
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
// isParameterProperty is the package's shared parameter-property predicate.
|
|
321
|
+
// The compiler's own mask is the source: a restated keyword list is what left
|
|
322
|
+
// `override` out of format/parameter-properties (#1131), and this predicate
|
|
323
|
+
// feeds six rules, so a restatement here would be five more chances at the same
|
|
324
|
+
// drift.
|
|
320
325
|
func isParameterProperty(param *shimast.Node) bool {
|
|
321
|
-
return
|
|
322
|
-
|
|
323
|
-
hasModifier(param, shimast.KindProtectedKeyword) ||
|
|
324
|
-
hasModifier(param, shimast.KindReadonlyKeyword) ||
|
|
325
|
-
hasModifier(param, shimast.KindOverrideKeyword)
|
|
326
|
+
return param != nil &&
|
|
327
|
+
param.ModifierFlags()&shimast.ModifierFlagsParameterPropertyModifier != 0
|
|
326
328
|
}
|
|
327
329
|
|
|
328
330
|
func thisPropertyAssignment(stmt *shimast.Node) (property string, value string, ok bool) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttsc/lint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.1",
|
|
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.
|
|
40
|
+
"ttsc": "0.26.1"
|
|
41
41
|
},
|
|
42
42
|
"repository": {
|
|
43
43
|
"type": "git",
|
package/rule/rule.go
CHANGED
|
@@ -262,15 +262,27 @@ type RelatedInformation struct {
|
|
|
262
262
|
// be replaced as a whole.
|
|
263
263
|
//
|
|
264
264
|
// Application policy: a rule may emit several `TextEdit`s in one
|
|
265
|
-
// `ReportFix` / `ReportRangeFix` call, in any order. The
|
|
266
|
-
//
|
|
267
|
-
//
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
//
|
|
265
|
+
// `ReportFix` / `ReportRangeFix` call, in any order. The unit the host
|
|
266
|
+
// resolves conflicts on is the FINDING, not the individual edit. Within one
|
|
267
|
+
// fix pass the host considers each finding's edits as one group, earliest
|
|
268
|
+
// group first, and accepts a group only when every member coexists with the
|
|
269
|
+
// edits already accepted. If any member would be dropped, the whole group is
|
|
270
|
+
// skipped, so a multi-edit fix never half-applies. The skipped finding is not
|
|
271
|
+
// lost: the next cascade pass re-runs the rule against the rewritten source
|
|
272
|
+
// and the fix applies then, or the cascade converges without it.
|
|
273
|
+
//
|
|
274
|
+
// A finding's own edits must therefore not overlap each other either, or the
|
|
275
|
+
// finding can never apply. Exact duplicates within one finding are collapsed
|
|
276
|
+
// rather than treated as a conflict, so repeating an identical edit is
|
|
277
|
+
// harmless. Nothing diagnoses a skipped group, and the host does not report
|
|
278
|
+
// when a comment falls inside a deletion range.
|
|
279
|
+
//
|
|
280
|
+
// Emit the narrowest edits that express the rewrite. Several small
|
|
281
|
+
// non-overlapping edits contend for less source than one wide replacement and
|
|
282
|
+
// are the shape the atomic applier exists to support. Built-ins that ship
|
|
283
|
+
// multi-edit fixes include `typescript/no-import-type-side-effects`,
|
|
284
|
+
// `format/whitespace`, `format/indent`, `unicorn/prevent-abbreviations`, and
|
|
285
|
+
// `unicorn/template-indent`.
|
|
274
286
|
type TextEdit struct {
|
|
275
287
|
Pos int
|
|
276
288
|
End int
|
package/src/index.ts
CHANGED
|
@@ -1789,8 +1789,9 @@ function evaluateTtsxConfigPlugins(
|
|
|
1789
1789
|
"--no-plugins",
|
|
1790
1790
|
loaderPath,
|
|
1791
1791
|
];
|
|
1792
|
-
|
|
1793
|
-
|
|
1792
|
+
const tsgoBinary = resolveConfigTsgo(configPath, context);
|
|
1793
|
+
if (tsgoBinary) {
|
|
1794
|
+
args.unshift("--binary", tsgoBinary);
|
|
1794
1795
|
}
|
|
1795
1796
|
const env = {
|
|
1796
1797
|
...nodeConfigLoaderEnv(configPath),
|
|
@@ -2453,6 +2454,73 @@ function nodeConfigLoaderEnv(configPath: string): NodeJS.ProcessEnv {
|
|
|
2453
2454
|
* and is not for a host that loaded this descriptor in process. The bare name
|
|
2454
2455
|
* remains the last resort for an installation none of them reach.
|
|
2455
2456
|
*/
|
|
2457
|
+
/**
|
|
2458
|
+
* The native TypeScript compiler the config evaluator must run.
|
|
2459
|
+
*
|
|
2460
|
+
* The evaluator builds the loader in an ephemeral directory, so the child it
|
|
2461
|
+
* spawns cannot discover `typescript` the way an ordinary invocation does: the
|
|
2462
|
+
* directory is not in the project, and `linkNearestNodeModules` is the only
|
|
2463
|
+
* thing that puts the project's modules within reach of it. Leaving the child
|
|
2464
|
+
* to re-derive the compiler from there made this work by inheritance —
|
|
2465
|
+
* `TTSC_TSGO_BINARY` is exported by `ttsx` to its own descendants, so a build
|
|
2466
|
+
* launched under `ttsx` passed a binary down and a build launched any other way
|
|
2467
|
+
* did not. A published consumer exports no such variable, and neither does a
|
|
2468
|
+
* process that deliberately sheds the launcher's runtime state, and for those
|
|
2469
|
+
* the evaluator failed with `ttsc: typescript is required` before it read a
|
|
2470
|
+
* line of the config.
|
|
2471
|
+
*
|
|
2472
|
+
* The host has already resolved a compiler for the project it is linting, so
|
|
2473
|
+
* the answer is asked of the project rather than of the environment. The config
|
|
2474
|
+
* comes first and the descriptor's own location second, the same order and for
|
|
2475
|
+
* the same reason as {@link resolveTtsxLauncher}. An explicit `TTSC_TSGO_BINARY`
|
|
2476
|
+
* still wins, so an embedder that pins a compiler keeps pinning it.
|
|
2477
|
+
*
|
|
2478
|
+
* Returning `undefined` leaves the child to resolve for itself, which is what
|
|
2479
|
+
* it did before: a project that cannot answer here could not answer there
|
|
2480
|
+
* either, and the child's own diagnostic is the one that names the missing
|
|
2481
|
+
* package.
|
|
2482
|
+
*/
|
|
2483
|
+
function resolveConfigTsgo(
|
|
2484
|
+
configPath: string,
|
|
2485
|
+
context: TtscPluginFactoryContext<ITtscLintPluginConfig>,
|
|
2486
|
+
): string | undefined {
|
|
2487
|
+
const explicit = process.env.TTSC_TSGO_BINARY?.trim();
|
|
2488
|
+
if (explicit) return explicit;
|
|
2489
|
+
const anchors = [
|
|
2490
|
+
configPath,
|
|
2491
|
+
path.join(context.projectRoot, "package.json"),
|
|
2492
|
+
context.dirname,
|
|
2493
|
+
];
|
|
2494
|
+
for (const anchor of anchors) {
|
|
2495
|
+
const binary = tsgoBinaryFrom(anchor);
|
|
2496
|
+
if (binary !== undefined) return binary;
|
|
2497
|
+
}
|
|
2498
|
+
return undefined;
|
|
2499
|
+
}
|
|
2500
|
+
|
|
2501
|
+
/**
|
|
2502
|
+
* The platform compiler binary of the `typescript` install `anchor` can see, or
|
|
2503
|
+
* `undefined` when this anchor reaches neither the package nor its platform
|
|
2504
|
+
* dependency. Mirrors ttsc's own resolution order so both name one file.
|
|
2505
|
+
*/
|
|
2506
|
+
function tsgoBinaryFrom(anchor: string): string | undefined {
|
|
2507
|
+
try {
|
|
2508
|
+
const manifest = createRequire(anchor).resolve("typescript/package.json");
|
|
2509
|
+
const platform = createRequire(manifest).resolve(
|
|
2510
|
+
`@typescript/typescript-${process.platform}-${process.arch}/package.json`,
|
|
2511
|
+
);
|
|
2512
|
+
const binary = path.join(
|
|
2513
|
+
path.dirname(platform),
|
|
2514
|
+
"lib",
|
|
2515
|
+
process.platform === "win32" ? "tsc.exe" : "tsc",
|
|
2516
|
+
);
|
|
2517
|
+
return fs.existsSync(binary) ? binary : undefined;
|
|
2518
|
+
} catch {
|
|
2519
|
+
// This anchor cannot see the compiler; the caller tries the next one.
|
|
2520
|
+
return undefined;
|
|
2521
|
+
}
|
|
2522
|
+
}
|
|
2523
|
+
|
|
2456
2524
|
function resolveTtsxLauncher(anchors: readonly string[]): string {
|
|
2457
2525
|
const explicit = process.env.TTSC_TTSX_BINARY?.trim();
|
|
2458
2526
|
if (explicit) return explicit;
|