Chromium Code Reviews| 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..bec6d918209c914615d95bfa0f39e646dced7004 |
| --- /dev/null |
| +++ b/pkg/analyzer/lib/src/dart/sdk/patch.dart |
| @@ -0,0 +1,151 @@ |
| +// 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(DartSdk sdk, int platform, AnalysisErrorListener errorListener, |
| + Source source, CompilationUnit unit, |
| + {bool addNewTopLevelDeclarations: true}) { |
| + if (sdk is FolderBasedDartSdk) { |
|
Paul Berry
2016/10/11 20:24:45
Will sdk ever not be FolderBasedDartSdk? What sho
scheglov
2016/10/11 20:34:08
Done.
|
| + // Prepare the patch files to apply. |
| + List<String> patchPaths; |
| + { |
| + String uriStr = source.uri.toString(); |
| + SdkLibrary sdkLibrary = sdk.getSdkLibrary(uriStr); |
|
Paul Berry
2016/10/11 20:24:45
When patching a file that is a "part" of an SDK li
scheglov
2016/10/11 20:34:08
I will add TODO for now.
|
| + 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'; |
| + }); |
| + } |
| +} |