Index: pkg/analyzer/lib/src/dart/sdk/patch.dart |
diff --git a/pkg/analyzer/lib/src/dart/sdk/patch.dart b/pkg/analyzer/lib/src/dart/sdk/patch.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..3a5e5acab8a4e8b00e2c462dce9b2ddb66cd935a |
--- /dev/null |
+++ b/pkg/analyzer/lib/src/dart/sdk/patch.dart |
@@ -0,0 +1,149 @@ |
+// Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library analyzer.src.dart.sdk.patch; |
+ |
+import 'package:analyzer/dart/ast/ast.dart'; |
+import 'package:analyzer/dart/ast/token.dart'; |
+import 'package:analyzer/error/listener.dart'; |
+import 'package:analyzer/file_system/file_system.dart'; |
+import 'package:analyzer/src/dart/scanner/reader.dart'; |
+import 'package:analyzer/src/dart/scanner/scanner.dart'; |
+import 'package:analyzer/src/dart/sdk/sdk.dart'; |
+import 'package:analyzer/src/generated/parser.dart'; |
+import 'package:analyzer/src/generated/sdk.dart'; |
+import 'package:analyzer/src/generated/source.dart'; |
+import 'package:meta/meta.dart'; |
+import 'package:path/src/context.dart'; |
+ |
+/** |
+ * [SdkPatcher] applies patches to SDK [CompilationUnit]. |
+ */ |
+class SdkPatcher { |
+ /** |
+ * Patch the given [unit] of a SDK [source] with the patches defined in |
+ * the [sdk] for the given [platform]. Throw [ArgumentError] if a patch |
+ * file cannot be read, or the contents violates rules for patch files. |
+ * |
+ * If [addNewTopLevelDeclarations] is `true`, then the [unit] is the |
+ * defining unit of a library, so new top-level declarations should be |
+ * added to this unit. For parts new declarations may be added only to the |
+ * patched classes. |
+ * |
+ * TODO(scheglov) auto-detect [addNewTopLevelDeclarations] |
+ */ |
+ void patch(FolderBasedDartSdk sdk, int platform, |
+ AnalysisErrorListener errorListener, Source source, CompilationUnit unit, |
+ {bool addNewTopLevelDeclarations: true}) { |
+ // Prepare the patch files to apply. |
+ List<String> patchPaths; |
+ { |
+ // TODO(scheglov) add support for patching parts |
+ String uriStr = source.uri.toString(); |
+ SdkLibrary sdkLibrary = sdk.getSdkLibrary(uriStr); |
+ if (sdkLibrary == null) { |
+ throw new ArgumentError( |
+ 'The library $uriStr is not defined in the SDK.'); |
+ } |
+ patchPaths = sdkLibrary.getPatches(platform); |
+ } |
+ |
+ bool strongMode = sdk.analysisOptions.strongMode; |
+ Context pathContext = sdk.resourceProvider.pathContext; |
+ for (String path in patchPaths) { |
+ String pathInLib = pathContext.joinAll(path.split('/')); |
+ File patchFile = sdk.libraryDirectory.getChildAssumingFile(pathInLib); |
+ if (!patchFile.exists) { |
+ throw new ArgumentError( |
+ 'The patch file ${patchFile.path} does not exist.'); |
+ } |
+ Source patchSource = patchFile.createSource(); |
+ CompilationUnit patchUnit = parse(patchSource, strongMode, errorListener); |
+ _patchTopLevelDeclarations( |
+ source, unit, patchSource, patchUnit, addNewTopLevelDeclarations); |
+ } |
+ } |
+ |
+ void _failExternalKeyword(Source source, String name, int offset) { |
+ throw new ArgumentError( |
+ 'The keyword "external" was expected for "$name" in $source @ $offset.'); |
+ } |
+ |
+ void _patchTopLevelDeclarations( |
+ Source baseSource, |
+ CompilationUnit baseUnit, |
+ Source patchSource, |
+ CompilationUnit patchUnit, |
+ bool addNewTopLevelDeclarations) { |
+ List<CompilationUnitMember> declarationsToAppend = []; |
+ for (CompilationUnitMember patchDeclaration in patchUnit.declarations) { |
+ if (patchDeclaration is FunctionDeclaration) { |
+ String name = patchDeclaration.name.name; |
+ if (_hasPatchAnnotation(patchDeclaration.metadata)) { |
+ for (CompilationUnitMember baseDeclaration in baseUnit.declarations) { |
+ if (patchDeclaration is FunctionDeclaration && |
+ baseDeclaration is FunctionDeclaration && |
+ baseDeclaration.name.name == name) { |
+ if (_hasPatchAnnotation(patchDeclaration.metadata)) { |
+ // Remove the "external" keyword. |
+ if (baseDeclaration.externalKeyword != null) { |
+ baseDeclaration.externalKeyword = null; |
+ } else { |
+ _failExternalKeyword( |
+ baseSource, name, baseDeclaration.offset); |
+ } |
+ // Replace the body. |
+ baseDeclaration.functionExpression.body = |
+ patchDeclaration.functionExpression.body; |
+ } |
+ } |
+ } |
+ } else if (addNewTopLevelDeclarations) { |
+ // No @patch, must be private. |
+ if (!Identifier.isPrivateName(name)) { |
+ throw new ArgumentError( |
+ 'The patch file $patchSource attempts to append ' |
+ 'a non-private declaration "$name".'); |
+ } |
+ declarationsToAppend.add(patchDeclaration); |
+ } |
+ } |
+ } |
+ // Append new top-level declarations. |
+ baseUnit.declarations.addAll(declarationsToAppend); |
+ } |
+ |
+ /** |
+ * Parse the given [source] into AST. |
+ */ |
+ @visibleForTesting |
+ static CompilationUnit parse( |
+ Source source, bool strong, AnalysisErrorListener errorListener) { |
+ String code = source.contents.data; |
+ |
+ CharSequenceReader reader = new CharSequenceReader(code); |
+ Scanner scanner = new Scanner(source, reader, errorListener); |
+ scanner.scanGenericMethodComments = strong; |
+ Token token = scanner.tokenize(); |
+ LineInfo lineInfo = new LineInfo(scanner.lineStarts); |
+ |
+ Parser parser = new Parser(source, errorListener); |
+ parser.parseGenericMethodComments = strong; |
+ CompilationUnit unit = parser.parseCompilationUnit(token); |
+ unit.lineInfo = lineInfo; |
+ return unit; |
+ } |
+ |
+ /** |
+ * Return `true` if [metadata] has the `@patch` annotation. |
+ */ |
+ static bool _hasPatchAnnotation(List<Annotation> metadata) { |
+ return metadata.any((annotation) { |
+ Identifier name = annotation.name; |
+ return annotation.constructorName == null && |
+ name is SimpleIdentifier && |
+ name.name == 'patch'; |
+ }); |
+ } |
+} |