-
Notifications
You must be signed in to change notification settings - Fork 3.7k
[pigeon] Add support for multiple part files #11664
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,6 +13,7 @@ import 'package:analyzer/dart/analysis/analysis_context_collection.dart' | |
| show AnalysisContextCollection; | ||
| import 'package:analyzer/dart/analysis/results.dart' show ParsedUnitResult; | ||
| import 'package:analyzer/dart/analysis/session.dart' show AnalysisSession; | ||
| import 'package:analyzer/dart/analysis/utilities.dart' show parseString; | ||
| import 'package:analyzer/dart/ast/ast.dart' as dart_ast; | ||
| import 'package:analyzer/diagnostic/diagnostic.dart' show Diagnostic; | ||
| import 'package:args/args.dart'; | ||
|
|
@@ -454,22 +455,69 @@ class Pigeon { | |
| /// [sdkPath] for specifying the Dart SDK path for | ||
| /// [AnalysisContextCollection]. | ||
| ParseResults parseFile(String inputPath, {String? sdkPath}) { | ||
| final includedPaths = <String>[path.absolute(path.normalize(inputPath))]; | ||
| final String normalizedInputPath = path.absolute(path.normalize(inputPath)); | ||
| final String? mainContent = _readFileOrNull(normalizedInputPath); | ||
| if (mainContent == null) { | ||
| return ParseResults( | ||
| root: Root.makeEmpty(), | ||
| errors: <Error>[ | ||
| Error( | ||
| message: 'File $normalizedInputPath does not exist', | ||
| filename: normalizedInputPath, | ||
| ), | ||
| ], | ||
| pigeonOptions: null, | ||
| ); | ||
| } | ||
| final dart_ast.CompilationUnit mainUnit = parseString( | ||
| content: mainContent, | ||
| path: normalizedInputPath, | ||
| throwIfDiagnostics: false, | ||
| ).unit; | ||
| final List<String> parts = _getPartPaths( | ||
| mainUnit.directives, | ||
| sourcePath: normalizedInputPath, | ||
| ); | ||
|
|
||
| final includedPaths = <String>[normalizedInputPath, ...parts]; | ||
|
|
||
| final collection = AnalysisContextCollection( | ||
| includedPaths: includedPaths, | ||
| sdkPath: sdkPath, | ||
| ); | ||
|
|
||
| final compilationErrors = <Error>[]; | ||
| final rootBuilder = RootBuilder(File(inputPath).readAsStringSync()); | ||
| final String? rootInputString = _getInputString( | ||
| inputPath: normalizedInputPath, | ||
| inputContent: mainContent, | ||
| inputUnit: mainUnit, | ||
| partPaths: parts, | ||
| ); | ||
| if (rootInputString == null) { | ||
| return ParseResults( | ||
| root: Root.makeEmpty(), | ||
| errors: <Error>[ | ||
| Error( | ||
| message: | ||
| 'Part file referenced by $normalizedInputPath does not exist', | ||
| filename: normalizedInputPath, | ||
| ), | ||
| ], | ||
| pigeonOptions: null, | ||
| ); | ||
| } | ||
| final rootBuilder = RootBuilder(rootInputString); | ||
| final dart_ast.CompilationUnit mergedUnit = parseString( | ||
| content: rootInputString, | ||
| path: normalizedInputPath, | ||
| throwIfDiagnostics: false, | ||
| ).unit; | ||
| mergedUnit.accept(rootBuilder); | ||
| for (final AnalysisContext context in collection.contexts) { | ||
| for (final String path in context.contextRoot.analyzedFiles()) { | ||
| final AnalysisSession session = context.currentSession; | ||
| final result = session.getParsedUnit(path) as ParsedUnitResult; | ||
| if (result.diagnostics.isEmpty) { | ||
| final dart_ast.CompilationUnit unit = result.unit; | ||
| unit.accept(rootBuilder); | ||
| } else { | ||
| if (result.diagnostics.isNotEmpty) { | ||
| for (final Diagnostic diagnostic in result.diagnostics) { | ||
| compilationErrors.add( | ||
| Error( | ||
|
|
@@ -497,6 +545,117 @@ class Pigeon { | |
| } | ||
| } | ||
|
|
||
| String? _readFileOrNull(String filePath) { | ||
| final file = File(filePath); | ||
| if (!file.existsSync()) { | ||
| return null; | ||
| } | ||
| return file.readAsStringSync(); | ||
| } | ||
|
|
||
| List<String> _getPartPaths( | ||
| Iterable<dart_ast.Directive> directives, { | ||
| required String sourcePath, | ||
| }) { | ||
| final parts = <String>[]; | ||
| for (final directive in directives) { | ||
| if (directive is dart_ast.PartDirective) { | ||
| final String? uri = directive.uri.stringValue; | ||
| if (uri != null) { | ||
| parts.add( | ||
| path.absolute( | ||
| path.normalize(path.join(path.dirname(sourcePath), uri)), | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| return parts; | ||
| } | ||
|
Comment on lines
+532
to
+550
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current implementation of |
||
|
|
||
| ({String body, List<String> imports}) _stripPartsAndCollectImports({ | ||
| required String sourceContent, | ||
| required dart_ast.CompilationUnit unit, | ||
| required bool collectImports, | ||
| }) { | ||
| final imports = <String>[]; | ||
| final removalRanges = <({int start, int end})>[]; | ||
| for (final dart_ast.Directive directive in unit.directives) { | ||
| if (directive is dart_ast.ImportDirective) { | ||
| if (collectImports) { | ||
| imports.add( | ||
| sourceContent | ||
| .substring(directive.offset, directive.end) | ||
| .trimRight(), | ||
| ); | ||
| } | ||
| removalRanges.add((start: directive.offset, end: directive.end)); | ||
| } else if (directive is dart_ast.PartDirective || | ||
| directive is dart_ast.PartOfDirective) { | ||
| removalRanges.add((start: directive.offset, end: directive.end)); | ||
| } | ||
|
Comment on lines
+569
to
+573
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The } else if (directive is dart_ast.PartDirective ||
directive is dart_ast.PartOfDirective ||
directive is dart_ast.LibraryDirective) {
removalRanges.add((start: directive.offset, end: directive.end));
} |
||
| } | ||
|
|
||
| final body = StringBuffer(); | ||
| var start = 0; | ||
| for (final range in removalRanges) { | ||
| body.write(sourceContent.substring(start, range.start)); | ||
| start = range.end; | ||
| } | ||
| body.write(sourceContent.substring(start)); | ||
| return (body: body.toString().trim(), imports: imports); | ||
| } | ||
|
|
||
| String? _getInputString({ | ||
| required String inputPath, | ||
| required String inputContent, | ||
| required dart_ast.CompilationUnit inputUnit, | ||
| required List<String> partPaths, | ||
| }) { | ||
| final ({String body, List<String> imports}) mainResult = | ||
| _stripPartsAndCollectImports( | ||
| sourceContent: inputContent, | ||
| unit: inputUnit, | ||
| collectImports: true, | ||
| ); | ||
| final List<String> imports = mainResult.imports; | ||
|
|
||
| final partBodies = <String>[]; | ||
| for (final partPath in partPaths) { | ||
| final String? partContent = _readFileOrNull(partPath); | ||
| if (partContent == null) { | ||
| return null; | ||
| } | ||
| final dart_ast.CompilationUnit partUnit = parseString( | ||
| content: partContent, | ||
| path: partPath, | ||
| throwIfDiagnostics: false, | ||
| ).unit; | ||
| final ({String body, List<String> imports}) partResult = | ||
| _stripPartsAndCollectImports( | ||
| sourceContent: partContent, | ||
| unit: partUnit, | ||
| collectImports: false, | ||
| ); | ||
| partBodies.add(partResult.body); | ||
| } | ||
|
|
||
| final output = StringBuffer(); | ||
| if (imports.isNotEmpty) { | ||
| output.writeln(imports.join('\n')); | ||
| output.writeln(); | ||
| } | ||
| output.writeln(mainResult.body); | ||
| for (final partBody in partBodies) { | ||
| if (partBody.isNotEmpty) { | ||
| output.writeln(); | ||
| output.writeln(); | ||
| output.write(partBody); | ||
| } | ||
| } | ||
| return output.toString().trimRight(); | ||
| } | ||
|
|
||
| /// String that describes how the tool is used. | ||
| static String get usage { | ||
| return ''' | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Merging the contents of the main file and its parts into a single
rootInputStringcauses a regression in error reporting. Any errors identified byRootBuilder(e.g., invalid types, unsupported constructs) will report line numbers relative to this concatenated string and attribute them to the main file path, making it difficult for users to locate the actual issue in their source files. Consider a strategy that preserves the mapping to original files or avoids string concatenation by visiting multipleCompilationUnits while providing the correct source context for each.