Skip to content

Commit b0f0e43

Browse files
committed
feat: return type check errors (#446)
1 parent bbd0a2b commit b0f0e43

File tree

13 files changed

+139
-4
lines changed

13 files changed

+139
-4
lines changed

cmd/tsgolint/headless.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,10 @@ func runHeadless(args []string) int {
373373
Fix: fix,
374374
FixSuggestions: fixSuggestions,
375375
},
376+
linter.TypeErrors{
377+
ReportSyntactic: payload.ReportSyntactic,
378+
ReportSemantic: payload.ReportSemantic,
379+
},
376380
)
377381

378382
close(diagnosticsChan)

cmd/tsgolint/main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
"time"
1717
"unicode"
1818

19+
"github.com/typescript-eslint/tsgolint/internal/diagnostic"
1920
"github.com/typescript-eslint/tsgolint/internal/linter"
2021
"github.com/typescript-eslint/tsgolint/internal/rule"
2122
"github.com/typescript-eslint/tsgolint/internal/utils"
@@ -479,10 +480,17 @@ func runMain() int {
479480
func(d rule.RuleDiagnostic) {
480481
diagnosticsChan <- d
481482
},
483+
func(d diagnostic.Internal) {
484+
// Internal diagnostics are not used in this mode
485+
},
482486
linter.Fixes{
483487
Fix: true,
484488
FixSuggestions: true,
485489
},
490+
linter.TypeErrors{
491+
ReportSyntactic: false,
492+
ReportSemantic: false,
493+
},
486494
)
487495

488496
close(diagnosticsChan)

cmd/tsgolint/payload.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ type headlessPayload struct {
2121
Version int `json:"version"` // version must be 2
2222
Configs []headlessConfig `json:"configs"`
2323
SourceOverrides map[string]string `json:"source_overrides,omitempty"`
24+
ReportSyntactic bool `json:"report_syntactic,omitempty"`
25+
ReportSemantic bool `json:"report_semantic,omitempty"`
2426
}
2527

2628
type headlessConfig struct {

e2e/__snapshots__/snapshot.test.ts.snap

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3966,6 +3966,23 @@ exports[`TSGoLint E2E Snapshot Tests > should report an error if the tsconfig.js
39663966
]
39673967
`;
39683968

3969+
exports[`TSGoLint E2E Snapshot Tests > should report type errors 1`] = `
3970+
[
3971+
{
3972+
"file_path": "fixtures/report-type-errors/index.ts",
3973+
"kind": 1,
3974+
"message": {
3975+
"description": "Type 'number' is not assignable to type 'string'.",
3976+
"id": "TS2322",
3977+
},
3978+
"range": {
3979+
"end": 7,
3980+
"pos": 6,
3981+
},
3982+
},
3983+
]
3984+
`;
3985+
39693986
exports[`TSGoLint E2E Snapshot Tests > should work correctly with nested module namespaces and parent module searches (\`ValueMatchesSomeSpecifier\`) (issue #135) 1`] = `[]`;
39703987

39713988
exports[`TSGoLint E2E Snapshot Tests > supports passing rule config 1`] = `
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
const x: string = 123;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

e2e/snapshot.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ function generateConfig(
189189
rules:
190190
readonly ((typeof ALL_RULES)[number] | { name: typeof ALL_RULES[number]; options: Record<string, unknown> })[] =
191191
ALL_RULES,
192+
options?: {
193+
reportSyntactic?: boolean;
194+
reportSemantic?: boolean;
195+
},
192196
): string {
193197
// Headless payload format:
194198
// ```json
@@ -212,6 +216,8 @@ function generateConfig(
212216
} => (typeof r === 'string' ? { name: r } : r)),
213217
},
214218
],
219+
...(options?.reportSyntactic !== undefined && { report_syntactic: options.reportSyntactic }),
220+
...(options?.reportSemantic !== undefined && { report_semantic: options.reportSemantic }),
215221
} as const;
216222
return JSON.stringify(config);
217223
}
@@ -535,4 +541,27 @@ console.log(x);
535541

536542
expect(diagnostics).toMatchSnapshot();
537543
});
544+
545+
it('should report type errors', async () => {
546+
const testFiles = await getTestFiles('report-type-errors');
547+
expect(testFiles.length).toBeGreaterThan(0);
548+
549+
const config = generateConfig(
550+
testFiles,
551+
['no-floating-promises'],
552+
{
553+
reportSemantic: true,
554+
},
555+
);
556+
557+
const output = execFileSync(TSGOLINT_BIN, ['headless'], {
558+
input: config,
559+
env: { ...process.env, GOMAXPROCS: '1' },
560+
});
561+
562+
let diagnostics = parseHeadlessOutput(output);
563+
diagnostics = sortDiagnostics(diagnostics);
564+
565+
expect(diagnostics).toMatchSnapshot();
566+
});
538567
});

internal/linter/linter.go

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"log"
7+
"strconv"
78
"strings"
89

910
"github.com/typescript-eslint/tsgolint/internal/diagnostic"
@@ -34,6 +35,11 @@ type Fixes struct {
3435
FixSuggestions bool
3536
}
3637

38+
type TypeErrors struct {
39+
ReportSyntactic bool
40+
ReportSemantic bool
41+
}
42+
3743
func RunLinter(
3844
logLevel utils.LogLevel,
3945
currentDirectory string,
@@ -44,6 +50,7 @@ func RunLinter(
4450
onRuleDiagnostic func(diagnostic rule.RuleDiagnostic),
4551
onInternalDiagnostic func(d diagnostic.Internal),
4652
fixState Fixes,
53+
typeErrors TypeErrors,
4754
) error {
4855

4956
idx := 0
@@ -99,7 +106,7 @@ func RunLinter(
99106
panic(fmt.Sprintf("Expected file '%s' to be in program '%s'", unmatchedFilesString, configFileName))
100107
}
101108

102-
err = RunLinterOnProgram(logLevel, program, sourceFiles, workers, getRulesForFile, onRuleDiagnostic, fixState)
109+
err = RunLinterOnProgram(logLevel, program, sourceFiles, workers, getRulesForFile, onRuleDiagnostic, onInternalDiagnostic, fixState, typeErrors)
103110
if err != nil {
104111
return err
105112
}
@@ -130,7 +137,7 @@ func RunLinter(
130137
files = append(files, sf)
131138
}
132139

133-
err = RunLinterOnProgram(logLevel, program, files, workers, getRulesForFile, onRuleDiagnostic, fixState)
140+
err = RunLinterOnProgram(logLevel, program, files, workers, getRulesForFile, onRuleDiagnostic, onInternalDiagnostic, fixState, typeErrors)
134141
if err != nil {
135142
return err
136143
}
@@ -140,7 +147,7 @@ func RunLinter(
140147

141148
}
142149

143-
func RunLinterOnProgram(logLevel utils.LogLevel, program *compiler.Program, files []*ast.SourceFile, workers int, getRulesForFile func(sourceFile *ast.SourceFile) []ConfiguredRule, onDiagnostic func(diagnostic rule.RuleDiagnostic), fixState Fixes) error {
150+
func RunLinterOnProgram(logLevel utils.LogLevel, program *compiler.Program, files []*ast.SourceFile, workers int, getRulesForFile func(sourceFile *ast.SourceFile) []ConfiguredRule, onDiagnostic func(diagnostic rule.RuleDiagnostic), onInternalDiagnostic func(d diagnostic.Internal), fixState Fixes, typeErrors TypeErrors) error {
144151
type checkerWorkload struct {
145152
checker *checker.Checker
146153
program *compiler.Program
@@ -157,6 +164,41 @@ func RunLinterOnProgram(logLevel utils.LogLevel, program *compiler.Program, file
157164
program.BindSourceFiles()
158165

159166
ctx := core.WithRequestID(context.Background(), "__single_run__")
167+
168+
if typeErrors.ReportSyntactic || typeErrors.ReportSemantic {
169+
for _, file := range files {
170+
fileName := file.FileName()
171+
172+
if typeErrors.ReportSyntactic {
173+
syntacticDiagnostics := program.GetSyntacticDiagnostics(ctx, file)
174+
for _, d := range syntacticDiagnostics {
175+
if d.File() != nil && d.File().FileName() == fileName {
176+
onInternalDiagnostic(diagnostic.Internal{
177+
Range: d.Loc(),
178+
Id: "TS" + strconv.Itoa(int(d.Code())),
179+
Description: d.Message(),
180+
FilePath: &fileName,
181+
})
182+
}
183+
}
184+
}
185+
186+
if typeErrors.ReportSemantic {
187+
semanticDiagnostics := program.GetSemanticDiagnostics(ctx, file)
188+
for _, d := range semanticDiagnostics {
189+
if d.File() != nil && d.File().FileName() == fileName {
190+
onInternalDiagnostic(diagnostic.Internal{
191+
Range: d.Loc(),
192+
Id: "TS" + strconv.Itoa(int(d.Code())),
193+
Description: d.Message(),
194+
FilePath: &fileName,
195+
})
196+
}
197+
}
198+
}
199+
}
200+
}
201+
160202
program.ForEachCheckerParallel(ctx, func(idx int, ch *checker.Checker) {
161203
flatQueue = append(flatQueue, checkerWorkload{ch, program, queue})
162204
})

internal/rule_tester/rule_tester.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/microsoft/typescript-go/shim/ast"
1111
"github.com/microsoft/typescript-go/shim/scanner"
1212
"github.com/microsoft/typescript-go/shim/tspath"
13+
"github.com/typescript-eslint/tsgolint/internal/diagnostic"
1314
"github.com/typescript-eslint/tsgolint/internal/linter"
1415
"github.com/typescript-eslint/tsgolint/internal/rule"
1516
"github.com/typescript-eslint/tsgolint/internal/utils"
@@ -98,10 +99,17 @@ func RunRuleTester(rootDir string, tsconfigPath string, t *testing.T, r *rule.Ru
9899

99100
diagnostics = append(diagnostics, diagnostic)
100101
},
102+
func(d diagnostic.Internal) {
103+
// Internal diagnostics are not used in rule tester
104+
},
101105
linter.Fixes{
102106
Fix: true,
103107
FixSuggestions: true,
104108
},
109+
linter.TypeErrors{
110+
ReportSyntactic: false,
111+
ReportSemantic: false,
112+
},
105113
)
106114

107115
assert.NilError(t, err, "error running linter. code:\n", code)

shim/ast/extra-shim.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22
"ExtraMethods": {
33
"Node": [
44
"Type"
5+
],
6+
"Diagnostic": [
7+
"File",
8+
"Loc",
9+
"Code",
10+
"Category",
11+
"Message"
512
]
613
}
714
}

0 commit comments

Comments
 (0)