OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2016, 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 library analyzer.src.dart.sdk.patch; | |
6 | |
7 import 'package:analyzer/dart/ast/ast.dart'; | |
8 import 'package:analyzer/dart/ast/token.dart'; | |
9 import 'package:analyzer/error/listener.dart'; | |
10 import 'package:analyzer/file_system/file_system.dart'; | |
11 import 'package:analyzer/src/dart/scanner/reader.dart'; | |
12 import 'package:analyzer/src/dart/scanner/scanner.dart'; | |
13 import 'package:analyzer/src/dart/sdk/sdk.dart'; | |
14 import 'package:analyzer/src/generated/parser.dart'; | |
15 import 'package:analyzer/src/generated/sdk.dart'; | |
16 import 'package:analyzer/src/generated/source.dart'; | |
17 import 'package:meta/meta.dart'; | |
18 import 'package:path/src/context.dart'; | |
19 | |
20 /** | |
21 * [SdkPatcher] applies patches to SDK [CompilationUnit]. | |
22 */ | |
23 class SdkPatcher { | |
24 /** | |
25 * Patch the given [unit] of a SDK [source] with the patches defined in | |
26 * the [sdk] for the given [platform]. Throw [ArgumentError] if a patch | |
27 * file cannot be read, or the contents violates rules for patch files. | |
28 * | |
29 * If [addNewTopLevelDeclarations] is `true`, then the [unit] is the | |
30 * defining unit of a library, so new top-level declarations should be | |
31 * added to this unit. For parts new declarations may be added only to the | |
32 * patched classes. | |
33 * | |
34 * TODO(scheglov) auto-detect [addNewTopLevelDeclarations] | |
35 */ | |
36 void patch(DartSdk sdk, int platform, AnalysisErrorListener errorListener, | |
37 Source source, CompilationUnit unit, | |
38 {bool addNewTopLevelDeclarations: true}) { | |
39 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.
| |
40 // Prepare the patch files to apply. | |
41 List<String> patchPaths; | |
42 { | |
43 String uriStr = source.uri.toString(); | |
44 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.
| |
45 if (sdkLibrary == null) { | |
46 throw new ArgumentError( | |
47 'The library $uriStr is not defined in the SDK.'); | |
48 } | |
49 patchPaths = sdkLibrary.getPatches(platform); | |
50 } | |
51 | |
52 bool strongMode = sdk.analysisOptions.strongMode; | |
53 Context pathContext = sdk.resourceProvider.pathContext; | |
54 for (String path in patchPaths) { | |
55 String pathInLib = pathContext.joinAll(path.split('/')); | |
56 File patchFile = sdk.libraryDirectory.getChildAssumingFile(pathInLib); | |
57 if (!patchFile.exists) { | |
58 throw new ArgumentError( | |
59 'The patch file ${patchFile.path} does not exist.'); | |
60 } | |
61 Source patchSource = patchFile.createSource(); | |
62 CompilationUnit patchUnit = | |
63 parse(patchSource, strongMode, errorListener); | |
64 _patchTopLevelDeclarations( | |
65 source, unit, patchSource, patchUnit, addNewTopLevelDeclarations); | |
66 } | |
67 } | |
68 } | |
69 | |
70 void _failExternalKeyword(Source source, String name, int offset) { | |
71 throw new ArgumentError( | |
72 'The keyword "external" was expected for "$name" in $source @ $offset.') ; | |
73 } | |
74 | |
75 void _patchTopLevelDeclarations( | |
76 Source baseSource, | |
77 CompilationUnit baseUnit, | |
78 Source patchSource, | |
79 CompilationUnit patchUnit, | |
80 bool addNewTopLevelDeclarations) { | |
81 List<CompilationUnitMember> declarationsToAppend = []; | |
82 for (CompilationUnitMember patchDeclaration in patchUnit.declarations) { | |
83 if (patchDeclaration is FunctionDeclaration) { | |
84 String name = patchDeclaration.name.name; | |
85 if (_hasPatchAnnotation(patchDeclaration.metadata)) { | |
86 for (CompilationUnitMember baseDeclaration in baseUnit.declarations) { | |
87 if (patchDeclaration is FunctionDeclaration && | |
88 baseDeclaration is FunctionDeclaration && | |
89 baseDeclaration.name.name == name) { | |
90 if (_hasPatchAnnotation(patchDeclaration.metadata)) { | |
91 // Remove the "external" keyword. | |
92 if (baseDeclaration.externalKeyword != null) { | |
93 baseDeclaration.externalKeyword = null; | |
94 } else { | |
95 _failExternalKeyword( | |
96 baseSource, name, baseDeclaration.offset); | |
97 } | |
98 // Replace the body. | |
99 baseDeclaration.functionExpression.body = | |
100 patchDeclaration.functionExpression.body; | |
101 } | |
102 } | |
103 } | |
104 } else if (addNewTopLevelDeclarations) { | |
105 // No @patch, must be private. | |
106 if (!Identifier.isPrivateName(name)) { | |
107 throw new ArgumentError( | |
108 'The patch file $patchSource attempts to append ' | |
109 'a non-private declaration "$name".'); | |
110 } | |
111 declarationsToAppend.add(patchDeclaration); | |
112 } | |
113 } | |
114 } | |
115 // Append new top-level declarations. | |
116 baseUnit.declarations.addAll(declarationsToAppend); | |
117 } | |
118 | |
119 /** | |
120 * Parse the given [source] into AST. | |
121 */ | |
122 @visibleForTesting | |
123 static CompilationUnit parse( | |
124 Source source, bool strong, AnalysisErrorListener errorListener) { | |
125 String code = source.contents.data; | |
126 | |
127 CharSequenceReader reader = new CharSequenceReader(code); | |
128 Scanner scanner = new Scanner(source, reader, errorListener); | |
129 scanner.scanGenericMethodComments = strong; | |
130 Token token = scanner.tokenize(); | |
131 LineInfo lineInfo = new LineInfo(scanner.lineStarts); | |
132 | |
133 Parser parser = new Parser(source, errorListener); | |
134 parser.parseGenericMethodComments = strong; | |
135 CompilationUnit unit = parser.parseCompilationUnit(token); | |
136 unit.lineInfo = lineInfo; | |
137 return unit; | |
138 } | |
139 | |
140 /** | |
141 * Return `true` if [metadata] has the `@patch` annotation. | |
142 */ | |
143 static bool _hasPatchAnnotation(List<Annotation> metadata) { | |
144 return metadata.any((annotation) { | |
145 Identifier name = annotation.name; | |
146 return annotation.constructorName == null && | |
147 name is SimpleIdentifier && | |
148 name.name == 'patch'; | |
149 }); | |
150 } | |
151 } | |
OLD | NEW |