mirror of
https://github.com/dart-lang/sdk
synced 2024-10-01 19:14:49 +00:00
Remove unused SdkPatcher.
Change-Id: I9ce49a8713e528b3376fc447d3b053c8e2b1087d Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/137322 Reviewed-by: Brian Wilkerson <brianwilkerson@google.com> Commit-Queue: Konstantin Shcheglov <scheglov@google.com>
This commit is contained in:
parent
0c7b9cf470
commit
118d220bfa
|
@ -1,473 +0,0 @@
|
|||
// 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.
|
||||
|
||||
import 'package:analyzer/dart/analysis/features.dart';
|
||||
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/source/line_info.dart';
|
||||
import 'package:analyzer/src/dart/scanner/reader.dart';
|
||||
import 'package:analyzer/src/dart/scanner/scanner.dart';
|
||||
import 'package:analyzer/src/generated/parser.dart';
|
||||
import 'package:analyzer/src/generated/source.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/**
|
||||
* [SdkPatcher] applies patches to SDK [CompilationUnit].
|
||||
*/
|
||||
class SdkPatcher {
|
||||
bool _allowNewPublicNames;
|
||||
String _baseDesc;
|
||||
String _patchDesc;
|
||||
CompilationUnit _patchUnit;
|
||||
|
||||
/**
|
||||
* Patch the given [unit] of a SDK [source] with the patches defined in
|
||||
* [allPatchPaths]. Throw [ArgumentError] if a patch
|
||||
* file cannot be read, or the contents violates rules for patch files.
|
||||
*/
|
||||
void patch(
|
||||
ResourceProvider resourceProvider,
|
||||
Map<String, List<String>> allPatchPaths,
|
||||
AnalysisErrorListener errorListener,
|
||||
Source source,
|
||||
CompilationUnit unit) {
|
||||
// Process URI.
|
||||
String libraryUriStr;
|
||||
bool isLibraryDefiningUnit;
|
||||
{
|
||||
Uri uri = source.uri;
|
||||
if (uri.scheme != 'dart') {
|
||||
throw ArgumentError(
|
||||
'The URI of the unit to patch must have the "dart" scheme: $uri');
|
||||
}
|
||||
List<String> uriSegments = uri.pathSegments;
|
||||
String libraryName = uriSegments.first;
|
||||
libraryUriStr = 'dart:$libraryName';
|
||||
isLibraryDefiningUnit = uriSegments.length == 1;
|
||||
_allowNewPublicNames = libraryName == '_internal';
|
||||
}
|
||||
// Prepare the patch files to apply.
|
||||
List<String> patchPaths = allPatchPaths[libraryUriStr] ?? const <String>[];
|
||||
|
||||
for (String path in patchPaths) {
|
||||
File patchFile = resourceProvider.getFile(path);
|
||||
if (!patchFile.exists) {
|
||||
throw ArgumentError(
|
||||
'The patch file ${patchFile.path} for $source does not exist.');
|
||||
}
|
||||
Source patchSource = patchFile.createSource();
|
||||
CompilationUnit patchUnit =
|
||||
parse(patchSource, errorListener, unit.featureSet);
|
||||
|
||||
// Prepare for reporting errors.
|
||||
_baseDesc = source.toString();
|
||||
_patchDesc = patchFile.path;
|
||||
_patchUnit = patchUnit;
|
||||
|
||||
if (isLibraryDefiningUnit) {
|
||||
_patchDirectives(source, unit, patchSource, patchUnit);
|
||||
}
|
||||
_patchTopLevelDeclarations(unit, patchUnit, isLibraryDefiningUnit);
|
||||
}
|
||||
}
|
||||
|
||||
void _failExternalKeyword(String name, int offset) {
|
||||
throw ArgumentError(
|
||||
'The keyword "external" was expected for "$name" in $_baseDesc @ $offset.');
|
||||
}
|
||||
|
||||
void _failIfPublicName(AstNode node, String name) {
|
||||
if (_allowNewPublicNames) {
|
||||
return;
|
||||
}
|
||||
if (!Identifier.isPrivateName(name)) {
|
||||
_failInPatch('contains a public declaration "$name"', node.offset);
|
||||
}
|
||||
}
|
||||
|
||||
void _failInPatch(String message, int offset) {
|
||||
String loc = _getLocationDesc3(_patchUnit, offset);
|
||||
throw ArgumentError(
|
||||
'The patch file $_patchDesc for $_baseDesc $message at $loc.');
|
||||
}
|
||||
|
||||
String _getLocationDesc3(CompilationUnit unit, int offset) {
|
||||
CharacterLocation location = unit.lineInfo.getLocation(offset);
|
||||
return 'the line ${location.lineNumber}';
|
||||
}
|
||||
|
||||
void _matchParameterLists(FormalParameterList baseParameters,
|
||||
FormalParameterList patchParameters, String Function() context) {
|
||||
if (baseParameters == null && patchParameters == null) return;
|
||||
if (baseParameters == null || patchParameters == null) {
|
||||
throw ArgumentError("${context()}, parameter lists don't match");
|
||||
}
|
||||
if (baseParameters.parameters.length != patchParameters.parameters.length) {
|
||||
throw ArgumentError(
|
||||
'${context()}, parameter lists have different lengths');
|
||||
}
|
||||
for (var i = 0; i < baseParameters.parameters.length; i++) {
|
||||
_matchParameters(baseParameters.parameters[i],
|
||||
patchParameters.parameters[i], () => '${context()}, parameter $i');
|
||||
}
|
||||
}
|
||||
|
||||
void _matchParameters(FormalParameter baseParameter,
|
||||
FormalParameter patchParameter, String Function() whichParameter) {
|
||||
if (baseParameter.identifier.name != patchParameter.identifier.name) {
|
||||
throw ArgumentError('${whichParameter()} has different name');
|
||||
}
|
||||
NormalFormalParameter baseParameterWithoutDefault =
|
||||
_withoutDefault(baseParameter);
|
||||
NormalFormalParameter patchParameterWithoutDefault =
|
||||
_withoutDefault(patchParameter);
|
||||
if (baseParameterWithoutDefault is SimpleFormalParameter &&
|
||||
patchParameterWithoutDefault is SimpleFormalParameter) {
|
||||
_matchTypes(baseParameterWithoutDefault.type,
|
||||
patchParameterWithoutDefault.type, () => '${whichParameter()} type');
|
||||
} else if (baseParameterWithoutDefault is FunctionTypedFormalParameter &&
|
||||
patchParameterWithoutDefault is FunctionTypedFormalParameter) {
|
||||
_matchTypes(
|
||||
baseParameterWithoutDefault.returnType,
|
||||
patchParameterWithoutDefault.returnType,
|
||||
() => '${whichParameter()} return type');
|
||||
_matchParameterLists(
|
||||
baseParameterWithoutDefault.parameters,
|
||||
patchParameterWithoutDefault.parameters,
|
||||
() => '${whichParameter()} parameters');
|
||||
} else if (baseParameterWithoutDefault is FieldFormalParameter &&
|
||||
patchParameter is FieldFormalParameter) {
|
||||
throw ArgumentError(
|
||||
'${whichParameter()} cannot be patched (field formal parameters are not supported)');
|
||||
} else {
|
||||
throw ArgumentError(
|
||||
'${whichParameter()} mismatch (different parameter kinds)');
|
||||
}
|
||||
}
|
||||
|
||||
void _matchTypes(
|
||||
TypeName baseType, TypeName patchType, String Function() whichType) {
|
||||
error() => ArgumentError("${whichType()} doesn't match");
|
||||
if (baseType == null && patchType == null) return;
|
||||
if (baseType == null || patchType == null) throw error();
|
||||
// Match up the types token by token; this is more restrictive than strictly
|
||||
// necessary, but it's easy and sufficient for patching purposes.
|
||||
Token baseToken = baseType.beginToken;
|
||||
Token patchToken = patchType.beginToken;
|
||||
while (true) {
|
||||
if (baseToken.lexeme != patchToken.lexeme) throw error();
|
||||
if (identical(baseToken, baseType.endToken) &&
|
||||
identical(patchToken, patchType.endToken)) {
|
||||
break;
|
||||
}
|
||||
if (identical(baseToken, baseType.endToken) ||
|
||||
identical(patchToken, patchType.endToken)) {
|
||||
throw error();
|
||||
}
|
||||
baseToken = baseToken.next;
|
||||
patchToken = patchToken.next;
|
||||
}
|
||||
}
|
||||
|
||||
void _patchClassMembers(
|
||||
ClassDeclaration baseClass, ClassDeclaration patchClass) {
|
||||
String className = baseClass.name.name;
|
||||
List<ClassMember> membersToAppend = [];
|
||||
for (ClassMember patchMember in patchClass.members) {
|
||||
if (patchMember is FieldDeclaration) {
|
||||
if (_hasPatchAnnotation(patchMember.metadata)) {
|
||||
_failInPatch('attempts to patch a field', patchMember.offset);
|
||||
}
|
||||
List<VariableDeclaration> fields = patchMember.fields.variables;
|
||||
if (fields.length != 1) {
|
||||
_failInPatch('contains a field declaration with more than one field',
|
||||
patchMember.offset);
|
||||
}
|
||||
String name = fields[0].name.name;
|
||||
if (!_allowNewPublicNames &&
|
||||
!Identifier.isPrivateName(className) &&
|
||||
!Identifier.isPrivateName(name)) {
|
||||
_failInPatch('contains a public field', patchMember.offset);
|
||||
}
|
||||
membersToAppend.add(patchMember);
|
||||
} else if (patchMember is MethodDeclaration) {
|
||||
String name = patchMember.name.name;
|
||||
if (_hasPatchAnnotation(patchMember.metadata)) {
|
||||
for (ClassMember baseMember in baseClass.members) {
|
||||
if (baseMember is MethodDeclaration &&
|
||||
baseMember.name.name == name) {
|
||||
// Remove the "external" keyword.
|
||||
Token externalKeyword = baseMember.externalKeyword;
|
||||
if (externalKeyword != null) {
|
||||
baseMember.externalKeyword = null;
|
||||
_removeToken(externalKeyword);
|
||||
} else {
|
||||
_failExternalKeyword(name, baseMember.offset);
|
||||
}
|
||||
_matchParameterLists(
|
||||
baseMember.parameters,
|
||||
patchMember.parameters,
|
||||
() => 'While patching $className.$name');
|
||||
_matchTypes(baseMember.returnType, patchMember.returnType,
|
||||
() => 'While patching $className.$name, return type');
|
||||
// Replace the body.
|
||||
FunctionBody oldBody = baseMember.body;
|
||||
FunctionBody newBody = patchMember.body;
|
||||
_replaceNodeTokens(oldBody, newBody);
|
||||
baseMember.body = newBody;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_failIfPublicName(patchMember, name);
|
||||
membersToAppend.add(patchMember);
|
||||
}
|
||||
} else if (patchMember is ConstructorDeclaration) {
|
||||
String name = patchMember.name?.name;
|
||||
if (_hasPatchAnnotation(patchMember.metadata)) {
|
||||
for (ClassMember baseMember in baseClass.members) {
|
||||
if (baseMember is ConstructorDeclaration &&
|
||||
baseMember.name?.name == name) {
|
||||
// Remove the "external" keyword.
|
||||
Token externalKeyword = baseMember.externalKeyword;
|
||||
if (externalKeyword != null) {
|
||||
baseMember.externalKeyword = null;
|
||||
_removeToken(externalKeyword);
|
||||
} else {
|
||||
_failExternalKeyword(name, baseMember.offset);
|
||||
}
|
||||
// Factory vs. generative.
|
||||
if (baseMember.factoryKeyword == null &&
|
||||
patchMember.factoryKeyword != null) {
|
||||
_failInPatch(
|
||||
'attempts to replace generative constructor with a factory one',
|
||||
patchMember.offset);
|
||||
} else if (baseMember.factoryKeyword != null &&
|
||||
patchMember.factoryKeyword == null) {
|
||||
_failInPatch(
|
||||
'attempts to replace factory constructor with a generative one',
|
||||
patchMember.offset);
|
||||
}
|
||||
// The base constructor should not have initializers.
|
||||
if (baseMember.initializers.isNotEmpty) {
|
||||
throw ArgumentError(
|
||||
'Cannot patch external constructors with initializers '
|
||||
'in $_baseDesc.');
|
||||
}
|
||||
_matchParameterLists(
|
||||
baseMember.parameters, patchMember.parameters, () {
|
||||
String nameSuffix = name == null ? '' : '.$name';
|
||||
return 'While patching $className$nameSuffix';
|
||||
});
|
||||
// Prepare nodes.
|
||||
FunctionBody baseBody = baseMember.body;
|
||||
FunctionBody patchBody = patchMember.body;
|
||||
NodeList<ConstructorInitializer> baseInitializers =
|
||||
baseMember.initializers;
|
||||
NodeList<ConstructorInitializer> patchInitializers =
|
||||
patchMember.initializers;
|
||||
// Replace initializers and link tokens.
|
||||
if (patchInitializers.isNotEmpty) {
|
||||
baseMember.parameters.endToken
|
||||
.setNext(patchInitializers.beginToken.previous);
|
||||
baseInitializers.addAll(patchInitializers);
|
||||
patchBody.endToken.setNext(baseBody.endToken.next);
|
||||
} else {
|
||||
_replaceNodeTokens(baseBody, patchBody);
|
||||
}
|
||||
// Replace the body.
|
||||
baseMember.body = patchBody;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (name == null) {
|
||||
if (!_allowNewPublicNames && !Identifier.isPrivateName(className)) {
|
||||
_failInPatch(
|
||||
'contains an unnamed public constructor', patchMember.offset);
|
||||
}
|
||||
} else {
|
||||
_failIfPublicName(patchMember, name);
|
||||
}
|
||||
membersToAppend.add(patchMember);
|
||||
}
|
||||
} else {
|
||||
String className = patchClass.name.name;
|
||||
_failInPatch('contains an unsupported class member in $className',
|
||||
patchMember.offset);
|
||||
}
|
||||
}
|
||||
// Append new class members.
|
||||
_appendToNodeList(
|
||||
baseClass.members, membersToAppend, baseClass.leftBracket);
|
||||
}
|
||||
|
||||
void _patchDirectives(Source baseSource, CompilationUnit baseUnit,
|
||||
Source patchSource, CompilationUnit patchUnit) {
|
||||
for (Directive patchDirective in patchUnit.directives) {
|
||||
if (patchDirective is ImportDirective) {
|
||||
baseUnit.directives.add(patchDirective);
|
||||
} else {
|
||||
_failInPatch('contains an unsupported "$patchDirective" directive',
|
||||
patchDirective.offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _patchTopLevelDeclarations(CompilationUnit baseUnit,
|
||||
CompilationUnit patchUnit, bool appendNewTopLevelDeclarations) {
|
||||
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) {
|
||||
// Remove the "external" keyword.
|
||||
Token externalKeyword = baseDeclaration.externalKeyword;
|
||||
if (externalKeyword != null) {
|
||||
baseDeclaration.externalKeyword = null;
|
||||
_removeToken(externalKeyword);
|
||||
} else {
|
||||
_failExternalKeyword(name, baseDeclaration.offset);
|
||||
}
|
||||
_matchParameterLists(
|
||||
baseDeclaration.functionExpression.parameters,
|
||||
patchDeclaration.functionExpression.parameters,
|
||||
() => 'While patching $name');
|
||||
_matchTypes(
|
||||
baseDeclaration.returnType,
|
||||
patchDeclaration.returnType,
|
||||
() => 'While patching $name, return type');
|
||||
// Replace the body.
|
||||
FunctionExpression oldExpr = baseDeclaration.functionExpression;
|
||||
FunctionBody newBody = patchDeclaration.functionExpression.body;
|
||||
_replaceNodeTokens(oldExpr.body, newBody);
|
||||
oldExpr.body = newBody;
|
||||
}
|
||||
}
|
||||
} else if (appendNewTopLevelDeclarations) {
|
||||
_failIfPublicName(patchDeclaration, name);
|
||||
declarationsToAppend.add(patchDeclaration);
|
||||
}
|
||||
} else if (patchDeclaration is FunctionTypeAlias) {
|
||||
if (patchDeclaration.metadata.isNotEmpty) {
|
||||
_failInPatch('contains a function type alias with an annotation',
|
||||
patchDeclaration.offset);
|
||||
}
|
||||
_failIfPublicName(patchDeclaration, patchDeclaration.name.name);
|
||||
declarationsToAppend.add(patchDeclaration);
|
||||
} else if (patchDeclaration is ClassDeclaration) {
|
||||
if (_hasPatchAnnotation(patchDeclaration.metadata)) {
|
||||
String name = patchDeclaration.name.name;
|
||||
for (CompilationUnitMember baseDeclaration in baseUnit.declarations) {
|
||||
if (baseDeclaration is ClassDeclaration &&
|
||||
baseDeclaration.name.name == name) {
|
||||
_patchClassMembers(baseDeclaration, patchDeclaration);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_failIfPublicName(patchDeclaration, patchDeclaration.name.name);
|
||||
declarationsToAppend.add(patchDeclaration);
|
||||
}
|
||||
} else if (patchDeclaration is TopLevelVariableDeclaration &&
|
||||
!_hasPatchAnnotation(patchDeclaration.metadata)) {
|
||||
for (VariableDeclaration variable
|
||||
in patchDeclaration.variables.variables) {
|
||||
_failIfPublicName(patchDeclaration, variable.name.name);
|
||||
}
|
||||
declarationsToAppend.add(patchDeclaration);
|
||||
} else {
|
||||
_failInPatch('contains an unsupported top-level declaration',
|
||||
patchDeclaration.offset);
|
||||
}
|
||||
}
|
||||
// Append new top-level declarations.
|
||||
if (appendNewTopLevelDeclarations) {
|
||||
_appendToNodeList(baseUnit.declarations, declarationsToAppend,
|
||||
baseUnit.endToken.previous);
|
||||
}
|
||||
}
|
||||
|
||||
NormalFormalParameter _withoutDefault(FormalParameter parameter) {
|
||||
if (parameter is NormalFormalParameter) {
|
||||
return parameter;
|
||||
} else if (parameter is DefaultFormalParameter) {
|
||||
return parameter.parameter;
|
||||
} else {
|
||||
// Should not happen.
|
||||
assert(false);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the given [source] into AST.
|
||||
*/
|
||||
@visibleForTesting
|
||||
static CompilationUnit parse(Source source,
|
||||
AnalysisErrorListener errorListener, FeatureSet featureSet) {
|
||||
String code = source.contents.data;
|
||||
|
||||
CharSequenceReader reader = CharSequenceReader(code);
|
||||
Scanner scanner = Scanner(source, reader, errorListener)
|
||||
..configureFeatures(featureSet);
|
||||
Token token = scanner.tokenize();
|
||||
LineInfo lineInfo = LineInfo(scanner.lineStarts);
|
||||
|
||||
Parser parser = Parser(
|
||||
source,
|
||||
errorListener,
|
||||
languageVersion: scanner.languageVersion,
|
||||
featureSet: featureSet,
|
||||
);
|
||||
CompilationUnit unit = parser.parseCompilationUnit(token);
|
||||
unit.lineInfo = lineInfo;
|
||||
return unit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append [newNodes] to the given [nodes] and attach new tokens to the end
|
||||
* token of the last [nodes] items, or, if it is empty, to [defaultPrevToken].
|
||||
*/
|
||||
static void _appendToNodeList(
|
||||
NodeList<AstNode> nodes, List<AstNode> newNodes, Token defaultPrevToken) {
|
||||
Token prevToken = nodes.endToken ?? defaultPrevToken;
|
||||
for (AstNode newNode in newNodes) {
|
||||
newNode.endToken.setNext(prevToken.next);
|
||||
prevToken.setNext(newNode.beginToken);
|
||||
nodes.add(newNode);
|
||||
prevToken = newNode.endToken;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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';
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the [token] from the stream.
|
||||
*/
|
||||
static void _removeToken(Token token) {
|
||||
token.previous.setNext(token.next);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace tokens of the [oldNode] with tokens of the [newNode].
|
||||
*/
|
||||
static void _replaceNodeTokens(AstNode oldNode, AstNode newNode) {
|
||||
oldNode.beginToken.previous.setNext(newNode.beginToken);
|
||||
newNode.endToken.setNext(oldNode.endToken.next);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -4,13 +4,11 @@
|
|||
|
||||
import 'package:test_reflective_loader/test_reflective_loader.dart';
|
||||
|
||||
import 'patch_test.dart' as patch_test;
|
||||
import 'sdk_test.dart' as sdk;
|
||||
|
||||
/// Utility for manually running all tests.
|
||||
main() {
|
||||
defineReflectiveSuite(() {
|
||||
patch_test.main();
|
||||
sdk.main();
|
||||
}, name: 'sdk');
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue