Skip to content

Commit 8ffaf5e

Browse files
[interop] Implement Diagnostics and Handling Errors (#426)
* implemented diagnostics * added extra invalid semantic
1 parent 767151e commit 8ffaf5e

File tree

6 files changed

+166
-0
lines changed

6 files changed

+166
-0
lines changed

web_generator/lib/src/cli.dart

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,17 @@ Future<void> compileDartMain({String? langVersion, String? dir}) async {
3131
);
3232
}
3333

34+
Future<Process> runProcWithResult(String executable, List<String> arguments,
35+
{required String workingDirectory}) async {
36+
print(ansi.styleBold.wrap(['*', executable, ...arguments].join(' ')));
37+
return Process.start(
38+
executable,
39+
arguments,
40+
runInShell: Platform.isWindows,
41+
workingDirectory: workingDirectory,
42+
);
43+
}
44+
3445
Future<void> runProc(String executable, List<String> arguments,
3546
{required String workingDirectory, bool detached = false}) async {
3647
print(ansi.styleBold.wrap(['*', executable, ...arguments].join(' ')));

web_generator/lib/src/interop_gen/parser.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:js_interop';
66

7+
import '../js/node.dart';
78
import '../js/typescript.dart' as ts;
89

910
class ParserResult {
@@ -13,9 +14,35 @@ class ParserResult {
1314
ParserResult({required this.program, required this.files});
1415
}
1516

17+
/// Parses the given TypeScript declaration [files], provides any diagnostics,
18+
/// if any, and generates a [ts.TSProgram] for transformation
1619
ParserResult parseDeclarationFiles(Iterable<String> files) {
1720
final program = ts.createProgram(files.jsify() as JSArray<JSString>,
1821
ts.TSCompilerOptions(declaration: true));
1922

23+
// get diagnostics
24+
final diagnostics = [
25+
...program.getSemanticDiagnostics().toDart,
26+
...program.getSyntacticDiagnostics().toDart,
27+
...program.getDeclarationDiagnostics().toDart,
28+
];
29+
30+
// handle diagnostics
31+
for (final diagnostic in diagnostics) {
32+
if (diagnostic.file case final diagnosticFile?) {
33+
final ts.TSLineAndCharacter(line: line, character: char) =
34+
ts.getLineAndCharacterOfPosition(diagnosticFile, diagnostic.start!);
35+
final message =
36+
ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n');
37+
printErr('${diagnosticFile.fileName} '
38+
'(${line.toDartInt + 1},${char.toDartInt + 1}): $message');
39+
}
40+
}
41+
42+
if (diagnostics.isNotEmpty) {
43+
// exit
44+
exit(1);
45+
}
46+
2047
return ParserResult(program: program, files: files);
2148
}

web_generator/lib/src/js/node.dart

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,9 @@ import 'dart:js_interop';
66

77
@JS()
88
external String get url;
9+
10+
@JS('process.exit')
11+
external void exit(int code);
12+
13+
@JS('console.error')
14+
external void printErr(String message);

web_generator/lib/src/js/typescript.dart

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ library;
77

88
import 'dart:js_interop';
99

10+
import 'package:meta/meta.dart';
11+
1012
import 'typescript.types.dart';
1113

1214
@JS()
@@ -30,6 +32,14 @@ external bool isTypeReferenceNode(TSNode node);
3032
@JS()
3133
external bool isThisTypeNode(TSNode node);
3234

35+
@JS()
36+
external TSLineAndCharacter getLineAndCharacterOfPosition(
37+
TSSourceFile sourceFile, int position);
38+
39+
@JS()
40+
external String flattenDiagnosticMessageText(JSAny? diag, String newLine,
41+
[int indent]);
42+
3343
@JS('CompilerOptions')
3444
extension type TSCompilerOptions._(JSObject _) implements JSObject {
3545
external TSCompilerOptions({bool? allowJs, bool? declaration});
@@ -41,6 +51,51 @@ extension type TSCompilerOptions._(JSObject _) implements JSObject {
4151
extension type TSProgram._(JSObject _) implements JSObject {
4252
external TSSourceFile? getSourceFile(String file);
4353
external TSTypeChecker getTypeChecker();
54+
55+
/// Diagnostics related to syntax errors
56+
external JSArray<TSDiagnosticWithLocation> getSyntacticDiagnostics([
57+
TSSourceFile? sourceFile,
58+
]);
59+
60+
/// Diagnostics related to type-checking
61+
external JSArray<TSDiagnostic> getSemanticDiagnostics([
62+
TSSourceFile? sourceFile,
63+
]);
64+
65+
/// Diagnostics related to the .d.ts file itself
66+
external JSArray<TSDiagnosticWithLocation> getDeclarationDiagnostics([
67+
TSSourceFile? sourceFile,
68+
]);
69+
}
70+
71+
@JS('DiagnosticRelatedInformation')
72+
extension type TSDiagnosticRelatedInformation._(JSObject _)
73+
implements JSObject {
74+
external TSSourceFile? file;
75+
external int code;
76+
77+
/// [String] or `TSDiagnosticMessageChain` (unimplemented)
78+
external JSAny messageText;
79+
external int? start;
80+
}
81+
82+
@JS('Diagnostic')
83+
extension type TSDiagnostic._(JSObject _)
84+
implements TSDiagnosticRelatedInformation {
85+
external String? source;
86+
external JSArray<TSDiagnosticRelatedInformation>? relatedInformation;
87+
88+
@doNotStore
89+
external JSObject? get reportsUnnecessary;
90+
@doNotStore
91+
external JSObject? get reportsDeprecated;
92+
}
93+
94+
@JS('DiagnosticWithLocation')
95+
extension type TSDiagnosticWithLocation._(JSObject _) implements TSDiagnostic {
96+
external TSSourceFile file;
97+
external int start;
98+
external int length;
4499
}
45100

46101
@JS('TypeChecker')
@@ -62,3 +117,9 @@ extension type TSNodeArrayCallback<T extends JSAny>._(JSObject _)
62117
implements JSObject {
63118
external T? call(TSNodeArray<TSNode> nodes);
64119
}
120+
121+
@JS('LineAndCharacter')
122+
extension type TSLineAndCharacter._(JSObject _) implements JSObject {
123+
external JSNumber get line;
124+
external JSNumber get character;
125+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// invalid-syntax.d.ts
2+
export interface Person {
3+
name: string
4+
age number
5+
}
6+
interface User {
7+
id: string;
8+
}
9+
export declare class Admin implements User {
10+
constructor(name: string);
11+
toString(): string;
12+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
@TestOn('vm')
6+
library;
7+
8+
import 'dart:convert';
9+
10+
import 'package:path/path.dart' as p;
11+
import 'package:test/test.dart';
12+
import 'package:web_generator/src/cli.dart';
13+
14+
/// Actual test output can be found in `.dart_tool/idl`
15+
void main() {
16+
final bindingsGenPath = p.join('lib', 'src');
17+
group('Interop Gen Integration Test', () {
18+
final testFile = p.join('test', 'assets', 'invalid.d.ts');
19+
final outputFile = p.join('.dart_tool', 'interop_gen', 'invalid.dart');
20+
21+
setUp(() async {
22+
// set up npm
23+
await runProc('npm', ['install'], workingDirectory: bindingsGenPath);
24+
25+
// compile file
26+
await compileDartMain(dir: bindingsGenPath);
27+
});
28+
29+
test('Expect Parsing to Fail', () async {
30+
final inputFilePath = p.relative(testFile, from: bindingsGenPath);
31+
final outputFilePath = p.relative(outputFile, from: bindingsGenPath);
32+
33+
final process = await runProcWithResult(
34+
'node',
35+
[
36+
'main.mjs',
37+
'--input=$inputFilePath',
38+
'--output=$outputFilePath',
39+
'--declaration'
40+
],
41+
workingDirectory: bindingsGenPath);
42+
43+
final stderr = await process.stderr.transform(utf8.decoder).toList();
44+
45+
expect(stderr, isNotEmpty);
46+
expect(await process.exitCode, isNot(0));
47+
});
48+
});
49+
}

0 commit comments

Comments
 (0)