mirror of
https://github.com/dart-lang/sdk
synced 2024-10-02 23:49:17 +00:00
[dartdevc] Delete the legacy version of DDC
Change-Id: I2dc3999b0b7e93252402422d662fb5da4dcca3f9 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/127840 Commit-Queue: Nicholas Shahan <nshahan@google.com> Reviewed-by: Sigmund Cherem <sigmund@google.com> Reviewed-by: Mark Zhou <markzipan@google.com>
This commit is contained in:
parent
e51702a7a1
commit
c561a9eacc
14
CHANGELOG.md
14
CHANGELOG.md
|
@ -12,6 +12,20 @@
|
|||
|
||||
### Tools
|
||||
|
||||
#### Dart Dev Compiler (DDC)
|
||||
|
||||
* **Breaking Change**: Deleted the legacy (analyzer based) version of DDC. For
|
||||
additional details see the [announcement].
|
||||
* The `--kernel` option is now ignored and defaults to true. There is no
|
||||
longer any way to invoke the legacy (analyzer based) version of DDC.
|
||||
* Command line arguments that were only used for the legacy DDC have been
|
||||
removed.
|
||||
* The pre-compiled ddc_sdk.js artifacts generated by legacy DDC have
|
||||
been deleted from `dart-sdk/lib/dev_compiler` in favor of the versions
|
||||
located at `dart-sdk/lib/dev_compiler/kernel`.
|
||||
|
||||
[announcement]: https://github.com/dart-lang/sdk/issues/38994
|
||||
|
||||
#### Linter
|
||||
|
||||
The Linter was updated to `0.1.106`, which includes:
|
||||
|
|
|
@ -1,462 +0,0 @@
|
|||
// Copyright (c) 2015, 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/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/standard_ast_factory.dart';
|
||||
import 'package:analyzer/dart/ast/token.dart';
|
||||
import 'package:analyzer/src/dart/ast/token.dart';
|
||||
import 'package:analyzer/src/generated/utilities_dart.dart';
|
||||
|
||||
export 'package:analyzer/dart/ast/standard_ast_factory.dart';
|
||||
|
||||
final ast = AstBuilder();
|
||||
|
||||
class AstBuilder {
|
||||
KeywordToken get constKeyword => KeywordToken(Keyword.CONST, 0);
|
||||
|
||||
TypeName typeName(Identifier id, List<TypeAnnotation> args) {
|
||||
TypeArgumentList argList;
|
||||
if (args != null && args.isNotEmpty) argList = typeArgumentList(args);
|
||||
return astFactory.typeName(id, argList);
|
||||
}
|
||||
|
||||
FunctionTypeAlias functionTypeAlias(TypeName ret, SimpleIdentifier name,
|
||||
List<TypeParameter> tParams, List<FormalParameter> params) {
|
||||
TypeParameterList tps =
|
||||
(tParams.isEmpty) ? null : typeParameterList(tParams);
|
||||
FormalParameterList fps = formalParameterList(params);
|
||||
Token semi = Token(TokenType.SEMICOLON, 0);
|
||||
Token td = KeywordToken(Keyword.TYPEDEF, 0);
|
||||
return astFactory.functionTypeAlias(
|
||||
null, null, td, ret, name, tps, fps, semi);
|
||||
}
|
||||
|
||||
Expression parenthesize(Expression exp) {
|
||||
if (exp is Identifier ||
|
||||
exp is ParenthesizedExpression ||
|
||||
exp is FunctionExpressionInvocation ||
|
||||
exp is MethodInvocation) return exp;
|
||||
return parenthesizedExpression(exp);
|
||||
}
|
||||
|
||||
PropertyAccess propertyAccess(Expression target, SimpleIdentifier name) {
|
||||
var p = Token(TokenType.PERIOD, 0);
|
||||
return astFactory.propertyAccess(target, p, name);
|
||||
}
|
||||
|
||||
MethodInvocation methodInvoke(Expression target, SimpleIdentifier name,
|
||||
TypeArgumentList typeArguments, NodeList<Expression> args) {
|
||||
var p = Token(TokenType.PERIOD, 0);
|
||||
return astFactory.methodInvocation(
|
||||
target, p, name, typeArguments, argumentList(args));
|
||||
}
|
||||
|
||||
TokenType getTokenType(String lexeme) {
|
||||
switch (lexeme) {
|
||||
case "&":
|
||||
return TokenType.AMPERSAND;
|
||||
case "&&":
|
||||
return TokenType.AMPERSAND_AMPERSAND;
|
||||
case "&=":
|
||||
return TokenType.AMPERSAND_EQ;
|
||||
case "@":
|
||||
return TokenType.AT;
|
||||
case "!":
|
||||
return TokenType.BANG;
|
||||
case "!=":
|
||||
return TokenType.BANG_EQ;
|
||||
case "|":
|
||||
return TokenType.BAR;
|
||||
case "||":
|
||||
return TokenType.BAR_BAR;
|
||||
case "|=":
|
||||
return TokenType.BAR_EQ;
|
||||
case ":":
|
||||
return TokenType.COLON;
|
||||
case ",":
|
||||
return TokenType.COMMA;
|
||||
case "^":
|
||||
return TokenType.CARET;
|
||||
case "^=":
|
||||
return TokenType.CARET_EQ;
|
||||
case "}":
|
||||
return TokenType.CLOSE_CURLY_BRACKET;
|
||||
case ")":
|
||||
return TokenType.CLOSE_PAREN;
|
||||
case "]":
|
||||
return TokenType.CLOSE_SQUARE_BRACKET;
|
||||
case "=":
|
||||
return TokenType.EQ;
|
||||
case "==":
|
||||
return TokenType.EQ_EQ;
|
||||
case "=>":
|
||||
return TokenType.FUNCTION;
|
||||
case ">":
|
||||
return TokenType.GT;
|
||||
case ">=":
|
||||
return TokenType.GT_EQ;
|
||||
case ">>":
|
||||
return TokenType.GT_GT;
|
||||
case ">>=":
|
||||
return TokenType.GT_GT_EQ;
|
||||
case ">>>":
|
||||
return TokenType.GT_GT_GT;
|
||||
case ">>>=":
|
||||
return TokenType.GT_GT_GT_EQ;
|
||||
case "#":
|
||||
return TokenType.HASH;
|
||||
case "[]":
|
||||
return TokenType.INDEX;
|
||||
case "[]=":
|
||||
return TokenType.INDEX_EQ;
|
||||
case "<":
|
||||
return TokenType.LT;
|
||||
case "<=":
|
||||
return TokenType.LT_EQ;
|
||||
case "<<":
|
||||
return TokenType.LT_LT;
|
||||
case "<<=":
|
||||
return TokenType.LT_LT_EQ;
|
||||
case "-":
|
||||
return TokenType.MINUS;
|
||||
case "-=":
|
||||
return TokenType.MINUS_EQ;
|
||||
case "--":
|
||||
return TokenType.MINUS_MINUS;
|
||||
case "{":
|
||||
return TokenType.OPEN_CURLY_BRACKET;
|
||||
case "(":
|
||||
return TokenType.OPEN_PAREN;
|
||||
case "[":
|
||||
return TokenType.OPEN_SQUARE_BRACKET;
|
||||
case "%":
|
||||
return TokenType.PERCENT;
|
||||
case "%=":
|
||||
return TokenType.PERCENT_EQ;
|
||||
case ".":
|
||||
return TokenType.PERIOD;
|
||||
case "..":
|
||||
return TokenType.PERIOD_PERIOD;
|
||||
case "+":
|
||||
return TokenType.PLUS;
|
||||
case "+=":
|
||||
return TokenType.PLUS_EQ;
|
||||
case "++":
|
||||
return TokenType.PLUS_PLUS;
|
||||
case "?":
|
||||
return TokenType.QUESTION;
|
||||
case ";":
|
||||
return TokenType.SEMICOLON;
|
||||
case "/":
|
||||
return TokenType.SLASH;
|
||||
case "/=":
|
||||
return TokenType.SLASH_EQ;
|
||||
case "*":
|
||||
return TokenType.STAR;
|
||||
case "*=":
|
||||
return TokenType.STAR_EQ;
|
||||
case "\${":
|
||||
return TokenType.STRING_INTERPOLATION_EXPRESSION;
|
||||
case "\$":
|
||||
return TokenType.STRING_INTERPOLATION_IDENTIFIER;
|
||||
case "~":
|
||||
return TokenType.TILDE;
|
||||
case "~/":
|
||||
return TokenType.TILDE_SLASH;
|
||||
case "~/=":
|
||||
return TokenType.TILDE_SLASH_EQ;
|
||||
case "`":
|
||||
return TokenType.BACKPING;
|
||||
case "\\":
|
||||
return TokenType.BACKSLASH;
|
||||
case "...":
|
||||
return TokenType.PERIOD_PERIOD_PERIOD;
|
||||
case "??":
|
||||
return TokenType.QUESTION_QUESTION;
|
||||
case "??=":
|
||||
return TokenType.QUESTION_QUESTION_EQ;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Token _binaryOperation(String oper) {
|
||||
var type = getTokenType(oper);
|
||||
assert(type != null);
|
||||
return Token(type, 0);
|
||||
}
|
||||
|
||||
BinaryExpression binaryExpression(Expression l, String oper, Expression r) {
|
||||
Token token = _binaryOperation(oper);
|
||||
return astFactory.binaryExpression(l, token, r);
|
||||
}
|
||||
|
||||
ConditionalExpression conditionalExpression(
|
||||
Expression cond, Expression tExp, Expression fExp) {
|
||||
var q = Token(TokenType.QUESTION, 0);
|
||||
var c = Token(TokenType.COLON, 0);
|
||||
return astFactory.conditionalExpression(cond, q, tExp, c, fExp);
|
||||
}
|
||||
|
||||
Expression application(Expression function, List<Expression> es) {
|
||||
ArgumentList args = argumentList(es);
|
||||
return functionExpressionInvocation(function, args);
|
||||
}
|
||||
|
||||
Block block(List<Statement> statements) {
|
||||
Token ld = BeginToken(TokenType.OPEN_CURLY_BRACKET, 0);
|
||||
Token rd = Token(TokenType.CLOSE_CURLY_BRACKET, 0);
|
||||
return astFactory.block(ld, statements, rd);
|
||||
}
|
||||
|
||||
MethodDeclaration blockMethodDeclaration(TypeName rt, SimpleIdentifier m,
|
||||
List<FormalParameter> params, List<Statement> statements,
|
||||
{bool isStatic = false}) {
|
||||
FormalParameterList fl = formalParameterList(params);
|
||||
Block b = block(statements);
|
||||
BlockFunctionBody body = blockFunctionBody(b);
|
||||
return methodDeclaration(rt, m, fl, body, isStatic: isStatic);
|
||||
}
|
||||
|
||||
FunctionDeclaration blockFunctionDeclaration(TypeName rt, SimpleIdentifier f,
|
||||
List<FormalParameter> params, List<Statement> statements) {
|
||||
FunctionExpression fexp = blockFunction(params, statements);
|
||||
return functionDeclaration(rt, f, fexp);
|
||||
}
|
||||
|
||||
FunctionExpression blockFunction(
|
||||
List<FormalParameter> params, List<Statement> statements) {
|
||||
FormalParameterList fl = formalParameterList(params);
|
||||
Block b = block(statements);
|
||||
BlockFunctionBody body = blockFunctionBody(b);
|
||||
return functionExpression(fl, body);
|
||||
}
|
||||
|
||||
FunctionExpression expressionFunction(
|
||||
List<FormalParameter> params, Expression body,
|
||||
[bool decl = false]) {
|
||||
FormalParameterList fl = formalParameterList(params);
|
||||
ExpressionFunctionBody b = expressionFunctionBody(body, decl);
|
||||
return functionExpression(fl, b);
|
||||
}
|
||||
|
||||
FunctionDeclarationStatement functionDeclarationStatement(
|
||||
TypeName rType, SimpleIdentifier name, FunctionExpression fe) {
|
||||
var fd = functionDeclaration(rType, name, fe);
|
||||
return astFactory.functionDeclarationStatement(fd);
|
||||
}
|
||||
|
||||
// let b = e1 in e2 == (\b.e2)(e1)
|
||||
Expression letExpression(FormalParameter b, Expression e1, Expression e2) {
|
||||
FunctionExpression l = expressionFunction(<FormalParameter>[b], e2);
|
||||
return application(parenthesize(l), <Expression>[e1]);
|
||||
}
|
||||
|
||||
FormalParameter requiredFormal(NormalFormalParameter fp) {
|
||||
return requiredFormalParameter(fp);
|
||||
}
|
||||
|
||||
FormalParameter optionalFormal(NormalFormalParameter fp) {
|
||||
return optionalFormalParameter(fp);
|
||||
}
|
||||
|
||||
FormalParameter namedFormal(NormalFormalParameter fp) {
|
||||
return namedFormalParameter(fp);
|
||||
}
|
||||
|
||||
NamedExpression namedParameter(String s, Expression e) {
|
||||
return namedExpression(s, e);
|
||||
}
|
||||
|
||||
NamedExpression namedExpression(String s, Expression e) {
|
||||
Label l =
|
||||
astFactory.label(identifierFromString(s), Token(TokenType.COLON, 0));
|
||||
return astFactory.namedExpression(l, e);
|
||||
}
|
||||
|
||||
/// Declares a single variable `var <name> = <init>` with the type and name
|
||||
/// specified by the VariableElement. See also [variableStatement].
|
||||
VariableDeclarationList declareVariable(SimpleIdentifier name,
|
||||
[Expression init]) {
|
||||
var eqToken = init != null ? Token(TokenType.EQ, 0) : null;
|
||||
var varToken = KeywordToken(Keyword.VAR, 0);
|
||||
return astFactory.variableDeclarationList(null, null, varToken, null,
|
||||
[astFactory.variableDeclaration(name, eqToken, init)]);
|
||||
}
|
||||
|
||||
VariableDeclarationStatement variableStatement(SimpleIdentifier name,
|
||||
[Expression init]) {
|
||||
return variableDeclarationStatement(declareVariable(name, init));
|
||||
}
|
||||
|
||||
InstanceCreationExpression instanceCreation(
|
||||
ConstructorName ctor, List<Expression> args) {
|
||||
var newToken = KeywordToken(Keyword.NEW, 0);
|
||||
return astFactory.instanceCreationExpression(
|
||||
newToken, ctor, argumentList(args));
|
||||
}
|
||||
|
||||
ConstructorName constructorName(TypeName type, [SimpleIdentifier name]) {
|
||||
Token period = name != null ? Token(TokenType.PERIOD, 0) : null;
|
||||
return astFactory.constructorName(type, period, name);
|
||||
}
|
||||
|
||||
SimpleIdentifier identifierFromString(String name) {
|
||||
StringToken token = SyntheticStringToken(TokenType.IDENTIFIER, name, 0);
|
||||
return astFactory.simpleIdentifier(token);
|
||||
}
|
||||
|
||||
PrefixedIdentifier prefixedIdentifier(
|
||||
SimpleIdentifier pre, SimpleIdentifier id) {
|
||||
Token period = Token(TokenType.PERIOD, 0);
|
||||
return astFactory.prefixedIdentifier(pre, period, id);
|
||||
}
|
||||
|
||||
TypeParameter typeParameter(SimpleIdentifier name, [TypeName bound]) {
|
||||
Token keyword = (bound == null) ? null : KeywordToken(Keyword.EXTENDS, 0);
|
||||
return astFactory.typeParameter(null, null, name, keyword, bound);
|
||||
}
|
||||
|
||||
TypeParameterList typeParameterList(List<TypeParameter> params) {
|
||||
Token lb = Token(TokenType.LT, 0);
|
||||
Token rb = Token(TokenType.GT, 0);
|
||||
return astFactory.typeParameterList(lb, params, rb);
|
||||
}
|
||||
|
||||
TypeArgumentList typeArgumentList(List<TypeAnnotation> args) {
|
||||
Token lb = Token(TokenType.LT, 0);
|
||||
Token rb = Token(TokenType.GT, 0);
|
||||
return astFactory.typeArgumentList(lb, args, rb);
|
||||
}
|
||||
|
||||
ArgumentList argumentList(List<Expression> args) {
|
||||
Token lp = BeginToken(TokenType.OPEN_PAREN, 0);
|
||||
Token rp = Token(TokenType.CLOSE_PAREN, 0);
|
||||
return astFactory.argumentList(lp, args, rp);
|
||||
}
|
||||
|
||||
BooleanLiteral booleanLiteral(bool b) {
|
||||
var k = KeywordToken(b ? Keyword.TRUE : Keyword.FALSE, 0);
|
||||
return astFactory.booleanLiteral(k, b);
|
||||
}
|
||||
|
||||
NullLiteral nullLiteral() {
|
||||
var n = KeywordToken(Keyword.NULL, 0);
|
||||
return astFactory.nullLiteral(n);
|
||||
}
|
||||
|
||||
IntegerLiteral integerLiteral(int i) {
|
||||
StringToken token = StringToken(TokenType.INT, '$i', 0);
|
||||
return astFactory.integerLiteral(token, i);
|
||||
}
|
||||
|
||||
SimpleStringLiteral simpleStringLiteral(String s) {
|
||||
StringToken token = StringToken(TokenType.STRING, "\"" + s + "\"", 0);
|
||||
return astFactory.simpleStringLiteral(token, s);
|
||||
}
|
||||
|
||||
SimpleStringLiteral tripleQuotedStringLiteral(String s) {
|
||||
StringToken token = StringToken(TokenType.STRING, '"""' + s + '"""', 0);
|
||||
return astFactory.simpleStringLiteral(token, s);
|
||||
}
|
||||
|
||||
AsExpression asExpression(Expression exp, TypeName type) {
|
||||
Token token = KeywordToken(Keyword.AS, 0);
|
||||
return astFactory.asExpression(exp, token, type);
|
||||
}
|
||||
|
||||
IsExpression isExpression(Expression exp, TypeName type) {
|
||||
Token token = KeywordToken(Keyword.IS, 0);
|
||||
return astFactory.isExpression(exp, token, null, type);
|
||||
}
|
||||
|
||||
ParenthesizedExpression parenthesizedExpression(Expression exp) {
|
||||
Token lp = BeginToken(TokenType.OPEN_PAREN, exp.offset);
|
||||
Token rp = Token(TokenType.CLOSE_PAREN, exp.end);
|
||||
return astFactory.parenthesizedExpression(lp, exp, rp);
|
||||
}
|
||||
|
||||
Expression functionExpressionInvocation(
|
||||
Expression function, ArgumentList es) {
|
||||
return astFactory.functionExpressionInvocation(function, null, es);
|
||||
}
|
||||
|
||||
FormalParameterList formalParameterList(List<FormalParameter> params) {
|
||||
Token lp = BeginToken(TokenType.OPEN_PAREN, 0);
|
||||
Token rp = Token(TokenType.CLOSE_PAREN, 0);
|
||||
bool hasOptional = params.any((p) => p.isPositional);
|
||||
bool hasNamed = params.any((p) => p.isNamed);
|
||||
assert(!(hasOptional && hasNamed));
|
||||
Token ld;
|
||||
Token rd;
|
||||
if (hasOptional) {
|
||||
ld = BeginToken(TokenType.OPEN_SQUARE_BRACKET, 0);
|
||||
rd = Token(TokenType.CLOSE_SQUARE_BRACKET, 0);
|
||||
}
|
||||
if (hasNamed) {
|
||||
ld = BeginToken(TokenType.OPEN_CURLY_BRACKET, 0);
|
||||
rd = Token(TokenType.CLOSE_CURLY_BRACKET, 0);
|
||||
}
|
||||
return astFactory.formalParameterList(lp, params, ld, rd, rp);
|
||||
}
|
||||
|
||||
BlockFunctionBody blockFunctionBody(Block b) {
|
||||
return astFactory.blockFunctionBody(null, null, b);
|
||||
}
|
||||
|
||||
ExpressionFunctionBody expressionFunctionBody(Expression body,
|
||||
[bool decl = false]) {
|
||||
Token semi = (decl) ? Token(TokenType.SEMICOLON, 0) : null;
|
||||
return astFactory.expressionFunctionBody(null, null, body, semi);
|
||||
}
|
||||
|
||||
ExpressionStatement expressionStatement(Expression expression) {
|
||||
Token semi = Token(TokenType.SEMICOLON, 0);
|
||||
return astFactory.expressionStatement(expression, semi);
|
||||
}
|
||||
|
||||
FunctionDeclaration functionDeclaration(
|
||||
TypeName rt, SimpleIdentifier f, FunctionExpression fexp) {
|
||||
return astFactory.functionDeclaration(null, null, null, rt, null, f, fexp);
|
||||
}
|
||||
|
||||
MethodDeclaration methodDeclaration(TypeName rt, SimpleIdentifier m,
|
||||
FormalParameterList fl, FunctionBody body,
|
||||
{bool isStatic = false}) {
|
||||
Token st = isStatic ? KeywordToken(Keyword.STATIC, 0) : null;
|
||||
return astFactory.methodDeclaration(
|
||||
null, null, null, st, rt, null, null, m, null, fl, body);
|
||||
}
|
||||
|
||||
FunctionExpression functionExpression(
|
||||
FormalParameterList fl, FunctionBody body) {
|
||||
return astFactory.functionExpression(null, fl, body);
|
||||
}
|
||||
|
||||
Statement returnExpression([Expression e]) {
|
||||
Token ret = KeywordToken(Keyword.RETURN, 0);
|
||||
Token semi = Token(TokenType.SEMICOLON, 0);
|
||||
return astFactory.returnStatement(ret, e, semi);
|
||||
}
|
||||
|
||||
FormalParameter requiredFormalParameter(NormalFormalParameter fp) {
|
||||
return fp;
|
||||
}
|
||||
|
||||
FormalParameter optionalFormalParameter(NormalFormalParameter fp) {
|
||||
return astFactory.defaultFormalParameter(
|
||||
fp, ParameterKind.POSITIONAL, null, null);
|
||||
}
|
||||
|
||||
FormalParameter namedFormalParameter(NormalFormalParameter fp) {
|
||||
return astFactory.defaultFormalParameter(
|
||||
fp, ParameterKind.NAMED, null, null);
|
||||
}
|
||||
|
||||
VariableDeclarationStatement variableDeclarationStatement(
|
||||
VariableDeclarationList varDecl) {
|
||||
var semi = Token(TokenType.SEMICOLON, 0);
|
||||
return astFactory.variableDeclarationStatement(varDecl, semi);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -1,255 +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 'dart:io';
|
||||
import 'package:analyzer/src/command_line/arguments.dart'
|
||||
show defineAnalysisArguments, ignoreUnrecognizedFlagsFlag;
|
||||
import 'package:analyzer/src/summary/package_bundle_reader.dart'
|
||||
show ConflictingSummaryException;
|
||||
import 'package:args/args.dart' show ArgParser, ArgResults;
|
||||
import 'package:args/command_runner.dart' show UsageException;
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../compiler/shared_command.dart' show CompilerResult;
|
||||
import 'context.dart' show AnalyzerOptions;
|
||||
import 'driver.dart';
|
||||
import 'module_compiler.dart';
|
||||
|
||||
const _binaryName = 'dartdevc';
|
||||
|
||||
bool _verbose = false;
|
||||
|
||||
/// Runs a single compile for dartdevc.
|
||||
///
|
||||
/// This handles argument parsing, usage, error handling.
|
||||
/// See bin/dartdevc.dart for the actual entry point, which includes Bazel
|
||||
/// worker support.
|
||||
CompilerResult compile(List<String> args,
|
||||
{CompilerAnalysisDriver compilerState}) {
|
||||
ArgResults argResults;
|
||||
AnalyzerOptions analyzerOptions;
|
||||
try {
|
||||
var parser = ddcArgParser();
|
||||
if (args.contains('--$ignoreUnrecognizedFlagsFlag')) {
|
||||
args = filterUnknownArguments(args, parser);
|
||||
}
|
||||
argResults = parser.parse(args);
|
||||
analyzerOptions = AnalyzerOptions.fromArguments(argResults);
|
||||
} on FormatException catch (error) {
|
||||
print('$error\n\n$_usageMessage');
|
||||
return CompilerResult(64);
|
||||
}
|
||||
|
||||
_verbose = argResults['verbose'] as bool;
|
||||
if (argResults['help'] as bool || args.isEmpty) {
|
||||
print(_usageMessage);
|
||||
return CompilerResult(0);
|
||||
}
|
||||
|
||||
if (argResults['version'] as bool) {
|
||||
print('$_binaryName version ${_getVersion()}');
|
||||
return CompilerResult(0);
|
||||
}
|
||||
|
||||
try {
|
||||
var driver = _compile(argResults, analyzerOptions);
|
||||
return CompilerResult(0, analyzerState: driver);
|
||||
} on UsageException catch (error) {
|
||||
// Incorrect usage, input file not found, etc.
|
||||
print('${error.message}\n\n$_usageMessage');
|
||||
return CompilerResult(64);
|
||||
} on ConflictingSummaryException catch (error) {
|
||||
// Same input file appears in multiple provided summaries.
|
||||
print(error);
|
||||
return CompilerResult(65);
|
||||
} on CompileErrorException catch (error) {
|
||||
// Code has error(s) and failed to compile.
|
||||
print(error);
|
||||
return CompilerResult(1);
|
||||
} catch (error, stackTrace) {
|
||||
// Anything else is likely a compiler bug.
|
||||
//
|
||||
// --unsafe-force-compile is a bit of a grey area, but it's nice not to
|
||||
// crash while compiling
|
||||
// (of course, output code may crash, if it had errors).
|
||||
//
|
||||
print('''
|
||||
We're sorry, you've found a bug in our compiler.
|
||||
You can report this bug at:
|
||||
https://github.com/dart-lang/sdk/issues/labels/web-dev-compiler
|
||||
Please include the information below in your report, along with
|
||||
any other information that may help us track it down. Thanks!
|
||||
$_binaryName arguments: ${args.join(' ')}
|
||||
dart --version: ${Platform.version}
|
||||
```
|
||||
$error
|
||||
$stackTrace
|
||||
```''');
|
||||
return CompilerResult(70);
|
||||
}
|
||||
}
|
||||
|
||||
ArgParser ddcArgParser(
|
||||
{bool hide = true, bool help = true, ArgParser argParser}) {
|
||||
argParser ??= ArgParser(allowTrailingOptions: true);
|
||||
if (help) {
|
||||
argParser.addFlag('help',
|
||||
abbr: 'h',
|
||||
help: 'Display this message. Add -v to show hidden options.',
|
||||
negatable: false);
|
||||
}
|
||||
argParser
|
||||
..addFlag('verbose',
|
||||
abbr: 'v', negatable: false, help: 'Verbose help output.', hide: hide)
|
||||
..addFlag('version',
|
||||
negatable: false, help: 'Print the $_binaryName version.', hide: hide)
|
||||
..addFlag(ignoreUnrecognizedFlagsFlag,
|
||||
help: 'Ignore unrecognized command line flags.',
|
||||
defaultsTo: false,
|
||||
hide: hide)
|
||||
..addMultiOption('out', abbr: 'o', help: 'Output file (required).');
|
||||
CompilerOptions.addArguments(argParser, hide: hide);
|
||||
defineAnalysisArguments(argParser, hide: hide, ddc: true);
|
||||
AnalyzerOptions.addArguments(argParser, hide: hide);
|
||||
return argParser;
|
||||
}
|
||||
|
||||
bool _changed(List<int> list1, List<int> list2) {
|
||||
var length = list1.length;
|
||||
if (length != list2.length) return true;
|
||||
for (var i = 0; i < length; ++i) {
|
||||
if (list1[i] != list2[i]) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
CompilerAnalysisDriver _compile(
|
||||
ArgResults argResults, AnalyzerOptions analyzerOptions,
|
||||
{CompilerAnalysisDriver compilerDriver}) {
|
||||
var compilerOpts = CompilerOptions.fromArguments(argResults);
|
||||
|
||||
var summaryPaths = compilerOpts.summaryModules.keys.toList();
|
||||
if (compilerDriver == null ||
|
||||
!compilerDriver.isCompatibleWith(analyzerOptions, summaryPaths)) {
|
||||
compilerDriver = CompilerAnalysisDriver(analyzerOptions,
|
||||
summaryPaths: summaryPaths, experiments: compilerOpts.experiments);
|
||||
}
|
||||
var outPaths = argResults['out'] as List<String>;
|
||||
var moduleFormats = compilerOpts.moduleFormats;
|
||||
if (outPaths.isEmpty) {
|
||||
throw UsageException(
|
||||
'Please specify the output file location. For example:\n'
|
||||
' -o PATH/TO/OUTPUT_FILE.js',
|
||||
'');
|
||||
} else if (outPaths.length != moduleFormats.length) {
|
||||
throw UsageException(
|
||||
'Number of output files (${outPaths.length}) must match '
|
||||
'number of module formats (${moduleFormats.length}).',
|
||||
'');
|
||||
}
|
||||
|
||||
var module = compileWithAnalyzer(
|
||||
compilerDriver,
|
||||
argResults.rest,
|
||||
analyzerOptions,
|
||||
compilerOpts,
|
||||
);
|
||||
module.errors.forEach(print);
|
||||
|
||||
if (!module.isValid) {
|
||||
throw compilerOpts.unsafeForceCompile
|
||||
? ForceCompileErrorException()
|
||||
: CompileErrorException();
|
||||
}
|
||||
|
||||
// Write JS file, as well as source map and summary (if requested).
|
||||
for (var i = 0; i < outPaths.length; i++) {
|
||||
module.writeCodeSync(moduleFormats[i], outPaths[i]);
|
||||
}
|
||||
if (compilerOpts.summarizeApi) {
|
||||
var summaryPaths = compilerOpts.summaryOutPath != null
|
||||
? [compilerOpts.summaryOutPath]
|
||||
: outPaths.map((path) =>
|
||||
'${p.withoutExtension(path)}.${compilerOpts.summaryExtension}');
|
||||
|
||||
// place next to every compiled module
|
||||
for (var summaryPath in summaryPaths) {
|
||||
// Only overwrite if summary changed. This plays better with timestamp
|
||||
// based build systems.
|
||||
var file = File(summaryPath);
|
||||
if (!file.existsSync() ||
|
||||
_changed(file.readAsBytesSync(), module.summaryBytes)) {
|
||||
if (!file.parent.existsSync()) file.parent.createSync(recursive: true);
|
||||
file.writeAsBytesSync(module.summaryBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
return compilerDriver;
|
||||
}
|
||||
|
||||
String get _usageMessage =>
|
||||
'The Dart Development Compiler compiles Dart sources into a JavaScript '
|
||||
'module.\n\n'
|
||||
'Usage: $_binaryName [options...] <sources...>\n\n'
|
||||
'${ddcArgParser(hide: !_verbose).usage}';
|
||||
|
||||
String _getVersion() {
|
||||
try {
|
||||
// This is relative to bin/snapshot, so ../..
|
||||
String versionPath = Platform.script.resolve('../../version').toFilePath();
|
||||
File versionFile = File(versionPath);
|
||||
return versionFile.readAsStringSync().trim();
|
||||
} catch (_) {
|
||||
// This happens when the script is not running in the context of an SDK.
|
||||
return "<unknown>";
|
||||
}
|
||||
}
|
||||
|
||||
/// Thrown when the input source code has errors.
|
||||
class CompileErrorException implements Exception {
|
||||
@override
|
||||
toString() => '\nPlease fix all errors before compiling (warnings are okay).';
|
||||
}
|
||||
|
||||
/// Thrown when force compilation failed (probably due to static errors).
|
||||
class ForceCompileErrorException extends CompileErrorException {
|
||||
@override
|
||||
toString() =>
|
||||
'\nForce-compilation not successful. Please check static errors.';
|
||||
}
|
||||
|
||||
// TODO(jmesserly): fix this function in analyzer
|
||||
List<String> filterUnknownArguments(List<String> args, ArgParser parser) {
|
||||
Set<String> knownOptions = Set<String>();
|
||||
Set<String> knownAbbreviations = Set<String>();
|
||||
parser.options.forEach((String name, option) {
|
||||
knownOptions.add(name);
|
||||
String abbreviation = option.abbr;
|
||||
if (abbreviation != null) {
|
||||
knownAbbreviations.add(abbreviation);
|
||||
}
|
||||
});
|
||||
List<String> filtered = <String>[];
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
String argument = args[i];
|
||||
if (argument.startsWith('--') && argument.length > 2) {
|
||||
int equalsOffset = argument.lastIndexOf('=');
|
||||
int end = equalsOffset < 0 ? argument.length : equalsOffset;
|
||||
if (knownOptions.contains(argument.substring(2, end))) {
|
||||
filtered.add(argument);
|
||||
}
|
||||
} else if (argument.startsWith('-') && argument.length > 1) {
|
||||
// TODO(jmesserly): fix this line in analyzer
|
||||
// It was discarding abbreviations such as -Da=b
|
||||
// Abbreviations must be 1-character (this is enforced by ArgParser),
|
||||
// so we don't need to use `optionName`
|
||||
if (knownAbbreviations.contains(argument[1])) {
|
||||
filtered.add(argument);
|
||||
}
|
||||
} else {
|
||||
filtered.add(argument);
|
||||
}
|
||||
}
|
||||
return filtered;
|
||||
}
|
|
@ -1,208 +0,0 @@
|
|||
// Copyright (c) 2015, 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/file_system/file_system.dart'
|
||||
show ResourceProvider, ResourceUriResolver;
|
||||
import 'package:analyzer/file_system/physical_file_system.dart'
|
||||
show PhysicalResourceProvider;
|
||||
import 'package:analyzer/source/custom_resolver.dart';
|
||||
import 'package:analyzer/source/package_map_resolver.dart';
|
||||
import 'package:analyzer/src/command_line/arguments.dart';
|
||||
import 'package:analyzer/src/context/builder.dart';
|
||||
import 'package:analyzer/src/generated/engine.dart' show AnalysisOptionsImpl;
|
||||
import 'package:analyzer/src/generated/sdk.dart';
|
||||
import 'package:analyzer/src/generated/source.dart' hide CustomUriResolver;
|
||||
import 'package:analyzer/src/summary/package_bundle_reader.dart'
|
||||
show InSummarySource, InSummaryUriResolver, SummaryDataStore;
|
||||
import 'package:args/args.dart' show ArgParser, ArgResults;
|
||||
import 'package:cli_util/cli_util.dart' show getSdkPath;
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
/// Options used to set up Source URI resolution in the analysis context.
|
||||
class AnalyzerOptions {
|
||||
final ContextBuilderOptions contextBuilderOptions;
|
||||
|
||||
/// Custom URI mappings, such as "dart:foo" -> "path/to/foo.dart"
|
||||
final Map<String, String> customUrlMappings;
|
||||
|
||||
/// Path to the dart-sdk, or `null` if the path couldn't be determined.
|
||||
final String dartSdkPath;
|
||||
|
||||
/// File resolvers if explicitly configured, otherwise null.
|
||||
List<UriResolver> fileResolvers;
|
||||
|
||||
/// Stores the value of [resourceProvider].
|
||||
ResourceProvider _resourceProvider;
|
||||
|
||||
/// The default analysis root.
|
||||
String analysisRoot = p.current;
|
||||
|
||||
// May be null.
|
||||
final DependencyTracker dependencyTracker;
|
||||
|
||||
AnalyzerOptions._(
|
||||
{this.contextBuilderOptions,
|
||||
String dartSdkPath,
|
||||
this.customUrlMappings = const {},
|
||||
this.dependencyTracker})
|
||||
: dartSdkPath = dartSdkPath ?? getSdkPath() {
|
||||
contextBuilderOptions.declaredVariables ??= const {};
|
||||
}
|
||||
|
||||
factory AnalyzerOptions.basic(
|
||||
{String dartSdkPath, String dartSdkSummaryPath}) {
|
||||
return AnalyzerOptions._(
|
||||
contextBuilderOptions: ContextBuilderOptions()
|
||||
..defaultOptions = (AnalysisOptionsImpl()..previewDart2 = true)
|
||||
..dartSdkSummaryPath = dartSdkSummaryPath,
|
||||
dartSdkPath: dartSdkPath);
|
||||
}
|
||||
|
||||
factory AnalyzerOptions.fromArguments(ArgResults args,
|
||||
{String dartSdkSummaryPath}) {
|
||||
var contextOpts =
|
||||
createContextBuilderOptions(args, trackCacheDependencies: false);
|
||||
(contextOpts.defaultOptions as AnalysisOptionsImpl).previewDart2 = true;
|
||||
|
||||
var dartSdkPath = args['dart-sdk'] as String ?? getSdkPath();
|
||||
dartSdkSummaryPath ??= contextOpts.dartSdkSummaryPath ??
|
||||
p.join(dartSdkPath, 'lib', '_internal', 'ddc_sdk.sum');
|
||||
// For building the SDK, we explicitly set the path to none.
|
||||
if (dartSdkSummaryPath == 'build') dartSdkSummaryPath = null;
|
||||
contextOpts.dartSdkSummaryPath = dartSdkSummaryPath;
|
||||
var summaryDepsOutput = args['summary-deps-output'] as String;
|
||||
var dependencyTracker =
|
||||
summaryDepsOutput != null ? DependencyTracker(summaryDepsOutput) : null;
|
||||
|
||||
return AnalyzerOptions._(
|
||||
contextBuilderOptions: contextOpts,
|
||||
dartSdkPath: dartSdkPath,
|
||||
customUrlMappings:
|
||||
_parseUrlMappings(args['url-mapping'] as List<String>),
|
||||
dependencyTracker: dependencyTracker);
|
||||
}
|
||||
|
||||
static void addArguments(ArgParser parser, {bool hide = true}) {
|
||||
parser.addOption('url-mapping',
|
||||
help: '--url-mapping=libraryUri,/path/to/library.dart uses\n'
|
||||
'library.dart as the source for an import of of "libraryUri".',
|
||||
allowMultiple: true,
|
||||
splitCommas: false,
|
||||
hide: hide);
|
||||
}
|
||||
|
||||
/// Package root when resolving 'package:' urls the standard way.
|
||||
String get packageRoot => contextBuilderOptions.defaultPackagesDirectoryPath;
|
||||
|
||||
/// Resource provider if explicitly set, otherwise this defaults to use
|
||||
/// the file system.
|
||||
ResourceProvider get resourceProvider =>
|
||||
_resourceProvider ??= PhysicalResourceProvider.INSTANCE;
|
||||
|
||||
set resourceProvider(ResourceProvider value) {
|
||||
_resourceProvider = value;
|
||||
}
|
||||
|
||||
/// Path to the dart-sdk summary. If this is set, it will be used in favor
|
||||
/// of the unsummarized one.
|
||||
String get dartSdkSummaryPath => contextBuilderOptions.dartSdkSummaryPath;
|
||||
|
||||
/// Defined variables used by `bool.fromEnvironment` etc.
|
||||
Map<String, String> get declaredVariables =>
|
||||
contextBuilderOptions.declaredVariables;
|
||||
|
||||
ContextBuilder createContextBuilder() {
|
||||
return ContextBuilder(
|
||||
resourceProvider, DartSdkManager(dartSdkPath, true), ContentCache(),
|
||||
options: contextBuilderOptions);
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a SourceFactory configured by the [options].
|
||||
///
|
||||
/// If supplied, [fileResolvers] will override the default `file:` and
|
||||
/// `package:` URI resolvers.
|
||||
SourceFactory createSourceFactory(AnalyzerOptions options,
|
||||
{DartUriResolver sdkResolver, SummaryDataStore summaryData}) {
|
||||
var resourceProvider = options.resourceProvider;
|
||||
var resolvers = <UriResolver>[sdkResolver];
|
||||
if (options.customUrlMappings.isNotEmpty) {
|
||||
resolvers
|
||||
.add(CustomUriResolver(resourceProvider, options.customUrlMappings));
|
||||
}
|
||||
if (summaryData != null) {
|
||||
UriResolver summaryResolver =
|
||||
InSummaryUriResolver(resourceProvider, summaryData);
|
||||
if (options.dependencyTracker != null) {
|
||||
// Wrap summaryResolver.
|
||||
summaryResolver = TrackingInSummaryUriResolver(
|
||||
summaryResolver, options.dependencyTracker);
|
||||
}
|
||||
resolvers.add(summaryResolver);
|
||||
}
|
||||
|
||||
var fileResolvers = options.fileResolvers ?? createFileResolvers(options);
|
||||
resolvers.addAll(fileResolvers);
|
||||
return SourceFactory(resolvers);
|
||||
}
|
||||
|
||||
List<UriResolver> createFileResolvers(AnalyzerOptions options) {
|
||||
var resourceProvider = options.resourceProvider;
|
||||
|
||||
var builderOptions = ContextBuilderOptions();
|
||||
if (options.packageRoot != null) {
|
||||
builderOptions.defaultPackagesDirectoryPath = options.packageRoot;
|
||||
}
|
||||
var builder =
|
||||
ContextBuilder(resourceProvider, null, null, options: builderOptions);
|
||||
|
||||
var packageResolver = PackageMapUriResolver(resourceProvider,
|
||||
builder.convertPackagesToMap(builder.createPackageMap(p.current)));
|
||||
|
||||
return [ResourceUriResolver(resourceProvider), packageResolver];
|
||||
}
|
||||
|
||||
Map<String, String> _parseUrlMappings(List<String> argument) {
|
||||
var mappings = <String, String>{};
|
||||
for (var mapping in argument) {
|
||||
var splitMapping = mapping.split(',');
|
||||
if (splitMapping.length >= 2) {
|
||||
mappings[splitMapping[0]] = p.absolute(splitMapping[1]);
|
||||
}
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
/// A set of path strings read during the build.
|
||||
class DependencyTracker {
|
||||
final _dependencies = Set<String>();
|
||||
|
||||
/// The path to the file to create once tracking is done.
|
||||
final String outputPath;
|
||||
|
||||
DependencyTracker(this.outputPath);
|
||||
|
||||
Iterable<String> get dependencies => _dependencies;
|
||||
|
||||
void record(String path) => _dependencies.add(path);
|
||||
}
|
||||
|
||||
/// Wrapper for [UriResolver] that tracks accesses to summaries.
|
||||
class TrackingInSummaryUriResolver extends UriResolver {
|
||||
final UriResolver _summaryResolver;
|
||||
final DependencyTracker _dependencyTracker;
|
||||
|
||||
TrackingInSummaryUriResolver(this._summaryResolver, this._dependencyTracker);
|
||||
|
||||
@override
|
||||
Source resolveAbsolute(Uri uri, [Uri actualUri]) {
|
||||
var source = _summaryResolver.resolveAbsolute(uri, actualUri);
|
||||
if (source != null && source is InSummarySource) {
|
||||
_dependencyTracker.record(source.summaryPath);
|
||||
}
|
||||
return source;
|
||||
}
|
||||
}
|
|
@ -1,241 +0,0 @@
|
|||
// Copyright (c) 2018, 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 'dart:typed_data';
|
||||
|
||||
import 'package:analyzer/dart/analysis/declared_variables.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/file_system/file_system.dart' show ResourceProvider;
|
||||
import 'package:analyzer/src/dart/analysis/byte_store.dart';
|
||||
import 'package:analyzer/src/dart/analysis/ddc.dart';
|
||||
import 'package:analyzer/src/dart/analysis/driver.dart' show AnalysisDriver;
|
||||
import 'package:analyzer/src/dart/analysis/file_state.dart';
|
||||
import 'package:analyzer/src/dart/analysis/library_analyzer.dart';
|
||||
import 'package:analyzer/src/dart/analysis/performance_logger.dart';
|
||||
import 'package:analyzer/src/dart/element/inheritance_manager3.dart';
|
||||
import 'package:analyzer/src/generated/engine.dart';
|
||||
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
|
||||
import 'package:analyzer/src/generated/sdk.dart';
|
||||
import 'package:analyzer/src/generated/source.dart';
|
||||
import 'package:analyzer/src/summary/package_bundle_reader.dart';
|
||||
import 'package:analyzer/src/summary2/linked_element_factory.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../compiler/shared_command.dart' show sdkLibraryVariables;
|
||||
import 'context.dart' show AnalyzerOptions, createSourceFactory;
|
||||
import 'extension_types.dart' show ExtensionTypeSet;
|
||||
|
||||
/// The analysis driver for `dartdevc`.
|
||||
///
|
||||
/// [linkLibraries] can be used to link input sources and input summaries,
|
||||
/// producing a [LinkedAnalysisDriver] that can analyze those sources.
|
||||
///
|
||||
/// This class can be reused to link different input files if they share the
|
||||
/// same [analysisOptions] and [summaryData].
|
||||
class CompilerAnalysisDriver {
|
||||
/// The Analyzer options used for analyzing the input sources.
|
||||
final AnalysisOptionsImpl analysisOptions;
|
||||
|
||||
/// The input summaries used for analyzing/compiling the input sources.
|
||||
///
|
||||
/// This should contain the summary of all imported/exported libraries and
|
||||
/// transitive dependencies, including the Dart SDK.
|
||||
final SummaryDataStore summaryData;
|
||||
|
||||
final ResourceProvider _resourceProvider;
|
||||
|
||||
final List<String> _summaryPaths;
|
||||
|
||||
@visibleForTesting
|
||||
final DartSdk dartSdk;
|
||||
|
||||
/// SDK summary path, used by [isCompatibleWith] for batch/worker mode.
|
||||
final String _dartSdkSummaryPath;
|
||||
|
||||
ExtensionTypeSet _extensionTypes;
|
||||
|
||||
factory CompilerAnalysisDriver(AnalyzerOptions options,
|
||||
{SummaryDataStore summaryData,
|
||||
List<String> summaryPaths = const [],
|
||||
Map<String, bool> experiments = const {}}) {
|
||||
var resourceProvider = options.resourceProvider;
|
||||
var contextBuilder = options.createContextBuilder();
|
||||
|
||||
var analysisOptions = contextBuilder
|
||||
.getAnalysisOptions(options.analysisRoot) as AnalysisOptionsImpl;
|
||||
|
||||
analysisOptions.enabledExperiments =
|
||||
experiments.entries.where((e) => e.value).map((e) => e.key).toList();
|
||||
|
||||
var dartSdk = contextBuilder.findSdk(null, analysisOptions);
|
||||
|
||||
// Read the summaries.
|
||||
summaryData ??=
|
||||
SummaryDataStore(summaryPaths, resourceProvider: resourceProvider);
|
||||
|
||||
return CompilerAnalysisDriver._(dartSdk, summaryPaths, summaryData,
|
||||
analysisOptions, resourceProvider, options.dartSdkSummaryPath);
|
||||
}
|
||||
|
||||
CompilerAnalysisDriver._(this.dartSdk, this._summaryPaths, this.summaryData,
|
||||
this.analysisOptions, this._resourceProvider, this._dartSdkSummaryPath) {
|
||||
var bundle = dartSdk.getLinkedBundle();
|
||||
if (bundle != null) summaryData.addBundle(null, bundle);
|
||||
}
|
||||
|
||||
/// Information about native extension types.
|
||||
///
|
||||
/// This will be `null` until [linkLibraries] has been called (because we
|
||||
/// could be compiling the Dart SDK, so it would not be available yet).
|
||||
ExtensionTypeSet get extensionTypes => _extensionTypes;
|
||||
|
||||
/// Whether this driver can be reused for the given [dartSdkSummaryPath] and
|
||||
/// [summaryPaths].
|
||||
bool isCompatibleWith(AnalyzerOptions options, List<String> summaryPaths) {
|
||||
return _dartSdkSummaryPath == options.dartSdkSummaryPath &&
|
||||
_summaryPaths.toSet().containsAll(summaryPaths);
|
||||
}
|
||||
|
||||
/// Parses [explicitSources] and any imports/exports/parts (that are not
|
||||
/// included in [summaryData]), and links the results so
|
||||
/// [LinkedAnalysisDriver.analyzeLibrary] can be called.
|
||||
///
|
||||
/// The analyzer [options] are used to configure URI resolution (Analyzer's
|
||||
/// [SourceFactory]) and declared variables, if any (`-Dfoo=bar`).
|
||||
LinkedAnalysisDriver linkLibraries(
|
||||
List<Uri> explicitSources, AnalyzerOptions options) {
|
||||
/// The URI resolution logic for this build unit.
|
||||
var sourceFactory = createSourceFactory(options,
|
||||
sdkResolver: DartUriResolver(dartSdk), summaryData: summaryData);
|
||||
|
||||
var declaredVariables = DeclaredVariables.fromMap(
|
||||
Map.of(options.declaredVariables)..addAll(sdkLibraryVariables));
|
||||
|
||||
/// A fresh file system state for this list of [explicitSources].
|
||||
var fsState = _createFileSystemState(declaredVariables, sourceFactory);
|
||||
|
||||
var resynthesizerBuilder = DevCompilerResynthesizerBuilder(
|
||||
fsState: fsState,
|
||||
analysisOptions: analysisOptions,
|
||||
declaredVariables: declaredVariables,
|
||||
sourceFactory: sourceFactory,
|
||||
summaryData: summaryData,
|
||||
explicitSources: explicitSources,
|
||||
);
|
||||
resynthesizerBuilder.build();
|
||||
|
||||
_extensionTypes ??= ExtensionTypeSet(
|
||||
resynthesizerBuilder.context.typeProvider,
|
||||
resynthesizerBuilder.elementFactory,
|
||||
);
|
||||
|
||||
return LinkedAnalysisDriver(
|
||||
analysisOptions,
|
||||
resynthesizerBuilder.elementFactory,
|
||||
sourceFactory,
|
||||
resynthesizerBuilder.libraryUris,
|
||||
declaredVariables,
|
||||
resynthesizerBuilder.summaryBytes,
|
||||
fsState,
|
||||
_resourceProvider,
|
||||
);
|
||||
}
|
||||
|
||||
FileSystemState _createFileSystemState(
|
||||
DeclaredVariables declaredVariables, SourceFactory sourceFactory) {
|
||||
var unlinkedSalt =
|
||||
Uint32List(1 + AnalysisOptionsImpl.unlinkedSignatureLength);
|
||||
unlinkedSalt[0] = AnalysisDriver.DATA_VERSION;
|
||||
unlinkedSalt.setAll(1, analysisOptions.unlinkedSignature);
|
||||
|
||||
var linkedSalt = Uint32List(1 + AnalysisOptions.signatureLength);
|
||||
linkedSalt[0] = AnalysisDriver.DATA_VERSION;
|
||||
linkedSalt.setAll(1, analysisOptions.signature);
|
||||
|
||||
return FileSystemState(
|
||||
PerformanceLog(StringBuffer()),
|
||||
MemoryByteStore(),
|
||||
FileContentOverlay(),
|
||||
_resourceProvider,
|
||||
'contextName',
|
||||
sourceFactory,
|
||||
analysisOptions,
|
||||
declaredVariables,
|
||||
unlinkedSalt,
|
||||
linkedSalt,
|
||||
externalSummaries: summaryData);
|
||||
}
|
||||
}
|
||||
|
||||
/// The analysis driver used after linking all input summaries and explicit
|
||||
/// sources, produced by [CompilerAnalysisDriver.linkLibraries].
|
||||
class LinkedAnalysisDriver {
|
||||
final AnalysisOptions analysisOptions;
|
||||
final LinkedElementFactory elementFactory;
|
||||
final SourceFactory sourceFactory;
|
||||
final List<String> libraryUris;
|
||||
final DeclaredVariables declaredVariables;
|
||||
|
||||
/// The summary bytes for this linked build unit.
|
||||
final List<int> summaryBytes;
|
||||
|
||||
final FileSystemState _fsState;
|
||||
|
||||
final ResourceProvider _resourceProvider;
|
||||
|
||||
LinkedAnalysisDriver(
|
||||
this.analysisOptions,
|
||||
this.elementFactory,
|
||||
this.sourceFactory,
|
||||
this.libraryUris,
|
||||
this.declaredVariables,
|
||||
this.summaryBytes,
|
||||
this._fsState,
|
||||
this._resourceProvider);
|
||||
|
||||
TypeProvider get typeProvider {
|
||||
return elementFactory.analysisContext.typeProvider;
|
||||
}
|
||||
|
||||
/// Analyzes the library at [uri] and returns the results of analysis for all
|
||||
/// file(s) in that library.
|
||||
Map<FileState, UnitAnalysisResult> analyzeLibrary(String libraryUri) {
|
||||
if (!_isLibraryUri(libraryUri)) {
|
||||
throw ArgumentError('"$libraryUri" is not a library');
|
||||
}
|
||||
|
||||
var analysisContext = elementFactory.analysisContext;
|
||||
var libraryFile = _fsState.getFileForUri(Uri.parse(libraryUri));
|
||||
var analyzer = LibraryAnalyzer(
|
||||
analysisOptions as AnalysisOptionsImpl,
|
||||
declaredVariables,
|
||||
sourceFactory,
|
||||
(uri) => _isLibraryUri('$uri'),
|
||||
analysisContext,
|
||||
elementFactory,
|
||||
InheritanceManager3(analysisContext.typeSystem),
|
||||
libraryFile,
|
||||
_resourceProvider);
|
||||
// TODO(jmesserly): ideally we'd use the existing public `analyze()` method,
|
||||
// but it's async. We can't use `async` here because it would break our
|
||||
// developer tools extension (see web/web_command.dart). We should be able
|
||||
// to fix it, but it requires significant changes to code outside of this
|
||||
// repository.
|
||||
return analyzer.analyzeSync();
|
||||
}
|
||||
|
||||
ClassElement getClass(String uri, String name) {
|
||||
return getLibrary(uri).getType(name);
|
||||
}
|
||||
|
||||
LibraryElement getLibrary(String uri) {
|
||||
return elementFactory.libraryOfUri(uri);
|
||||
}
|
||||
|
||||
/// True if [uri] refers to a Dart library (i.e. a Dart source file exists
|
||||
/// with this uri, and it is not a part file).
|
||||
bool _isLibraryUri(String uri) {
|
||||
return elementFactory.isLibraryUri(uri);
|
||||
}
|
||||
}
|
|
@ -1,428 +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 'dart:collection';
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/nullability_suffix.dart';
|
||||
import 'package:analyzer/dart/element/type.dart'
|
||||
show DartType, InterfaceType, FunctionType;
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/src/dart/element/type.dart';
|
||||
import 'package:analyzer/src/generated/constant.dart'
|
||||
show DartObject, DartObjectImpl;
|
||||
import 'package:analyzer/src/generated/constant.dart';
|
||||
import 'package:analyzer/src/generated/type_system.dart' show TypeSystemImpl;
|
||||
|
||||
import 'type_utilities.dart';
|
||||
|
||||
class Tuple2<T0, T1> {
|
||||
final T0 e0;
|
||||
final T1 e1;
|
||||
Tuple2(this.e0, this.e1);
|
||||
}
|
||||
|
||||
/// Instantiates [t] with dynamic bounds.
|
||||
///
|
||||
/// Note: this should only be used when the resulting type is later replaced,
|
||||
/// such as a "deferred supertype" (used to break circularity between the
|
||||
/// supertype's type arguments and the class definition). Otherwise
|
||||
/// [instantiateElementTypeToBounds] should be used instead.
|
||||
InterfaceType fillDynamicTypeArgsForClass(InterfaceType t) {
|
||||
if (t.typeArguments.isNotEmpty) {
|
||||
var dyn =
|
||||
List.filled(t.element.typeParameters.length, DynamicTypeImpl.instance);
|
||||
return t.element.instantiate(
|
||||
typeArguments: dyn,
|
||||
nullabilitySuffix: NullabilitySuffix.star,
|
||||
);
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
/// Instantiates the [element] type to bounds.
|
||||
///
|
||||
/// Conceptually this is similar to [Dart2TypeSystem.instantiateToBounds] on
|
||||
/// `element.type`, but unfortunately typedef elements do not return a
|
||||
/// meaningful type, so we need to work around that.
|
||||
DartType instantiateElementTypeToBounds(
|
||||
TypeSystemImpl rules, TypeDefiningElement element) {
|
||||
if (element is TypeParameterizedElement) {
|
||||
if (element is ClassElement) {
|
||||
var typeArguments = rules.instantiateTypeFormalsToBounds2(element);
|
||||
return element.instantiate(
|
||||
typeArguments: typeArguments,
|
||||
nullabilitySuffix: NullabilitySuffix.star,
|
||||
);
|
||||
} else if (element is GenericTypeAliasElement) {
|
||||
var typeArguments = rules.instantiateTypeFormalsToBounds2(element);
|
||||
return element.instantiate(
|
||||
typeArguments: typeArguments,
|
||||
nullabilitySuffix: NullabilitySuffix.star,
|
||||
);
|
||||
} else {
|
||||
throw StateError('${element.runtimeType}');
|
||||
}
|
||||
}
|
||||
return getLegacyElementType(element);
|
||||
}
|
||||
|
||||
/// Given an [element] and a [test] function, returns the first matching
|
||||
/// constant valued metadata annotation on the element.
|
||||
///
|
||||
/// If the element is a synthetic getter/setter (Analyzer creates these for
|
||||
/// fields), then this will use the corresponding real element, which will have
|
||||
/// the metadata annotations.
|
||||
///
|
||||
/// For example if we had the [ClassDeclaration] node for `FontElement`:
|
||||
///
|
||||
/// @js.JS('HTMLFontElement')
|
||||
/// @deprecated
|
||||
/// class FontElement { ... }
|
||||
///
|
||||
/// We could match `@deprecated` with a test function like:
|
||||
///
|
||||
/// (v) => v.type.name == 'Deprecated' && v.type.element.library.isDartCore
|
||||
///
|
||||
DartObject findAnnotation(Element element, bool test(DartObjectImpl value)) {
|
||||
if (element == null) return null;
|
||||
var accessor = element;
|
||||
if (accessor is PropertyAccessorElement && accessor.isSynthetic) {
|
||||
// Look for metadata on the real element, not the synthetic one.
|
||||
element = accessor.variable;
|
||||
}
|
||||
for (var metadata in element.metadata) {
|
||||
var value = metadata.computeConstantValue();
|
||||
if (value is DartObjectImpl && test(value)) return value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Searches all supertype, in order of most derived members, to see if any
|
||||
/// [match] a condition. If so, returns the first match, otherwise returns null.
|
||||
InterfaceType findSupertype(InterfaceType type, bool match(InterfaceType t)) {
|
||||
for (var m in type.mixins.reversed) {
|
||||
if (match(m)) return m;
|
||||
}
|
||||
var s = type.superclass;
|
||||
if (s == null) return null;
|
||||
|
||||
if (match(s)) return type;
|
||||
return findSupertype(s, match);
|
||||
}
|
||||
|
||||
//TODO(leafp): Is this really necessary? In theory I think
|
||||
// the static type should always be filled in for resolved
|
||||
// ASTs. This may be a vestigial workaround.
|
||||
DartType getStaticType(Expression e) =>
|
||||
e.staticType ?? DynamicTypeImpl.instance;
|
||||
|
||||
// TODO(leafp) Factor this out or use an existing library
|
||||
/// Similar to [SimpleIdentifier] inGetterContext, inSetterContext, and
|
||||
/// inDeclarationContext, this method returns true if [node] is used in an
|
||||
/// invocation context such as a MethodInvocation.
|
||||
bool inInvocationContext(Expression node) {
|
||||
if (node == null) return false;
|
||||
var parent = node.parent;
|
||||
while (parent is ParenthesizedExpression) {
|
||||
node = parent as Expression;
|
||||
parent = node.parent;
|
||||
}
|
||||
return parent is InvocationExpression && identical(node, parent.function) ||
|
||||
parent is MethodInvocation &&
|
||||
parent.methodName.name == 'call' &&
|
||||
identical(node, parent.target);
|
||||
}
|
||||
|
||||
bool isInlineJS(Element e) {
|
||||
if (e != null && e.name == 'JS' && e is FunctionElement) {
|
||||
var uri = e.librarySource.uri;
|
||||
return uri.scheme == 'dart' && uri.path == '_foreign_helper';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isLibraryPrefix(Expression node) =>
|
||||
node is SimpleIdentifier && node.staticElement is PrefixElement;
|
||||
|
||||
ExecutableElement getFunctionBodyElement(FunctionBody body) {
|
||||
var f = body.parent;
|
||||
if (f is FunctionExpression) {
|
||||
return f.declaredElement;
|
||||
} else if (f is MethodDeclaration) {
|
||||
return f.declaredElement;
|
||||
} else {
|
||||
return (f as ConstructorDeclaration).declaredElement;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value of the `name` field from the [match]ing annotation on
|
||||
/// [element], or `null` if we didn't find a valid match or it was not a string.
|
||||
///
|
||||
/// For example, consider this code:
|
||||
///
|
||||
/// class MyAnnotation {
|
||||
/// final String name;
|
||||
/// // ...
|
||||
/// const MyAnnotation(this.name/*, ... other params ... */);
|
||||
/// }
|
||||
///
|
||||
/// @MyAnnotation('FooBar')
|
||||
/// main() { ... }
|
||||
///
|
||||
/// If we match the annotation for the `@MyAnnotation('FooBar')` this will
|
||||
/// return the string 'FooBar'.
|
||||
String getAnnotationName(Element element, bool match(DartObjectImpl value)) =>
|
||||
findAnnotation(element, match)?.getField('name')?.toStringValue();
|
||||
|
||||
List<ClassElement> getSuperclasses(ClassElement cls) {
|
||||
var result = <ClassElement>[];
|
||||
var visited = HashSet<ClassElement>();
|
||||
while (cls != null && visited.add(cls)) {
|
||||
for (var mixinType in cls.mixins.reversed) {
|
||||
var mixin = mixinType.element;
|
||||
if (mixin != null) result.add(mixin);
|
||||
}
|
||||
var supertype = cls.supertype;
|
||||
// Object or mixin declaration.
|
||||
if (supertype == null) break;
|
||||
|
||||
cls = supertype.element;
|
||||
result.add(cls);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
List<ClassElement> getImmediateSuperclasses(ClassElement c) {
|
||||
var result = <ClassElement>[];
|
||||
for (var m in c.mixins.reversed) {
|
||||
result.add(m.element);
|
||||
}
|
||||
var s = c.supertype;
|
||||
if (s != null) result.add(s.element);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Returns true if the library [l] is dart:_runtime.
|
||||
// TODO(jmesserly): unlike other methods in this file, this one wouldn't be
|
||||
// suitable for upstream to Analyzer, as it's DDC specific.
|
||||
bool isSdkInternalRuntime(LibraryElement l) {
|
||||
var uri = l.source.uri;
|
||||
return uri.scheme == 'dart' && uri.path == '_runtime';
|
||||
}
|
||||
|
||||
/// Return `true` if the given [classElement] has a noSuchMethod() method
|
||||
/// distinct from the one declared in class Object, as per the Dart Language
|
||||
/// Specification (section 10.4).
|
||||
// TODO(jmesserly): this was taken from error_verifier.dart
|
||||
bool hasNoSuchMethod(ClassElement classElement) {
|
||||
// TODO(jmesserly): this is slow in Analyzer. It's a linear scan through all
|
||||
// methods, up through the class hierarchy.
|
||||
var method = classElement.lookUpMethod(
|
||||
FunctionElement.NO_SUCH_METHOD_METHOD_NAME, classElement.library);
|
||||
var definingClass = method?.enclosingElement;
|
||||
return definingClass is ClassElement && !definingClass.isDartCoreObject;
|
||||
}
|
||||
|
||||
/// Returns true if this class is of the form:
|
||||
/// `class C = Object with M [implements I1, I2 ...];`
|
||||
///
|
||||
/// A mixin alias class is a mixin application, that can also be itself used as
|
||||
/// a mixin.
|
||||
bool isMixinAliasClass(ClassElement c) {
|
||||
return c.isMixinApplication && c.supertype.isObject && c.mixins.length == 1;
|
||||
}
|
||||
|
||||
bool isCallableClass(ClassElement c) {
|
||||
// See if we have a "call" with a statically known function type:
|
||||
//
|
||||
// - if it's a method, then it does because all methods do,
|
||||
// - if it's a getter, check the return type.
|
||||
//
|
||||
// Other cases like a getter returning dynamic/Object/Function will be
|
||||
// handled at runtime by the dynamic call mechanism. So we only
|
||||
// concern ourselves with statically known function types.
|
||||
//
|
||||
// We can ignore `noSuchMethod` because:
|
||||
// * `dynamic d; d();` without a declared `call` method is handled by dcall.
|
||||
// * for `class C implements Callable { noSuchMethod(i) { ... } }` we find
|
||||
// the `call` method on the `Callable` interface.
|
||||
var callMethod =
|
||||
getLegacyRawClassType(c).lookUpInheritedGetterOrMethod('call');
|
||||
return callMethod is PropertyAccessorElement
|
||||
? callMethod.returnType is FunctionType
|
||||
: callMethod != null;
|
||||
}
|
||||
|
||||
/// Returns true if [x] and [y] are equal, in other words, `x <: y` and `y <: x`
|
||||
/// and they have equivalent display form when printed.
|
||||
//
|
||||
// TODO(jmesserly): this exists to work around broken FunctionTypeImpl.== in
|
||||
// Analyzer. It has two bugs:
|
||||
// - typeArguments are considered, even though this has no semantic effect.
|
||||
// For example: `int -> int` that resulted from `(<T>(T) -> T)<int>` will not
|
||||
// equal another `int -> int`, even though they are the same type.
|
||||
// - named arguments are incorrectly treated as ordered, see
|
||||
// https://github.com/dart-lang/sdk/issues/26126.
|
||||
bool typesAreEqual(DartType x, DartType y) {
|
||||
if (identical(x, y)) return true;
|
||||
if (x is FunctionType) {
|
||||
if (y is FunctionType) {
|
||||
if (x.typeFormals.length != y.typeFormals.length) {
|
||||
return false;
|
||||
}
|
||||
// `<T>T -> T` should be equal to `<U>U -> U`
|
||||
// To test this, we instantiate both types with the same (unique) type
|
||||
// variables, and see if the result is equal.
|
||||
if (x.typeFormals.isNotEmpty) {
|
||||
var fresh = FunctionTypeImpl.relateTypeFormals(
|
||||
x, y, (t, s, _, __) => typesAreEqual(t, s));
|
||||
if (fresh == null) {
|
||||
return false;
|
||||
}
|
||||
return typesAreEqual(x.instantiate(fresh), y.instantiate(fresh));
|
||||
}
|
||||
|
||||
return typesAreEqual(x.returnType, y.returnType) &&
|
||||
_argumentsAreEqual(x.normalParameterTypes, y.normalParameterTypes) &&
|
||||
_argumentsAreEqual(
|
||||
x.optionalParameterTypes, y.optionalParameterTypes) &&
|
||||
_namedArgumentsAreEqual(x.namedParameterTypes, y.namedParameterTypes);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (x is InterfaceType) {
|
||||
return y is InterfaceType &&
|
||||
x.element == y.element &&
|
||||
_argumentsAreEqual(x.typeArguments, y.typeArguments);
|
||||
}
|
||||
return x == y;
|
||||
}
|
||||
|
||||
bool _argumentsAreEqual(List<DartType> first, List<DartType> second) {
|
||||
if (first.length != second.length) return false;
|
||||
for (int i = 0; i < first.length; i++) {
|
||||
if (!typesAreEqual(first[i], second[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _namedArgumentsAreEqual(
|
||||
Map<String, DartType> xArgs, Map<String, DartType> yArgs) {
|
||||
if (yArgs.length != xArgs.length) return false;
|
||||
for (var name in xArgs.keys) {
|
||||
var x = xArgs[name];
|
||||
var y = yArgs[name];
|
||||
if (y == null || !typesAreEqual(x, y)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Returns a valid hashCode for [t] for use with [typesAreEqual].
|
||||
int typeHashCode(DartType t) {
|
||||
// TODO(jmesserly): this is from Analyzer; it's not a great hash function.
|
||||
// We should at least fix how this combines hashes.
|
||||
if (t is FunctionType) {
|
||||
if (t.typeFormals.isNotEmpty) {
|
||||
// Instantiate the generic function type, so we hash equivalent
|
||||
// generic function types to the same value. For example, `<X>(X) -> X`
|
||||
// and `<Y>(Y) -> Y` shouold have the same hash.
|
||||
//
|
||||
// TODO(jmesserly): it would be better to instantiate these with unique
|
||||
// types for each position, so we can distinguish cases like these:
|
||||
//
|
||||
// <A, B>(A) -> B
|
||||
// <C, D>(D) -> C // reversed
|
||||
// <E, F>(E) -> void // uses `void`
|
||||
//
|
||||
// Currently all of those will have the same hash code.
|
||||
//
|
||||
// The choice of `void` is rather arbitrary. Of the types we can easily
|
||||
// obtain from Analyzer, it should collide a bit less than something like
|
||||
// `dynamic`.
|
||||
int code = t.typeFormals.length;
|
||||
code = (code << 1) +
|
||||
typeHashCode(t.instantiate(
|
||||
List.filled(t.typeFormals.length, VoidTypeImpl.instance)));
|
||||
return code;
|
||||
}
|
||||
int code = typeHashCode(t.returnType);
|
||||
for (var p in t.normalParameterTypes) {
|
||||
code = (code << 1) + typeHashCode(p);
|
||||
}
|
||||
for (var p in t.optionalParameterTypes) {
|
||||
code = (code << 1) + typeHashCode(p);
|
||||
}
|
||||
for (var p in t.namedParameterTypes.values) {
|
||||
code ^= typeHashCode(p); // xor because named parameters are unordered.
|
||||
}
|
||||
return code;
|
||||
}
|
||||
return t.hashCode;
|
||||
}
|
||||
|
||||
Uri uriForCompilationUnit(CompilationUnitElement unit) {
|
||||
if (unit.source.isInSystemLibrary) {
|
||||
return unit.source.uri;
|
||||
}
|
||||
// TODO(jmesserly): this needs serious cleanup.
|
||||
// There does appear to be something strange going on with Analyzer
|
||||
// URIs if we try and use them directly on Windows.
|
||||
// See also compiler.dart placeSourceMap, which could use cleanup too.
|
||||
var sourcePath = unit.source.fullName;
|
||||
return sourcePath.startsWith('package:')
|
||||
? Uri.parse(sourcePath)
|
||||
// TODO(jmesserly): shouldn't this be path.toUri?
|
||||
: Uri.file(sourcePath);
|
||||
}
|
||||
|
||||
/// Returns true iff this factory constructor just throws [UnsupportedError]/
|
||||
///
|
||||
/// `dart:html` has many of these.
|
||||
bool isUnsupportedFactoryConstructor(ConstructorDeclaration node) {
|
||||
var ctorBody = node.body;
|
||||
var element = node.declaredElement;
|
||||
if (element.isPrivate &&
|
||||
element.librarySource.isInSystemLibrary &&
|
||||
ctorBody is BlockFunctionBody) {
|
||||
var statements = ctorBody.block.statements;
|
||||
if (statements.length == 1) {
|
||||
var statement = statements[0];
|
||||
if (statement is ExpressionStatement) {
|
||||
var expr = statement.expression;
|
||||
if (expr is ThrowExpression &&
|
||||
expr.expression is InstanceCreationExpression) {
|
||||
if (expr.expression.staticType.name == 'UnsupportedError') {
|
||||
// HTML adds a lot of private constructors that are unreachable.
|
||||
// Skip these.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isBuiltinAnnotation(
|
||||
DartObjectImpl value, String libraryName, String annotationName) {
|
||||
var e = value?.type?.element;
|
||||
if (e?.name != annotationName) return false;
|
||||
var uri = e.source.uri;
|
||||
var path = uri.pathSegments[0];
|
||||
return uri.scheme == 'dart' && path == libraryName;
|
||||
}
|
||||
|
||||
/// Returns the integer value for [node] as a [BigInt].
|
||||
///
|
||||
/// `node.value` should not be used directly as it depends on platform integers
|
||||
/// and may be `null` for some valid integer literals (in either an `int` or a
|
||||
/// `double` context)
|
||||
BigInt getLiteralBigIntValue(IntegerLiteral node) {
|
||||
// TODO(jmesserly): workaround for #34360: Analyzer tree does not store
|
||||
// the BigInt or double value, so we need to re-parse it from the token.
|
||||
return BigInt.parse(node.literal.lexeme);
|
||||
}
|
|
@ -1,128 +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 'dart:collection';
|
||||
|
||||
import 'package:analyzer/error/error.dart';
|
||||
import 'package:analyzer/source/error_processor.dart' show ErrorProcessor;
|
||||
import 'package:analyzer/source/line_info.dart';
|
||||
import 'package:analyzer/src/error/codes.dart';
|
||||
import 'package:analyzer/src/generated/engine.dart' show AnalysisOptions;
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class ErrorCollector {
|
||||
final bool _replCompile;
|
||||
final AnalysisOptions _options;
|
||||
SplayTreeMap<AnalysisError, String> _errors;
|
||||
|
||||
ErrorCollector(this._options, this._replCompile) {
|
||||
_errors = SplayTreeMap<AnalysisError, String>(_compareErrors);
|
||||
}
|
||||
|
||||
bool get hasFatalErrors => _errors.keys.any(_isFatalError);
|
||||
|
||||
Iterable<String> get formattedErrors => _errors.values;
|
||||
|
||||
void add(LineInfo lineInfo, AnalysisError error) {
|
||||
if (_shouldIgnoreError(error)) return;
|
||||
|
||||
// Skip hints, some like TODOs are not useful.
|
||||
if (_errorSeverity(error).ordinal <= ErrorSeverity.INFO.ordinal) return;
|
||||
|
||||
_errors[error] = _formatError(lineInfo, error);
|
||||
}
|
||||
|
||||
void addAll(LineInfo lineInfo, Iterable<AnalysisError> errors) {
|
||||
for (var e in errors) {
|
||||
add(lineInfo, e);
|
||||
}
|
||||
}
|
||||
|
||||
ErrorSeverity _errorSeverity(AnalysisError error) {
|
||||
var errorCode = error.errorCode;
|
||||
if (errorCode == StrongModeCode.TOP_LEVEL_FUNCTION_LITERAL_BLOCK ||
|
||||
errorCode == StrongModeCode.TOP_LEVEL_INSTANCE_GETTER ||
|
||||
errorCode == StrongModeCode.TOP_LEVEL_INSTANCE_METHOD) {
|
||||
// These are normally hints, but they should be errors when running DDC, so
|
||||
// that users won't be surprised by behavioral differences between DDC and
|
||||
// dart2js.
|
||||
return ErrorSeverity.ERROR;
|
||||
}
|
||||
|
||||
// TODO(jmesserly): remove support for customizing error levels via
|
||||
// analysis_options from DDC. (it won't work with --kernel).
|
||||
return ErrorProcessor.getProcessor(_options, error)?.severity ??
|
||||
errorCode.errorSeverity;
|
||||
}
|
||||
|
||||
String _formatError(LineInfo lineInfo, AnalysisError error) {
|
||||
var location = lineInfo.getLocation(error.offset);
|
||||
|
||||
// [warning] 'foo' is not a... (/Users/.../tmp/foo.dart, line 1, col 2)
|
||||
return (StringBuffer()
|
||||
..write('[${_errorSeverity(error).displayName}] ')
|
||||
..write(error.message)
|
||||
..write(' (${p.prettyUri(error.source.uri)}')
|
||||
..write(
|
||||
', line ${location.lineNumber}, col ${location.columnNumber})'))
|
||||
.toString();
|
||||
}
|
||||
|
||||
bool _shouldIgnoreError(AnalysisError error) {
|
||||
var uri = error.source.uri;
|
||||
if (uri.scheme != 'dart') return false;
|
||||
var sdkLib = uri.pathSegments[0];
|
||||
if (sdkLib == 'html' || sdkLib == 'svg' || sdkLib == '_interceptors') {
|
||||
var c = error.errorCode;
|
||||
return c == StaticWarningCode.FINAL_NOT_INITIALIZED_CONSTRUCTOR_1 ||
|
||||
c == StaticWarningCode.FINAL_NOT_INITIALIZED_CONSTRUCTOR_2 ||
|
||||
c == StaticWarningCode.FINAL_NOT_INITIALIZED_CONSTRUCTOR_3_PLUS;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int _compareErrors(AnalysisError error1, AnalysisError error2) {
|
||||
// severity
|
||||
int compare = _errorSeverity(error2).compareTo(_errorSeverity(error1));
|
||||
if (compare != 0) return compare;
|
||||
|
||||
// path
|
||||
compare = Comparable.compare(error1.source.fullName.toLowerCase(),
|
||||
error2.source.fullName.toLowerCase());
|
||||
if (compare != 0) return compare;
|
||||
|
||||
// offset
|
||||
compare = error1.offset - error2.offset;
|
||||
if (compare != 0) return compare;
|
||||
|
||||
// compare message, in worst case.
|
||||
return error1.message.compareTo(error2.message);
|
||||
}
|
||||
|
||||
bool _isFatalError(AnalysisError e) {
|
||||
if (_errorSeverity(e) != ErrorSeverity.ERROR) return false;
|
||||
|
||||
// These errors are not fatal in the REPL compile mode as we
|
||||
// allow access to private members across library boundaries
|
||||
// and those accesses will show up as undefined members unless
|
||||
// additional analyzer changes are made to support them.
|
||||
// TODO(jacobr): consider checking that the identifier name
|
||||
// referenced by the error is private.
|
||||
return !_replCompile ||
|
||||
(e.errorCode != StaticTypeWarningCode.UNDEFINED_GETTER &&
|
||||
e.errorCode != StaticTypeWarningCode.UNDEFINED_SETTER &&
|
||||
e.errorCode != StaticTypeWarningCode.UNDEFINED_METHOD);
|
||||
}
|
||||
}
|
||||
|
||||
const invalidImportDartMirrors = StrongModeCode(
|
||||
ErrorType.COMPILE_TIME_ERROR,
|
||||
'IMPORT_DART_MIRRORS',
|
||||
'Cannot import "dart:mirrors" in web applications (https://goo.gl/R1anEs).');
|
||||
|
||||
const invalidJSInteger = StrongModeCode(
|
||||
ErrorType.COMPILE_TIME_ERROR,
|
||||
'INVALID_JS_INTEGER',
|
||||
"The integer literal '{0}' can't be represented exactly in JavaScript. "
|
||||
"The nearest value that can be represented exactly is '{1}'.");
|
|
@ -1,177 +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 'dart:collection';
|
||||
|
||||
import 'package:analyzer/dart/element/element.dart'
|
||||
show ClassElement, CompilationUnitElement, Element, LibraryElement;
|
||||
import 'package:analyzer/dart/element/type.dart' show DartType, InterfaceType;
|
||||
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
|
||||
import 'package:analyzer/src/summary2/linked_element_factory.dart';
|
||||
|
||||
import 'element_helpers.dart' show getAnnotationName, isBuiltinAnnotation;
|
||||
import 'type_utilities.dart';
|
||||
|
||||
/// Contains information about native JS types (those types provided by the
|
||||
/// implementation) that are also provided by the Dart SDK.
|
||||
///
|
||||
/// For types provided by JavaScript, it is important that we don't add methods
|
||||
/// directly to those types. Instead, we must call through a special set of
|
||||
/// JS Symbol names, that are used for the "Dart extensions". For example:
|
||||
///
|
||||
/// // Dart
|
||||
/// Iterable iter = myList;
|
||||
/// print(iter.first);
|
||||
///
|
||||
/// // JS
|
||||
/// let iter = myLib.myList;
|
||||
/// core.print(iter[dartx.first]);
|
||||
///
|
||||
/// This will provide the [Iterable.first] property, without needing to add
|
||||
/// `first` to the `Array.prototype`.
|
||||
class ExtensionTypeSet {
|
||||
final LinkedElementFactory _elementFactory;
|
||||
|
||||
// Abstract types that may be implemented by both native and non-native
|
||||
// classes.
|
||||
final _extensibleTypes = HashSet<ClassElement>();
|
||||
|
||||
// Concrete native types.
|
||||
final _nativeTypes = HashSet<ClassElement>();
|
||||
final _pendingLibraries = HashSet<String>();
|
||||
|
||||
ExtensionTypeSet(TypeProvider types, this._elementFactory) {
|
||||
// TODO(vsm): Eventually, we want to make this extensible - i.e., find
|
||||
// annotations in user code as well. It would need to be summarized in
|
||||
// the element model - not searched this way on every compile. To make this
|
||||
// a little more efficient now, we do this in two phases.
|
||||
|
||||
// First, core types:
|
||||
// TODO(vsm): If we're analyzing against the main SDK, those
|
||||
// types are not explicitly annotated.
|
||||
_extensibleTypes.add(types.objectType.element);
|
||||
_addExtensionType(types.intType, true);
|
||||
_addExtensionType(types.doubleType, true);
|
||||
_addExtensionType(types.boolType, true);
|
||||
_addExtensionType(types.stringType, true);
|
||||
_addExtensionTypes('dart:_interceptors');
|
||||
_addExtensionTypes('dart:_native_typed_data');
|
||||
|
||||
// These are used natively by dart:html but also not annotated.
|
||||
_addExtensionTypesForLibrary('dart:core', ['Comparable', 'Map']);
|
||||
_addExtensionTypesForLibrary('dart:collection', ['ListMixin', 'MapMixin']);
|
||||
_addExtensionTypesForLibrary('dart:math', ['Rectangle']);
|
||||
|
||||
// Second, html types - these are only searched if we use dart:html, etc.:
|
||||
_addPendingExtensionTypes('dart:html');
|
||||
_addPendingExtensionTypes('dart:indexed_db');
|
||||
_addPendingExtensionTypes('dart:svg');
|
||||
_addPendingExtensionTypes('dart:web_audio');
|
||||
_addPendingExtensionTypes('dart:web_gl');
|
||||
_addPendingExtensionTypes('dart:web_sql');
|
||||
}
|
||||
|
||||
/// Gets the JS peer for this Dart type if any, otherwise null.
|
||||
///
|
||||
/// For example for dart:_interceptors `JSArray` this will return "Array",
|
||||
/// referring to the JavaScript built-in `Array` type.
|
||||
List<String> getNativePeers(ClassElement classElem) {
|
||||
if (classElem.isDartCoreObject) return ['Object'];
|
||||
var names = getAnnotationName(
|
||||
classElem,
|
||||
(a) =>
|
||||
isBuiltinAnnotation(a, '_js_helper', 'JsPeerInterface') ||
|
||||
isBuiltinAnnotation(a, '_js_helper', 'Native'));
|
||||
if (names == null) return [];
|
||||
|
||||
// Omit the special name "!nonleaf" and any future hacks starting with "!"
|
||||
return names.split(',').where((peer) => !peer.startsWith("!")).toList();
|
||||
}
|
||||
|
||||
bool hasNativeSubtype(DartType type) =>
|
||||
isNativeInterface(type.element) || isNativeClass(type.element);
|
||||
|
||||
bool isNativeClass(Element element) => _setContains(_nativeTypes, element);
|
||||
|
||||
bool isNativeInterface(Element element) =>
|
||||
_setContains(_extensibleTypes, element);
|
||||
|
||||
void _addExtensionType(InterfaceType t, [bool mustBeNative = false]) {
|
||||
if (t.isObject) return;
|
||||
var element = t.element;
|
||||
if (_extensibleTypes.contains(element) || _nativeTypes.contains(element)) {
|
||||
return;
|
||||
}
|
||||
bool isNative = mustBeNative || _isNative(element);
|
||||
if (isNative) {
|
||||
_nativeTypes.add(element);
|
||||
} else {
|
||||
_extensibleTypes.add(element);
|
||||
}
|
||||
element.interfaces.forEach(_addExtensionType);
|
||||
element.mixins.forEach(_addExtensionType);
|
||||
var supertype = element.supertype;
|
||||
if (supertype != null) _addExtensionType(element.supertype);
|
||||
}
|
||||
|
||||
void _addExtensionTypes(String libraryUri) {
|
||||
var library = _getLibraryByUri(libraryUri);
|
||||
_visitCompilationUnit(library.definingCompilationUnit);
|
||||
library.parts.forEach(_visitCompilationUnit);
|
||||
}
|
||||
|
||||
void _addExtensionTypesForLibrary(String libraryUri, List<String> typeNames) {
|
||||
var library = _getLibraryByUri(libraryUri);
|
||||
for (var typeName in typeNames) {
|
||||
_addExtensionType(getLegacyRawClassType(library.getType(typeName)));
|
||||
}
|
||||
}
|
||||
|
||||
void _addPendingExtensionTypes(String libraryUri) {
|
||||
_pendingLibraries.add(libraryUri);
|
||||
}
|
||||
|
||||
LibraryElement _getLibraryByUri(String uriStr) {
|
||||
return _elementFactory.libraryOfUri(uriStr);
|
||||
}
|
||||
|
||||
bool _isNative(ClassElement element) {
|
||||
for (var metadata in element.metadata) {
|
||||
var e = metadata.element?.enclosingElement;
|
||||
if (e.name == 'Native' || e.name == 'JsPeerInterface') {
|
||||
if (e.source.isInSystemLibrary) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _processPending(Element element) {
|
||||
if (_pendingLibraries.isEmpty) return false;
|
||||
if (element is ClassElement) {
|
||||
var uri = element.library.source.uri.toString();
|
||||
if (_pendingLibraries.contains(uri)) {
|
||||
// Load all pending libraries
|
||||
_pendingLibraries.forEach(_addExtensionTypes);
|
||||
_pendingLibraries.clear();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _setContains(HashSet<ClassElement> set, Element element) {
|
||||
return set.contains(element) ||
|
||||
_processPending(element) && set.contains(element);
|
||||
}
|
||||
|
||||
void _visitClass(ClassElement element) {
|
||||
if (_isNative(element)) {
|
||||
_addExtensionType(getLegacyRawClassType(element), true);
|
||||
}
|
||||
}
|
||||
|
||||
void _visitCompilationUnit(CompilationUnitElement unit) {
|
||||
unit.types.forEach(_visitClass);
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
// Copyright (c) 2015, 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/ast/ast.dart';
|
||||
import 'package:analyzer/src/generated/constant.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
|
||||
import 'element_helpers.dart';
|
||||
|
||||
bool _isJsLibType(String expectedName, Element e) =>
|
||||
e?.name == expectedName && _isJsLib(e.library);
|
||||
|
||||
/// Returns true if [e] represents any library from `package:js` or is the
|
||||
/// internal `dart:_js_helper` library.
|
||||
bool _isJsLib(LibraryElement e) {
|
||||
if (e == null) return false;
|
||||
var uri = e.source.uri;
|
||||
if (uri.scheme == 'package' && uri.path.startsWith('js/')) return true;
|
||||
if (uri.scheme == 'dart') {
|
||||
// TODO(jmesserly): this needs cleanup: many of the annotations don't exist
|
||||
// in these libraries.
|
||||
return uri.path == '_js_helper' || uri.path == '_foreign_helper';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Whether [value] is a `@rest` annotation (to be used on function parameters
|
||||
/// to have them compiled as `...` rest params in ES6 outputs).
|
||||
bool isJsRestAnnotation(DartObjectImpl value) =>
|
||||
_isJsLibType('_Rest', value.type.element);
|
||||
|
||||
/// Whether [i] is a `spread` invocation (to be used on function arguments
|
||||
/// to have them compiled as `...` spread args in ES6 outputs).
|
||||
bool isJsSpreadInvocation(MethodInvocation i) =>
|
||||
_isJsLibType('spread', i.methodName?.staticElement);
|
||||
|
||||
// TODO(jmesserly): Move JsPeerInterface to package:js (see issue #135).
|
||||
// TODO(jacobr): The 'JS' annotation is the new, publically accessible one.
|
||||
// The 'JsName' annotation is the old one using internally by dart2js and
|
||||
// html libraries. These two concepts will probably merge eventually.
|
||||
bool isJSAnnotation(DartObjectImpl value) =>
|
||||
_isJsLibType('JS', value.type.element) || isJSName(value);
|
||||
|
||||
/// Returns [true] if [e] is the `JS` annotation from `package:js`.
|
||||
bool isPublicJSAnnotation(DartObjectImpl value) =>
|
||||
_isJsLibType('JS', value.type.element);
|
||||
|
||||
bool isJSAnonymousAnnotation(DartObjectImpl value) =>
|
||||
_isJsLibType('_Anonymous', value.type.element);
|
||||
|
||||
/// Whether [value] is a `@JSExportName` (internal annotation used in SDK
|
||||
/// instead of `@JS` from `package:js`).
|
||||
bool isJSExportNameAnnotation(DartObjectImpl value) =>
|
||||
isBuiltinAnnotation(value, '_foreign_helper', 'JSExportName');
|
||||
|
||||
/// Whether [value] is a `@JSName` (internal annotation used in dart:html for
|
||||
/// renaming members).
|
||||
bool isJSName(DartObjectImpl value) =>
|
||||
isBuiltinAnnotation(value, '_js_helper', 'JSName');
|
||||
|
||||
bool isNotNullAnnotation(DartObjectImpl value) =>
|
||||
isBuiltinAnnotation(value, '_js_helper', '_NotNull');
|
||||
|
||||
bool isNullCheckAnnotation(DartObjectImpl value) =>
|
||||
isBuiltinAnnotation(value, '_js_helper', '_NullCheck');
|
||||
|
||||
bool isUndefinedAnnotation(DartObjectImpl value) =>
|
||||
isBuiltinAnnotation(value, '_js_helper', '_Undefined');
|
||||
|
||||
/// Returns the name value of the `JSExportName` annotation (when compiling
|
||||
/// the SDK), or `null` if there's none. This is used to control the name
|
||||
/// under which functions are compiled and exported.
|
||||
String getJSExportName(Element e) {
|
||||
if (!e.source.isInSystemLibrary) return null;
|
||||
|
||||
e = e is PropertyAccessorElement && e.isSynthetic ? e.variable : e;
|
||||
return getAnnotationName(e, isJSExportNameAnnotation);
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
// Copyright (c) 2017, 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/element/element.dart' show ClassElement;
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
|
||||
import 'package:analyzer/src/generated/type_system.dart' show TypeSystemImpl;
|
||||
|
||||
import '../compiler/js_typerep.dart';
|
||||
import 'driver.dart';
|
||||
import 'type_utilities.dart';
|
||||
|
||||
class JSTypeRep extends SharedJSTypeRep<DartType> {
|
||||
final TypeSystemImpl rules;
|
||||
final TypeProvider types;
|
||||
|
||||
final ClassElement _jsBool;
|
||||
final ClassElement _jsNumber;
|
||||
final ClassElement _jsString;
|
||||
|
||||
JSTypeRep(this.rules, LinkedAnalysisDriver driver)
|
||||
: types = driver.typeProvider,
|
||||
_jsBool = driver.getClass('dart:_interceptors', 'JSBool'),
|
||||
_jsString = driver.getClass('dart:_interceptors', 'JSString'),
|
||||
_jsNumber = driver.getClass('dart:_interceptors', 'JSNumber');
|
||||
|
||||
@override
|
||||
JSType typeFor(DartType type) {
|
||||
while (type is TypeParameterType) {
|
||||
type = (type as TypeParameterType).element.bound;
|
||||
}
|
||||
if (type == null) return JSType.jsUnknown;
|
||||
if (type.isDartCoreNull) return JSType.jsNull;
|
||||
// Note that this should be changed if Dart gets non-nullable types
|
||||
if (type.isBottom) return JSType.jsNull;
|
||||
if (rules.isSubtypeOf(type, types.numType)) return JSType.jsNumber;
|
||||
if (rules.isSubtypeOf(type, types.boolType)) return JSType.jsBoolean;
|
||||
if (rules.isSubtypeOf(type, types.stringType)) return JSType.jsString;
|
||||
if (type.isDartAsyncFutureOr) {
|
||||
var argument = (type as InterfaceType).typeArguments[0];
|
||||
var argumentRep = typeFor(argument);
|
||||
if (argumentRep is JSObject || argumentRep is JSNull) {
|
||||
return JSType.jsObject;
|
||||
}
|
||||
return JSType.jsUnknown;
|
||||
}
|
||||
if (type.isDynamic || type.isObject || type.isVoid) return JSType.jsUnknown;
|
||||
return JSType.jsObject;
|
||||
}
|
||||
|
||||
/// Given a Dart type return the known implementation type, if any.
|
||||
/// Given `bool`, `String`, or `num`/`int`/`double`,
|
||||
/// returns the corresponding type in `dart:_interceptors`:
|
||||
/// `JSBool`, `JSString`, and `JSNumber` respectively, otherwise null.
|
||||
InterfaceType getImplementationType(DartType t) {
|
||||
var rep = typeFor(t);
|
||||
// Number, String, and Bool are final
|
||||
if (rep == JSType.jsNumber) return getLegacyRawClassType(_jsNumber);
|
||||
if (rep == JSType.jsBoolean) return getLegacyRawClassType(_jsBool);
|
||||
if (rep == JSType.jsString) return getLegacyRawClassType(_jsString);
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,348 +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 'dart:convert' show json;
|
||||
import 'dart:io' show File;
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/element/element.dart'
|
||||
show LibraryElement, UriReferencedElement;
|
||||
import 'package:analyzer/error/error.dart';
|
||||
|
||||
import 'package:analyzer/src/generated/engine.dart' show AnalysisContext;
|
||||
import 'package:args/args.dart' show ArgParser, ArgResults;
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:source_maps/source_maps.dart';
|
||||
|
||||
import '../compiler/js_names.dart' as js_ast;
|
||||
import '../compiler/module_builder.dart'
|
||||
show transformModuleFormat, ModuleFormat;
|
||||
import '../compiler/shared_command.dart';
|
||||
import '../compiler/shared_compiler.dart';
|
||||
import '../js_ast/js_ast.dart' as js_ast;
|
||||
import '../js_ast/js_ast.dart' show js;
|
||||
import '../js_ast/source_map_printer.dart' show SourceMapPrintingContext;
|
||||
import 'code_generator.dart' show CodeGenerator;
|
||||
import 'context.dart';
|
||||
|
||||
import 'driver.dart';
|
||||
import 'error_helpers.dart';
|
||||
|
||||
/// Compiles a set of Dart files into a single JavaScript module.
|
||||
///
|
||||
/// For a single build unit, this will produce a [JSModuleFile].
|
||||
///
|
||||
/// A build unit is a collection of Dart sources that is sufficient to be
|
||||
/// compiled together. This can be as small as a single Dart library file, but
|
||||
/// if the library has parts, or if the library has cyclic dependencies on other
|
||||
/// libraries, those must be included as well. A common build unit is the lib
|
||||
/// directory of a Dart package.
|
||||
///
|
||||
/// This class exists to cache global state associated with a single in-memory
|
||||
/// [AnalysisContext], such as information about extension types in the Dart
|
||||
/// SDK. It can be used once to produce a single module, or reused to save
|
||||
/// warm-up time. (Currently there is no warm up, but there may be in the
|
||||
/// future.)
|
||||
///
|
||||
/// The SDK source code is assumed to be immutable for the life of this class.
|
||||
///
|
||||
/// For all other files, it is up to the analysis context to decide whether or
|
||||
/// not any caching is performed. By default an analysis context will assume
|
||||
/// sources are immutable for the life of the context, and cache information
|
||||
/// about them.
|
||||
JSModuleFile compileWithAnalyzer(
|
||||
CompilerAnalysisDriver compilerDriver,
|
||||
List<String> sourcePaths,
|
||||
AnalyzerOptions analyzerOptions,
|
||||
CompilerOptions options) {
|
||||
var trees = <CompilationUnit>[];
|
||||
|
||||
var explicitSources = <Uri>[];
|
||||
var compilingSdk = false;
|
||||
for (var sourcePath in sourcePaths) {
|
||||
var sourceUri = sourcePathToUri(sourcePath);
|
||||
if (sourceUri.scheme == "dart") {
|
||||
compilingSdk = true;
|
||||
}
|
||||
explicitSources.add(sourceUri);
|
||||
}
|
||||
var driver = compilerDriver.linkLibraries(explicitSources, analyzerOptions);
|
||||
|
||||
var errors = ErrorCollector(driver.analysisOptions, options.replCompile);
|
||||
for (var libraryUri in driver.libraryUris) {
|
||||
var analysisResults = driver.analyzeLibrary(libraryUri);
|
||||
|
||||
CompilationUnit definingUnit;
|
||||
for (var result in analysisResults.values) {
|
||||
if (result.file.uriStr == libraryUri) definingUnit = result.unit;
|
||||
errors.addAll(result.unit.lineInfo, result.errors);
|
||||
trees.add(result.unit);
|
||||
}
|
||||
|
||||
var library = driver.getLibrary(libraryUri);
|
||||
|
||||
// TODO(jmesserly): remove "dart:mirrors" from DDC's SDK, and then remove
|
||||
// this special case error message.
|
||||
if (!compilingSdk && !options.emitMetadata) {
|
||||
var node = _getDartMirrorsImport(library);
|
||||
if (node != null) {
|
||||
errors.add(
|
||||
definingUnit.lineInfo,
|
||||
AnalysisError(library.source, node.uriOffset, node.uriEnd,
|
||||
invalidImportDartMirrors));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
js_ast.Program jsProgram;
|
||||
if (options.unsafeForceCompile || !errors.hasFatalErrors) {
|
||||
var codeGenerator = CodeGenerator(
|
||||
driver,
|
||||
driver.typeProvider,
|
||||
compilerDriver.summaryData,
|
||||
options,
|
||||
compilerDriver.extensionTypes,
|
||||
errors);
|
||||
try {
|
||||
jsProgram = codeGenerator.compile(trees);
|
||||
} catch (e) {
|
||||
// If force compilation failed, suppress the exception and report the
|
||||
// static errors instead. Otherwise, rethrow an internal compiler error.
|
||||
if (!errors.hasFatalErrors) rethrow;
|
||||
}
|
||||
|
||||
if (!options.unsafeForceCompile && errors.hasFatalErrors) {
|
||||
jsProgram = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (analyzerOptions.dependencyTracker != null) {
|
||||
var file = File(analyzerOptions.dependencyTracker.outputPath);
|
||||
file.writeAsStringSync(
|
||||
(analyzerOptions.dependencyTracker.dependencies.toList()..sort())
|
||||
.join('\n'));
|
||||
}
|
||||
|
||||
var jsModule = JSModuleFile(
|
||||
errors.formattedErrors.toList(), options, jsProgram, driver.summaryBytes);
|
||||
return jsModule;
|
||||
}
|
||||
|
||||
UriReferencedElement _getDartMirrorsImport(LibraryElement library) {
|
||||
return library.imports.firstWhere(_isDartMirrorsImort, orElse: () => null) ??
|
||||
library.exports.firstWhere(_isDartMirrorsImort, orElse: () => null);
|
||||
}
|
||||
|
||||
bool _isDartMirrorsImort(UriReferencedElement import) {
|
||||
return import.uri == 'dart:mirrors';
|
||||
}
|
||||
|
||||
class CompilerOptions extends SharedCompilerOptions {
|
||||
/// If [sourceMap] is emitted, this will emit a `sourceMappingUrl` comment
|
||||
/// into the output JavaScript module.
|
||||
final bool sourceMapComment;
|
||||
|
||||
/// The file extension for summaries.
|
||||
final String summaryExtension;
|
||||
|
||||
/// Whether to force compilation of code with static errors.
|
||||
final bool unsafeForceCompile;
|
||||
|
||||
/// If specified, the path to write the summary file.
|
||||
/// Used when building the SDK.
|
||||
final String summaryOutPath;
|
||||
|
||||
/// *deprecated* If specified, this is used to initialize the import paths for
|
||||
/// [summaryModules].
|
||||
final String moduleRoot;
|
||||
|
||||
/// *deprecated* If specified, `dartdevc` will synthesize library names that
|
||||
/// are relative to this path for all libraries in the JS module.
|
||||
String libraryRoot;
|
||||
|
||||
CompilerOptions(
|
||||
{bool sourceMap = true,
|
||||
this.sourceMapComment = true,
|
||||
bool summarizeApi = true,
|
||||
this.summaryExtension = 'sum',
|
||||
this.unsafeForceCompile = false,
|
||||
bool replCompile = false,
|
||||
bool emitMetadata = false,
|
||||
bool enableAsserts = true,
|
||||
Map<String, String> bazelMapping = const {},
|
||||
this.summaryOutPath,
|
||||
Map<String, String> summaryModules = const {},
|
||||
this.moduleRoot,
|
||||
this.libraryRoot})
|
||||
: super(
|
||||
sourceMap: sourceMap,
|
||||
summarizeApi: summarizeApi,
|
||||
emitMetadata: emitMetadata,
|
||||
enableAsserts: enableAsserts,
|
||||
replCompile: replCompile,
|
||||
bazelMapping: bazelMapping,
|
||||
summaryModules: summaryModules);
|
||||
|
||||
CompilerOptions.fromArguments(ArgResults args)
|
||||
: sourceMapComment = args['source-map-comment'] as bool,
|
||||
summaryExtension = args['summary-extension'] as String,
|
||||
unsafeForceCompile = args['unsafe-force-compile'] as bool,
|
||||
summaryOutPath = args['summary-out'] as String,
|
||||
moduleRoot = args['module-root'] as String,
|
||||
libraryRoot = _getLibraryRoot(args),
|
||||
super.fromArguments(args, args['module-root'] as String,
|
||||
args['summary-extension'] as String);
|
||||
|
||||
static void addArguments(ArgParser parser, {bool hide = true}) {
|
||||
SharedCompilerOptions.addArguments(parser, hide: hide);
|
||||
parser
|
||||
..addOption('summary-extension',
|
||||
help: 'file extension for Dart summary files',
|
||||
defaultsTo: 'sum',
|
||||
hide: hide)
|
||||
..addFlag('source-map-comment',
|
||||
help: 'adds a sourceMappingURL comment to the end of the JS,\n'
|
||||
'disable if using X-SourceMap header',
|
||||
defaultsTo: true,
|
||||
hide: hide)
|
||||
..addFlag('unsafe-force-compile',
|
||||
help: 'Compile code even if it has errors. ಠ_ಠ\n'
|
||||
'This has undefined behavior!',
|
||||
hide: hide)
|
||||
..addOption('summary-out',
|
||||
help: 'location to write the summary file', hide: hide)
|
||||
..addOption('summary-deps-output',
|
||||
help: 'Path to a file to dump summary dependency info to.',
|
||||
hide: hide)
|
||||
..addOption('module-root',
|
||||
help: '(deprecated) used to determine the default module name and\n'
|
||||
'summary import name if those are not provided.',
|
||||
hide: hide);
|
||||
}
|
||||
|
||||
static String _getLibraryRoot(ArgResults args) {
|
||||
var root = args['library-root'] as String;
|
||||
return root != null ? p.absolute(root) : p.current;
|
||||
}
|
||||
}
|
||||
|
||||
/// The output of Dart->JS compilation.
|
||||
///
|
||||
/// This contains the file contents of the JS module, as well as a list of
|
||||
/// Dart libraries that are contained in this module.
|
||||
class JSModuleFile {
|
||||
/// The list of messages (errors and warnings)
|
||||
final List<String> errors;
|
||||
|
||||
/// The AST that will be used to generate the [code] and [sourceMap] for this
|
||||
/// module.
|
||||
final js_ast.Program moduleTree;
|
||||
|
||||
/// The compiler options used to generate this module.
|
||||
final CompilerOptions options;
|
||||
|
||||
/// The binary contents of the API summary file, including APIs from each of
|
||||
/// the libraries in this module.
|
||||
final List<int> summaryBytes;
|
||||
|
||||
JSModuleFile(this.errors, this.options, this.moduleTree, this.summaryBytes);
|
||||
|
||||
JSModuleFile.invalid(this.errors, this.options)
|
||||
: moduleTree = null,
|
||||
summaryBytes = null;
|
||||
|
||||
/// The name of this module.
|
||||
String get name => options.moduleName;
|
||||
|
||||
/// True if this library was successfully compiled.
|
||||
bool get isValid => moduleTree != null;
|
||||
|
||||
/// Gets the source code and source map for this JS module, given the
|
||||
/// locations where the JS file and map file will be served from.
|
||||
///
|
||||
/// Relative URLs will be used to point from the .js file to the .map file
|
||||
//
|
||||
// TODO(jmesserly): this should match our old logic, but I'm not sure we are
|
||||
// correctly handling the pointer from the .js file to the .map file.
|
||||
JSModuleCode getCode(ModuleFormat format, String jsUrl, String mapUrl) {
|
||||
var opts = js_ast.JavaScriptPrintingOptions(
|
||||
allowKeywordsInProperties: true, allowSingleLineIfStatements: true);
|
||||
js_ast.SimpleJavaScriptPrintingContext printer;
|
||||
SourceMapBuilder sourceMap;
|
||||
if (options.sourceMap) {
|
||||
var sourceMapContext = SourceMapPrintingContext();
|
||||
sourceMap = sourceMapContext.sourceMap;
|
||||
printer = sourceMapContext;
|
||||
} else {
|
||||
printer = js_ast.SimpleJavaScriptPrintingContext();
|
||||
}
|
||||
|
||||
var tree = transformModuleFormat(format, moduleTree);
|
||||
tree.accept(
|
||||
js_ast.Printer(opts, printer, localNamer: js_ast.TemporaryNamer(tree)));
|
||||
|
||||
Map builtMap;
|
||||
if (options.sourceMap && sourceMap != null) {
|
||||
builtMap = placeSourceMap(
|
||||
sourceMap.build(jsUrl), mapUrl, options.bazelMapping, null);
|
||||
if (options.sourceMapComment) {
|
||||
var jsDir = p.dirname(p.fromUri(jsUrl));
|
||||
var relative = p.relative(p.fromUri(mapUrl), from: jsDir);
|
||||
var relativeMapUrl = p.toUri(relative).toString();
|
||||
assert(p.dirname(jsUrl) == p.dirname(mapUrl));
|
||||
printer.emit('\n//# sourceMappingURL=');
|
||||
printer.emit(relativeMapUrl);
|
||||
printer.emit('\n');
|
||||
}
|
||||
}
|
||||
|
||||
var text = printer.getText();
|
||||
var rawSourceMap = options.inlineSourceMap
|
||||
? js.escapedString(json.encode(builtMap), "'").value
|
||||
: 'null';
|
||||
text = text.replaceFirst(SharedCompiler.sourceMapLocationID, rawSourceMap);
|
||||
|
||||
return JSModuleCode(text, builtMap);
|
||||
}
|
||||
|
||||
/// Similar to [getCode] but immediately writes the resulting files.
|
||||
///
|
||||
/// If [mapPath] is not supplied but [options.sourceMap] is set, mapPath
|
||||
/// will default to [jsPath].map.
|
||||
void writeCodeSync(ModuleFormat format, String jsPath) {
|
||||
String mapPath = jsPath + '.map';
|
||||
var code = getCode(
|
||||
format, p.toUri(jsPath).toString(), p.toUri(mapPath).toString());
|
||||
var file = File(jsPath);
|
||||
if (!file.parent.existsSync()) file.parent.createSync(recursive: true);
|
||||
file.writeAsStringSync(code.code);
|
||||
|
||||
// TODO(jacobr): it is a bit strange we are writing the source map to a file
|
||||
// even when options.inlineSourceMap is true. To be consistent perhaps we
|
||||
// should also write a copy of the source file without a sourcemap even when
|
||||
// inlineSourceMap is true.
|
||||
if (code.sourceMap != null) {
|
||||
file = File(mapPath);
|
||||
if (!file.parent.existsSync()) file.parent.createSync(recursive: true);
|
||||
file.writeAsStringSync(json.encode(code.sourceMap));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The output of compiling a JavaScript module in a particular format.
|
||||
class JSModuleCode {
|
||||
/// The JavaScript code for this module.
|
||||
///
|
||||
/// If a [sourceMap] is available, this will include the `sourceMappingURL`
|
||||
/// comment at end of the file.
|
||||
final String code;
|
||||
|
||||
/// The JSON of the source map, if generated, otherwise `null`.
|
||||
///
|
||||
/// The source paths will initially be absolute paths. They can be adjusted
|
||||
/// using [placeSourceMap].
|
||||
final Map sourceMap;
|
||||
|
||||
JSModuleCode(this.code, this.sourceMap);
|
||||
}
|
|
@ -1,447 +0,0 @@
|
|||
// Copyright (c) 2015, 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 'dart:collection';
|
||||
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/token.dart' show TokenType;
|
||||
import 'package:analyzer/dart/ast/visitor.dart' show RecursiveAstVisitor;
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
|
||||
import 'element_helpers.dart' show getStaticType, isInlineJS, findAnnotation;
|
||||
import 'js_interop.dart' show isNotNullAnnotation, isNullCheckAnnotation;
|
||||
import 'js_typerep.dart';
|
||||
import 'property_model.dart';
|
||||
import 'type_utilities.dart';
|
||||
|
||||
/// An inference engine for nullable types.
|
||||
///
|
||||
/// This can answer questions about whether expressions are nullable
|
||||
/// (see [isNullable]). Given a set of compilation units in a library, it will
|
||||
/// determine if locals can be null using flow-insensitive analysis.
|
||||
///
|
||||
/// The analysis for null expressions is conservative and incomplete, but it can
|
||||
/// optimize some patterns.
|
||||
// TODO(vsm): Revisit whether we really need this when we get
|
||||
// better non-nullability in the type system.
|
||||
abstract class NullableTypeInference {
|
||||
LibraryElement get coreLibrary;
|
||||
VirtualFieldModel get virtualFields;
|
||||
|
||||
JSTypeRep get jsTypeRep;
|
||||
bool isObjectMember(String name);
|
||||
|
||||
/// Known non-null local variables.
|
||||
HashSet<LocalVariableElement> _notNullLocals;
|
||||
|
||||
void inferNullableTypes(AstNode node) {
|
||||
var visitor = _NullableLocalInference(this);
|
||||
node.accept(visitor);
|
||||
_notNullLocals = visitor.computeNotNullLocals();
|
||||
}
|
||||
|
||||
/// Adds a new variable, typically a compiler generated temporary, and record
|
||||
/// whether its type is nullable.
|
||||
void addTemporaryVariable(LocalVariableElement local,
|
||||
{bool nullable = true}) {
|
||||
if (!nullable) _notNullLocals.add(local);
|
||||
}
|
||||
|
||||
/// Returns true if [expr] can be null.
|
||||
bool isNullable(Expression expr) => _isNullable(expr);
|
||||
|
||||
bool _isNonNullMethodInvocation(MethodInvocation expr) {
|
||||
// TODO(vsm): This logic overlaps with the resolver.
|
||||
// Where is the best place to put this?
|
||||
var e = expr.methodName.staticElement;
|
||||
if (e == null) return false;
|
||||
if (isInlineJS(e)) {
|
||||
// Fix types for JS builtin calls.
|
||||
//
|
||||
// This code was taken from analyzer. It's not super sophisticated:
|
||||
// only looks for the type name in dart:core, so we just copy it here.
|
||||
//
|
||||
// TODO(jmesserly): we'll likely need something that can handle a wider
|
||||
// variety of types, especially when we get to JS interop.
|
||||
var args = expr.argumentList.arguments;
|
||||
var first = args.isNotEmpty ? args.first : null;
|
||||
if (first is SimpleStringLiteral) {
|
||||
var types = first.stringValue;
|
||||
if (types != '' &&
|
||||
types != 'var' &&
|
||||
!types.split('|').contains('Null')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (e.name == 'identical' && identical(e.library, coreLibrary)) {
|
||||
return true;
|
||||
}
|
||||
// If this is a method call, check to see whether it is to a final
|
||||
// type for which we have a known implementation type (i.e. int, bool,
|
||||
// double, and String), and if so use the element for the implementation
|
||||
// type instead.
|
||||
if (e is MethodElement) {
|
||||
Element container = e.enclosingElement;
|
||||
if (container is ClassElement) {
|
||||
DartType targetType = getLegacyRawClassType(container);
|
||||
InterfaceType implType = jsTypeRep.getImplementationType(targetType);
|
||||
if (implType != null) {
|
||||
MethodElement method = implType.lookUpMethod(e.name, coreLibrary);
|
||||
if (method != null) e = method;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If the method or function is annotated as returning a non-null value
|
||||
// then the result of the call is non-null.
|
||||
return (e is MethodElement || e is FunctionElement) && _assertedNotNull(e);
|
||||
}
|
||||
|
||||
bool _isNonNullProperty(Element element, String name) {
|
||||
if (element is! PropertyInducingElement &&
|
||||
element is! PropertyAccessorElement) {
|
||||
return false;
|
||||
}
|
||||
// If this is a reference to an element of a type for which
|
||||
// we have a known implementation type (i.e. int, double,
|
||||
// bool, String), then use the element for the implementation
|
||||
// type.
|
||||
Element container = element.enclosingElement;
|
||||
if (container is ClassElement) {
|
||||
var targetType = getLegacyRawClassType(container);
|
||||
var implType = jsTypeRep.getImplementationType(targetType);
|
||||
if (implType != null) {
|
||||
var getter = implType.lookUpGetter(name, coreLibrary);
|
||||
if (getter != null) element = getter;
|
||||
}
|
||||
}
|
||||
// If the getter is a synthetic element, then any annotations will
|
||||
// be on the variable, so use those instead.
|
||||
if (element is PropertyAccessorElement && element.isSynthetic) {
|
||||
return _assertedNotNull(element.variable);
|
||||
}
|
||||
// Return true if the element is annotated as returning a non-null value.
|
||||
return _assertedNotNull(element);
|
||||
}
|
||||
|
||||
/// Returns true if [expr] can be null, optionally using [localIsNullable]
|
||||
/// for locals.
|
||||
///
|
||||
/// If [localIsNullable] is not supplied, this will use the known list of
|
||||
/// [_notNullLocals].
|
||||
bool _isNullable(Expression expr,
|
||||
[bool localIsNullable(LocalVariableElement e)]) {
|
||||
// TODO(jmesserly): we do recursive calls in a few places. This could
|
||||
// leads to O(depth) cost for calling this function. We could store the
|
||||
// resulting value if that becomes an issue, so we maintain the invariant
|
||||
// that each node is visited once.
|
||||
Element element;
|
||||
String name;
|
||||
if (expr is PropertyAccess &&
|
||||
expr.operator?.type != TokenType.QUESTION_PERIOD) {
|
||||
element = expr.propertyName.staticElement;
|
||||
name = expr.propertyName.name;
|
||||
} else if (expr is PrefixedIdentifier) {
|
||||
element = expr.staticElement;
|
||||
name = expr.identifier.name;
|
||||
} else if (expr is Identifier) {
|
||||
element = expr.staticElement;
|
||||
name = expr.name;
|
||||
}
|
||||
if (element != null) {
|
||||
if (_isNonNullProperty(element, name)) return false;
|
||||
|
||||
// Type literals are not null.
|
||||
if (element is ClassElement || element is FunctionTypeAliasElement) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (element is LocalVariableElement) {
|
||||
if (localIsNullable != null) {
|
||||
return localIsNullable(element);
|
||||
}
|
||||
return !_notNullLocals.contains(element);
|
||||
}
|
||||
|
||||
if (element is ParameterElement && _assertedNotNull(element)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (element is FunctionElement || element is MethodElement) {
|
||||
// A function or method. This can't be null.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (element is PropertyAccessorElement && element.isGetter) {
|
||||
PropertyInducingElement variable = element.variable;
|
||||
if (variable is FieldElement && virtualFields.isVirtual(variable)) {
|
||||
return true;
|
||||
}
|
||||
var value = variable.computeConstantValue();
|
||||
return value == null || value.isNull || !value.hasKnownValue;
|
||||
}
|
||||
|
||||
// Other types of identifiers are nullable (parameters, fields).
|
||||
return true;
|
||||
}
|
||||
|
||||
if (expr is Literal) return expr is NullLiteral;
|
||||
if (expr is IsExpression) return false;
|
||||
if (expr is FunctionExpression) return false;
|
||||
if (expr is ThisExpression) return false;
|
||||
if (expr is SuperExpression) return false;
|
||||
if (expr is CascadeExpression) {
|
||||
// Cascades normally can't return `null`, because if the target is null,
|
||||
// they will throw noSuchMethod.
|
||||
// The only properties/methods on `null` are those on Object itself.
|
||||
for (var section in expr.cascadeSections) {
|
||||
Element e;
|
||||
if (section is PropertyAccess) {
|
||||
e = section.propertyName.staticElement;
|
||||
} else if (section is MethodInvocation) {
|
||||
e = section.methodName.staticElement;
|
||||
} else if (section is IndexExpression) {
|
||||
// Object does not have operator []=.
|
||||
return false;
|
||||
}
|
||||
// We encountered a non-Object method/property.
|
||||
if (e != null && !isObjectMember(e.name)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return _isNullable(expr.target, localIsNullable);
|
||||
}
|
||||
if (expr is ConditionalExpression) {
|
||||
return _isNullable(expr.thenExpression, localIsNullable) ||
|
||||
_isNullable(expr.elseExpression, localIsNullable);
|
||||
}
|
||||
if (expr is ParenthesizedExpression) {
|
||||
return _isNullable(expr.expression, localIsNullable);
|
||||
}
|
||||
if (expr is InstanceCreationExpression) {
|
||||
var e = expr.staticElement;
|
||||
if (e == null) return true;
|
||||
|
||||
// Follow redirects.
|
||||
while (e.redirectedConstructor != null) {
|
||||
e = e.redirectedConstructor;
|
||||
}
|
||||
|
||||
// Generative constructors are not nullable.
|
||||
if (!e.isFactory) return false;
|
||||
|
||||
// Factory constructors are nullable. However it is a bad pattern and
|
||||
// our own SDK will never do this.
|
||||
// TODO(jmesserly): we could enforce this for user-defined constructors.
|
||||
if (e.library.source.isInSystemLibrary) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
if (expr is MethodInvocation &&
|
||||
(expr.operator?.lexeme != '?.' ||
|
||||
!_isNullable(expr.target, localIsNullable)) &&
|
||||
_isNonNullMethodInvocation(expr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Expression operand, rightOperand;
|
||||
String op;
|
||||
if (expr is AssignmentExpression) {
|
||||
op = expr.operator.lexeme;
|
||||
assert(op.endsWith('='));
|
||||
if (op == '=') {
|
||||
return _isNullable(expr.rightHandSide, localIsNullable);
|
||||
}
|
||||
// op assignment like +=, remove trailing '='
|
||||
op = op.substring(0, op.length - 1);
|
||||
operand = expr.leftHandSide;
|
||||
rightOperand = expr.rightHandSide;
|
||||
} else if (expr is BinaryExpression) {
|
||||
operand = expr.leftOperand;
|
||||
rightOperand = expr.rightOperand;
|
||||
op = expr.operator.lexeme;
|
||||
} else if (expr is PrefixExpression) {
|
||||
operand = expr.operand;
|
||||
op = expr.operator.lexeme;
|
||||
} else if (expr is PostfixExpression) {
|
||||
operand = expr.operand;
|
||||
op = expr.operator.lexeme;
|
||||
}
|
||||
switch (op) {
|
||||
case '==':
|
||||
case '!=':
|
||||
case '&&':
|
||||
case '||':
|
||||
case '!':
|
||||
return false;
|
||||
case '??':
|
||||
return _isNullable(operand, localIsNullable) &&
|
||||
_isNullable(rightOperand, localIsNullable);
|
||||
}
|
||||
if (operand != null && jsTypeRep.isPrimitive(getStaticType(operand))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(ochafik,jmesserly): handle other cases such as: refs to top-level
|
||||
// finals that have been assigned non-nullable values.
|
||||
|
||||
// Failed to recognize a non-nullable case: assume it can be null.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// A visitor that determines which local variables are non-nullable.
|
||||
///
|
||||
/// This will consider all assignments to local variables using
|
||||
/// flow-insensitive inference. That information is used to determine which
|
||||
/// variables are nullable in the given scope.
|
||||
// TODO(ochafik): Introduce flow analysis (a variable may be nullable in
|
||||
// some places and not in others).
|
||||
class _NullableLocalInference extends RecursiveAstVisitor {
|
||||
final NullableTypeInference _nullInference;
|
||||
|
||||
/// Known local variables.
|
||||
final _locals = HashSet<LocalVariableElement>.identity();
|
||||
|
||||
/// Variables that are known to be nullable.
|
||||
final _nullableLocals = HashSet<LocalVariableElement>.identity();
|
||||
|
||||
/// Given a variable, tracks all other variables that it is assigned to.
|
||||
final _assignments =
|
||||
HashMap<LocalVariableElement, Set<LocalVariableElement>>.identity();
|
||||
|
||||
_NullableLocalInference(this._nullInference);
|
||||
|
||||
/// After visiting nodes, this can be called to compute the set of not-null
|
||||
/// locals.
|
||||
///
|
||||
/// This method must only be called once. After it is called, the visitor
|
||||
/// should be discarded.
|
||||
HashSet<LocalVariableElement> computeNotNullLocals() {
|
||||
// Given a set of variables that are nullable, remove them from our list of
|
||||
// local variables. The end result of this process is a list of variables
|
||||
// known to be not null.
|
||||
visitNullableLocal(LocalVariableElement e) {
|
||||
_locals.remove(e);
|
||||
|
||||
// Visit all other locals that this one is assigned to, and record that
|
||||
// they are nullable too.
|
||||
_assignments.remove(e)?.forEach(visitNullableLocal);
|
||||
}
|
||||
|
||||
_nullableLocals.forEach(visitNullableLocal);
|
||||
|
||||
// Any remaining locals are non-null.
|
||||
return _locals;
|
||||
}
|
||||
|
||||
@override
|
||||
visitVariableDeclaration(VariableDeclaration node) {
|
||||
var element = node.declaredElement;
|
||||
var initializer = node.initializer;
|
||||
if (element is LocalVariableElement) {
|
||||
_locals.add(element);
|
||||
if (initializer != null) {
|
||||
_visitAssignment(node.name, initializer);
|
||||
} else if (!_assertedNotNull(element)) {
|
||||
_nullableLocals.add(element);
|
||||
}
|
||||
}
|
||||
super.visitVariableDeclaration(node);
|
||||
}
|
||||
|
||||
@override
|
||||
visitForStatement(ForStatement node) {
|
||||
var forLoopParts = node.forLoopParts;
|
||||
if (forLoopParts is ForEachParts) {
|
||||
if (forLoopParts is ForEachPartsWithIdentifier &&
|
||||
forLoopParts.identifier != null) {
|
||||
var element = forLoopParts.identifier.staticElement;
|
||||
if (element is LocalVariableElement && !_assertedNotNull(element)) {
|
||||
_nullableLocals.add(element);
|
||||
}
|
||||
} else if (forLoopParts is ForEachPartsWithDeclaration) {
|
||||
var declaration = forLoopParts.loopVariable;
|
||||
var element = declaration.declaredElement;
|
||||
_locals.add(element);
|
||||
if (!_assertedNotNull(element)) {
|
||||
_nullableLocals.add(element);
|
||||
}
|
||||
} else {
|
||||
throw StateError('Unrecognized for loop parts');
|
||||
}
|
||||
}
|
||||
super.visitForStatement(node);
|
||||
}
|
||||
|
||||
@override
|
||||
visitCatchClause(CatchClause node) {
|
||||
var e = node.exceptionParameter?.staticElement;
|
||||
if (e is LocalVariableElement) {
|
||||
_locals.add(e);
|
||||
// TODO(jmesserly): we allow throwing of `null`, for better or worse.
|
||||
_nullableLocals.add(e);
|
||||
}
|
||||
|
||||
e = node.stackTraceParameter?.staticElement;
|
||||
if (e is LocalVariableElement) _locals.add(e);
|
||||
|
||||
super.visitCatchClause(node);
|
||||
}
|
||||
|
||||
@override
|
||||
visitAssignmentExpression(AssignmentExpression node) {
|
||||
if (node.operator.lexeme == '=') {
|
||||
_visitAssignment(node.leftHandSide, node.rightHandSide);
|
||||
} else {
|
||||
_visitAssignment(node.leftHandSide, node);
|
||||
}
|
||||
super.visitAssignmentExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
visitPostfixExpression(PostfixExpression node) {
|
||||
var op = node.operator.type;
|
||||
if (op.isIncrementOperator) {
|
||||
_visitAssignment(node.operand, node);
|
||||
}
|
||||
super.visitPostfixExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
visitPrefixExpression(PrefixExpression node) {
|
||||
var op = node.operator.type;
|
||||
if (op.isIncrementOperator) {
|
||||
_visitAssignment(node.operand, node);
|
||||
}
|
||||
super.visitPrefixExpression(node);
|
||||
}
|
||||
|
||||
void _visitAssignment(Expression left, Expression right) {
|
||||
if (left is SimpleIdentifier) {
|
||||
var element = left.staticElement;
|
||||
if (element is LocalVariableElement && !_assertedNotNull(element)) {
|
||||
bool visitLocal(LocalVariableElement otherLocal) {
|
||||
// Record the assignment.
|
||||
_assignments
|
||||
.putIfAbsent(otherLocal, () => HashSet.identity())
|
||||
.add(element);
|
||||
// Optimistically assume this local is not null.
|
||||
// We will validate this assumption later.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_nullInference._isNullable(right, visitLocal)) {
|
||||
_nullableLocals.add(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _assertedNotNull(Element e) =>
|
||||
findAnnotation(e, isNotNullAnnotation) != null ||
|
||||
findAnnotation(e, isNullCheckAnnotation) != null;
|
|
@ -1,432 +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 'dart:collection' show HashMap, HashSet, Queue;
|
||||
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/type.dart' show InterfaceType;
|
||||
import 'package:analyzer/src/dart/element/element.dart' show FieldElementImpl;
|
||||
|
||||
import '../compiler/js_names.dart' as js_ast;
|
||||
import 'element_helpers.dart';
|
||||
import 'extension_types.dart';
|
||||
import 'type_utilities.dart';
|
||||
|
||||
/// Dart allows all fields to be overridden.
|
||||
///
|
||||
/// To prevent a performance/code size penalty for allowing this, we analyze
|
||||
/// private classes within each library that is being compiled to determine
|
||||
/// if those fields should be virtual or not. In effect, we devirtualize fields
|
||||
/// when possible by analyzing the class hierarchy and using knowledge of
|
||||
/// which members are private and thus, could not be overridden outside of the
|
||||
/// current library.
|
||||
class VirtualFieldModel {
|
||||
final _modelForLibrary = HashMap<LibraryElement, _LibraryVirtualFieldModel>();
|
||||
|
||||
_LibraryVirtualFieldModel _getModel(LibraryElement library) =>
|
||||
_modelForLibrary.putIfAbsent(
|
||||
library, () => _LibraryVirtualFieldModel.build(library));
|
||||
|
||||
/// Returns true if a field is virtual.
|
||||
bool isVirtual(FieldElement field) =>
|
||||
_getModel(field.library).isVirtual(field);
|
||||
}
|
||||
|
||||
/// This is a building block of [VirtualFieldModel], used to track information
|
||||
/// about a single library that has been analyzed.
|
||||
class _LibraryVirtualFieldModel {
|
||||
/// Fields that are private (or public fields of a private class) and
|
||||
/// overridden in this library.
|
||||
///
|
||||
/// This means we must generate them as virtual fields using a property pair
|
||||
/// in JavaScript.
|
||||
final _overriddenPrivateFields = HashSet<FieldElement>();
|
||||
|
||||
/// Private classes that can be extended outside of this library.
|
||||
///
|
||||
/// Normally private classes cannot be accessed outside this library, however,
|
||||
/// this can happen if they are extended by a public class, for example:
|
||||
///
|
||||
/// class _A { int x = 42; }
|
||||
/// class _B { int x = 42; }
|
||||
///
|
||||
/// // _A is now effectively public for the purpose of overrides.
|
||||
/// class C extends _A {}
|
||||
///
|
||||
/// The class _A must treat is "x" as virtual, however _B does not.
|
||||
final _extensiblePrivateClasses = HashSet<ClassElement>();
|
||||
|
||||
_LibraryVirtualFieldModel.build(LibraryElement library) {
|
||||
var allClasses = Set<ClassElement>();
|
||||
for (var libraryPart in library.units) {
|
||||
allClasses.addAll(libraryPart.types);
|
||||
allClasses.addAll(libraryPart.mixins);
|
||||
}
|
||||
|
||||
// The set of public types is our initial extensible type set.
|
||||
// From there, visit all immediate private types in this library, and so on
|
||||
// from those private types, marking them as extensible.
|
||||
var classesToVisit =
|
||||
Queue<ClassElement>.from(allClasses.where((t) => t.isPublic));
|
||||
while (classesToVisit.isNotEmpty) {
|
||||
var extensibleClass = classesToVisit.removeFirst();
|
||||
|
||||
// For each supertype of a public type in this library,
|
||||
// if we encounter a private class, we mark it as being extended, and
|
||||
// add it to our work set if this is the first time we've visited it.
|
||||
for (var superclass in getImmediateSuperclasses(extensibleClass)) {
|
||||
if (!superclass.isPublic && superclass.library == library) {
|
||||
if (_extensiblePrivateClasses.add(superclass)) {
|
||||
classesToVisit.add(superclass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ClassElement can only look up inherited members with an O(N) scan through
|
||||
// the class, so we build up a mapping of all fields in the library ahead of
|
||||
// time.
|
||||
Map<String, FieldElement> getInstanceFieldMap(ClassElement c) {
|
||||
var instanceFields = c.fields.where((f) => !f.isStatic);
|
||||
return HashMap.fromIterables(
|
||||
instanceFields.map((f) => f.name), instanceFields);
|
||||
}
|
||||
|
||||
var allFields =
|
||||
HashMap.fromIterables(allClasses, allClasses.map(getInstanceFieldMap));
|
||||
|
||||
for (var class_ in allClasses) {
|
||||
Set<ClassElement> superclasses;
|
||||
|
||||
// Visit accessors in the current class, and see if they override an
|
||||
// otherwise private field.
|
||||
for (var accessor in class_.accessors) {
|
||||
// For getter/setter pairs only process them once.
|
||||
if (accessor.correspondingGetter != null) continue;
|
||||
// Ignore abstract or static accessors.
|
||||
if (accessor.isAbstract || accessor.isStatic) continue;
|
||||
// Ignore public accessors in extensible classes.
|
||||
if (accessor.isPublic &&
|
||||
(class_.isPublic || _extensiblePrivateClasses.contains(class_))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (superclasses == null) {
|
||||
superclasses = Set();
|
||||
void collectSuperclasses(ClassElement cls) {
|
||||
if (!superclasses.add(cls)) return;
|
||||
var s = cls.supertype?.element;
|
||||
if (s != null) collectSuperclasses(s);
|
||||
cls.mixins.forEach((m) => collectSuperclasses(m.element));
|
||||
}
|
||||
|
||||
collectSuperclasses(class_);
|
||||
superclasses.remove(class_);
|
||||
superclasses.removeWhere((c) => c.library != library);
|
||||
}
|
||||
|
||||
// Look in all super classes to see if we're overriding a field in our
|
||||
// library, if so mark that field as overridden.
|
||||
var name = accessor.variable.name;
|
||||
_overriddenPrivateFields.addAll(superclasses
|
||||
.map((c) => allFields[c][name])
|
||||
.where((f) => f != null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if a field inside this library is virtual.
|
||||
bool isVirtual(FieldElement field) {
|
||||
if (field.isStatic) return false;
|
||||
|
||||
var type = field.enclosingElement;
|
||||
var uri = type.source.uri;
|
||||
if (uri.scheme == 'dart' && uri.path.startsWith('_')) {
|
||||
// There should be no extensible fields in private SDK libraries.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (field.isPublic) {
|
||||
// Public fields in public classes (or extensible private classes)
|
||||
// are always virtual.
|
||||
// They could be overridden by someone using our library.
|
||||
if (type.isPublic) return true;
|
||||
if (_extensiblePrivateClasses.contains(type)) return true;
|
||||
}
|
||||
|
||||
// Otherwise, the field is effectively private and we only need to make it
|
||||
// virtual if it's overridden.
|
||||
return _overriddenPrivateFields.contains(field);
|
||||
}
|
||||
}
|
||||
|
||||
/// Tracks how fields, getters and setters are represented when emitting JS.
|
||||
///
|
||||
/// Dart classes have implicit features that must be made explicit:
|
||||
///
|
||||
/// - virtual fields induce a getter and setter pair.
|
||||
/// - getters and setters are independent.
|
||||
/// - getters and setters can be overridden.
|
||||
///
|
||||
class ClassPropertyModel {
|
||||
final ExtensionTypeSet extensionTypes;
|
||||
|
||||
/// Fields that are virtual, that is, they must be generated as a property
|
||||
/// pair in JavaScript.
|
||||
///
|
||||
/// The value property stores the symbol used for the field's storage slot.
|
||||
final virtualFields = <FieldElement, js_ast.TemporaryId>{};
|
||||
|
||||
/// The set of inherited getters, used because JS getters/setters are paired,
|
||||
/// so if we're generating a setter we may need to emit a getter that calls
|
||||
/// super.
|
||||
final inheritedGetters = HashSet<String>();
|
||||
|
||||
/// The set of inherited setters, used because JS getters/setters are paired,
|
||||
/// so if we're generating a getter we may need to emit a setter that calls
|
||||
/// super.
|
||||
final inheritedSetters = HashSet<String>();
|
||||
|
||||
final mockMembers = <String, ExecutableElement>{};
|
||||
|
||||
final extensionMethods = Set<String>();
|
||||
|
||||
final extensionAccessors = Set<String>();
|
||||
|
||||
/// Parameters that are covariant due to covariant generics.
|
||||
final Set<Element> covariantParameters;
|
||||
|
||||
ClassPropertyModel.build(
|
||||
this.extensionTypes,
|
||||
VirtualFieldModel fieldModel,
|
||||
ClassElement classElem,
|
||||
this.covariantParameters,
|
||||
Set<ExecutableElement> covariantPrivateMembers) {
|
||||
// Visit superclasses to collect information about their fields/accessors.
|
||||
// This is expensive so we try to collect everything in one pass.
|
||||
for (var base in getSuperclasses(classElem)) {
|
||||
for (var accessor in base.accessors) {
|
||||
// For getter/setter pairs only process them once.
|
||||
if (accessor.correspondingGetter != null) continue;
|
||||
|
||||
var field = accessor.variable;
|
||||
// Ignore private names from other libraries.
|
||||
if (field.isPrivate && accessor.library != classElem.library) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (field.getter?.isAbstract == false) inheritedGetters.add(field.name);
|
||||
if (field.setter?.isAbstract == false) inheritedSetters.add(field.name);
|
||||
}
|
||||
}
|
||||
|
||||
_collectMockMembers(getLegacyRawClassType(classElem));
|
||||
_collectExtensionMembers(classElem);
|
||||
|
||||
var virtualAccessorNames = HashSet<String>()
|
||||
..addAll(inheritedGetters)
|
||||
..addAll(inheritedSetters)
|
||||
..addAll(extensionAccessors)
|
||||
..addAll(mockMembers.values
|
||||
.map((m) => m is PropertyAccessorElement ? m.variable.name : m.name));
|
||||
|
||||
// Visit accessors in the current class, and see if they need to be
|
||||
// generated differently based on the inherited fields/accessors.
|
||||
for (var accessor in classElem.accessors) {
|
||||
// For getter/setter pairs only process them once.
|
||||
if (accessor.correspondingGetter != null) continue;
|
||||
// Also ignore abstract fields.
|
||||
if (accessor.isAbstract || accessor.isStatic) continue;
|
||||
|
||||
var field = accessor.variable;
|
||||
var name = field.name;
|
||||
// Is it a field?
|
||||
if (!field.isSynthetic && field is FieldElementImpl) {
|
||||
var setter = field.setter;
|
||||
if (virtualAccessorNames.contains(name) ||
|
||||
fieldModel.isVirtual(field) ||
|
||||
setter != null &&
|
||||
covariantParameters != null &&
|
||||
covariantParameters.contains(setter.parameters[0]) &&
|
||||
covariantPrivateMembers.contains(setter)) {
|
||||
virtualFields[field] = js_ast.TemporaryId(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _collectMockMembers(InterfaceType type) {
|
||||
// TODO(jmesserly): every type with nSM will generate new stubs for all
|
||||
// abstract members. For example:
|
||||
//
|
||||
// class C { m(); noSuchMethod(...) { ... } }
|
||||
// class D extends C { m(); noSuchMethod(...) { ... } }
|
||||
//
|
||||
// We'll generate D.m even though it is not necessary.
|
||||
//
|
||||
// Doing better is a bit tricky, as our current codegen strategy for the
|
||||
// mock methods encodes information about the number of arguments (and type
|
||||
// arguments) that D expects.
|
||||
var element = type.element;
|
||||
if (element.isMixin || !hasNoSuchMethod(element)) return;
|
||||
|
||||
// Collect all unimplemented members.
|
||||
//
|
||||
// Initially, we track abstract and concrete members separately, then
|
||||
// remove concrete from the abstract set. This is done because abstract
|
||||
// members are allowed to "override" concrete ones in Dart.
|
||||
// (In that case, it will still be treated as a concrete member and can be
|
||||
// called at runtime.)
|
||||
var concreteMembers = HashSet<String>();
|
||||
|
||||
void visit(InterfaceType type, bool isAbstract) {
|
||||
if (type == null) return;
|
||||
visit(type.superclass, isAbstract);
|
||||
for (var m in type.mixins) {
|
||||
visit(m, isAbstract);
|
||||
}
|
||||
for (var i in type.interfaces) {
|
||||
visit(i, true);
|
||||
}
|
||||
|
||||
for (var m in [type.methods, type.accessors].expand((m) => m)) {
|
||||
if (m.isStatic) continue;
|
||||
if (isAbstract || m.isAbstract) {
|
||||
mockMembers[m.name] = m;
|
||||
} else {
|
||||
concreteMembers.add(m.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visit(type, false);
|
||||
|
||||
concreteMembers.forEach(mockMembers.remove);
|
||||
}
|
||||
|
||||
void _collectExtensionMembers(ClassElement element) {
|
||||
if (extensionTypes.isNativeClass(element)) return;
|
||||
|
||||
// Find all generic interfaces that could be used to call into members of
|
||||
// this class. This will help us identify which parameters need checks
|
||||
// for soundness.
|
||||
var allNatives = HashSet<String>();
|
||||
_collectNativeMembers(getLegacyRawClassType(element), allNatives);
|
||||
if (allNatives.isEmpty) return;
|
||||
|
||||
// For members on this class, check them against all generic interfaces.
|
||||
var seenConcreteMembers = HashSet<String>();
|
||||
_findExtensionMembers(
|
||||
getLegacyRawClassType(element), seenConcreteMembers, allNatives);
|
||||
// Add mock members. These are compiler-generated concrete members that
|
||||
// forward to `noSuchMethod`.
|
||||
for (var m in mockMembers.values) {
|
||||
var name = m is PropertyAccessorElement ? m.variable.name : m.name;
|
||||
if (seenConcreteMembers.add(name) && allNatives.contains(name)) {
|
||||
var extMembers = m is PropertyAccessorElement
|
||||
? extensionAccessors
|
||||
: extensionMethods;
|
||||
extMembers.add(name);
|
||||
}
|
||||
}
|
||||
|
||||
// For members of the superclass, we may need to add checks because this
|
||||
// class adds a new unsafe interface. Collect those checks.
|
||||
|
||||
var visited = HashSet<ClassElement>()..add(element);
|
||||
var existingMembers = HashSet<String>();
|
||||
|
||||
void visitImmediateSuper(InterfaceType type) {
|
||||
// For members of mixins/supertypes, check them against new interfaces,
|
||||
// and also record any existing checks they already had.
|
||||
var oldCovariant = HashSet<String>();
|
||||
_collectNativeMembers(type, oldCovariant);
|
||||
var newCovariant = allNatives.difference(oldCovariant);
|
||||
if (newCovariant.isEmpty) return;
|
||||
|
||||
existingMembers.addAll(oldCovariant);
|
||||
|
||||
void visitSuper(InterfaceType type) {
|
||||
var element = type.element;
|
||||
if (visited.add(element)) {
|
||||
_findExtensionMembers(type, seenConcreteMembers, newCovariant);
|
||||
element.mixins.reversed.forEach(visitSuper);
|
||||
var s = element.supertype;
|
||||
if (s != null) visitSuper(s);
|
||||
}
|
||||
}
|
||||
|
||||
visitSuper(type);
|
||||
}
|
||||
|
||||
element.mixins.reversed.forEach(visitImmediateSuper);
|
||||
var s = element.supertype;
|
||||
if (s != null) visitImmediateSuper(s);
|
||||
}
|
||||
|
||||
/// Searches all concrete instance members declared on this type, skipping
|
||||
/// already [seenConcreteMembers], and adds them to [extensionMembers] if
|
||||
/// needed.
|
||||
///
|
||||
/// By tracking the set of seen members, we can visit superclasses and mixins
|
||||
/// and ultimately collect every most-derived member exposed by a given type.
|
||||
void _findExtensionMembers(InterfaceType type,
|
||||
HashSet<String> seenConcreteMembers, Set<String> allNatives) {
|
||||
// We only visit each most derived concrete member.
|
||||
// To avoid visiting an overridden superclass member, we skip members
|
||||
// we've seen, and visit starting from the class, then mixins in
|
||||
// reverse order, then superclasses.
|
||||
for (var m in type.methods) {
|
||||
var name = m.name;
|
||||
if (!m.isStatic &&
|
||||
!m.isAbstract &&
|
||||
seenConcreteMembers.add(name) &&
|
||||
allNatives.contains(name)) {
|
||||
extensionMethods.add(name);
|
||||
}
|
||||
}
|
||||
for (var m in type.accessors) {
|
||||
var name = m.variable.name;
|
||||
if (!m.isStatic &&
|
||||
!m.isAbstract &&
|
||||
seenConcreteMembers.add(name) &&
|
||||
allNatives.contains(name)) {
|
||||
extensionAccessors.add(name);
|
||||
}
|
||||
}
|
||||
if (type.element.isEnum) {
|
||||
extensionMethods.add('toString');
|
||||
}
|
||||
}
|
||||
|
||||
/// Collects all supertypes that may themselves contain native subtypes,
|
||||
/// excluding [Object], for example `List` is implemented by several native
|
||||
/// types.
|
||||
void _collectNativeMembers(InterfaceType type, Set<String> members) {
|
||||
var element = type.element;
|
||||
if (extensionTypes.hasNativeSubtype(type)) {
|
||||
for (var m in type.methods) {
|
||||
if (m.isPublic && !m.isStatic) members.add(m.name);
|
||||
}
|
||||
for (var m in type.accessors) {
|
||||
if (m.isPublic && !m.isStatic) members.add(m.variable.name);
|
||||
}
|
||||
}
|
||||
for (var m in element.mixins.reversed) {
|
||||
_collectNativeMembers(m, members);
|
||||
}
|
||||
for (var i in element.interfaces) {
|
||||
_collectNativeMembers(i, members);
|
||||
}
|
||||
var supertype = element.supertype;
|
||||
if (supertype != null) {
|
||||
_collectNativeMembers(element.supertype, members);
|
||||
}
|
||||
if (element.isEnum) {
|
||||
// TODO(jmesserly): analyzer does not create the synthetic element
|
||||
// for the enum's `toString()` method, so we'll use the one on Object.
|
||||
members.add('toString');
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,216 +0,0 @@
|
|||
// Copyright (c) 2015, 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/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/standard_ast_factory.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart' show GeneralizingAstVisitor;
|
||||
import 'package:analyzer/dart/element/type.dart' show DartType;
|
||||
import 'package:analyzer/src/dart/ast/ast.dart'
|
||||
show
|
||||
FunctionBodyImpl,
|
||||
FunctionExpressionInvocationImpl,
|
||||
MethodInvocationImpl;
|
||||
import 'package:analyzer/src/dart/ast/utilities.dart'
|
||||
show AstCloner, NodeReplacer;
|
||||
import 'package:analyzer/src/generated/parser.dart' show ResolutionCopier;
|
||||
import 'package:analyzer/src/task/strong/ast_properties.dart' as ast_properties;
|
||||
|
||||
import 'ast_builder.dart';
|
||||
import 'element_helpers.dart' show isInlineJS;
|
||||
|
||||
// This class implements a pass which modifies (in place) the ast replacing
|
||||
// abstract coercion nodes with their dart implementations.
|
||||
class CoercionReifier extends GeneralizingAstVisitor<void> {
|
||||
final cloner = _TreeCloner();
|
||||
|
||||
CoercionReifier._();
|
||||
|
||||
/// Transforms the given compilation units, and returns a new AST with
|
||||
/// explicit coercion nodes in appropriate places.
|
||||
static List<CompilationUnit> reify(List<CompilationUnit> units) {
|
||||
var cr = CoercionReifier._();
|
||||
return units.map(cr.visitCompilationUnit).toList(growable: false);
|
||||
}
|
||||
|
||||
/// True if the `as` [node] is a required runtime check for soundness.
|
||||
// TODO(sra): Find a better way to recognize reified coercion, since we
|
||||
// can't set the isSynthetic attribute.
|
||||
static bool isImplicit(AsExpression node) => node.asOperator.offset == 0;
|
||||
|
||||
/// Creates an implicit cast for expression [e] to [toType].
|
||||
static Expression castExpression(Expression e, DartType toType) {
|
||||
// We use an empty name in the AST, because the JS code generator only cares
|
||||
// about the target type. It does not look at the AST name.
|
||||
var typeName = astFactory.typeName(ast.identifierFromString(''), null);
|
||||
typeName.type = toType;
|
||||
var cast = ast.asExpression(e, typeName);
|
||||
cast.staticType = toType;
|
||||
return cast;
|
||||
}
|
||||
|
||||
@override
|
||||
CompilationUnit visitCompilationUnit(CompilationUnit node) {
|
||||
if (ast_properties.hasImplicitCasts(node)) {
|
||||
// Clone compilation unit, so we don't modify the originals.
|
||||
node = _clone(node);
|
||||
super.visitCompilationUnit(node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
@override
|
||||
visitExpression(Expression node) {
|
||||
node.visitChildren(this);
|
||||
|
||||
var castType = ast_properties.getImplicitCast(node);
|
||||
if (castType != null) {
|
||||
_replaceNode(node.parent, node, castExpression(node, castType));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
visitSpreadElement(SpreadElement node) {
|
||||
// Skip visiting the expression so we can handle all casts during code
|
||||
// generation.
|
||||
node.expression.visitChildren(this);
|
||||
}
|
||||
|
||||
@override
|
||||
visitMethodInvocation(MethodInvocation node) {
|
||||
if (isInlineJS(node.methodName.staticElement)) {
|
||||
// Don't cast our inline-JS code in SDK.
|
||||
ast_properties.setImplicitCast(node, null);
|
||||
}
|
||||
visitExpression(node);
|
||||
}
|
||||
|
||||
@override
|
||||
visitParenthesizedExpression(ParenthesizedExpression node) {
|
||||
super.visitParenthesizedExpression(node);
|
||||
node.staticType = node.expression.staticType;
|
||||
}
|
||||
|
||||
@override
|
||||
void visitForStatement(ForStatement node) {
|
||||
var forLoopParts = node.forLoopParts;
|
||||
if (forLoopParts is ForEachParts) {
|
||||
// Visit other children.
|
||||
forLoopParts.iterable.accept(this);
|
||||
node.body.accept(this);
|
||||
} else {
|
||||
super.visitForStatement(node);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void visitForElement(ForElement node) {
|
||||
var forLoopParts = node.forLoopParts;
|
||||
if (forLoopParts is ForEachParts) {
|
||||
// Visit other children.
|
||||
forLoopParts.iterable.accept(this);
|
||||
node.body.accept(this);
|
||||
} else {
|
||||
super.visitForElement(node);
|
||||
}
|
||||
}
|
||||
|
||||
void _replaceNode(AstNode parent, AstNode oldNode, AstNode newNode) {
|
||||
if (!identical(oldNode, newNode)) {
|
||||
var replaced = parent.accept(NodeReplacer(oldNode, newNode));
|
||||
// It looks like NodeReplacer will always return true.
|
||||
// It does throw IllegalArgumentException though, if child is not found.
|
||||
assert(replaced);
|
||||
}
|
||||
}
|
||||
|
||||
T _clone<T extends AstNode>(T node) {
|
||||
var copy = node.accept(cloner) as T;
|
||||
ResolutionCopier.copyResolutionData(node, copy);
|
||||
return copy;
|
||||
}
|
||||
}
|
||||
|
||||
class _TreeCloner extends AstCloner {
|
||||
void _cloneProperties(AstNode clone, AstNode node) {
|
||||
if (clone is Expression && node is Expression) {
|
||||
ast_properties.setImplicitCast(
|
||||
clone, ast_properties.getImplicitCast(node));
|
||||
ast_properties.setImplicitOperationCast(
|
||||
clone, ast_properties.getImplicitOperationCast(node));
|
||||
ast_properties.setImplicitSpreadCast(
|
||||
clone, ast_properties.getImplicitSpreadCast(node));
|
||||
ast_properties.setImplicitSpreadKeyCast(
|
||||
clone, ast_properties.getImplicitSpreadKeyCast(node));
|
||||
ast_properties.setImplicitSpreadValueCast(
|
||||
clone, ast_properties.getImplicitSpreadValueCast(node));
|
||||
ast_properties.setIsDynamicInvoke(
|
||||
clone, ast_properties.isDynamicInvoke(node));
|
||||
}
|
||||
if (clone is Declaration && node is Declaration) {
|
||||
ast_properties.setClassCovariantParameters(
|
||||
clone, ast_properties.getClassCovariantParameters(node));
|
||||
ast_properties.setSuperclassCovariantParameters(
|
||||
clone, ast_properties.getSuperclassCovariantParameters(node));
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
E cloneNode<E extends AstNode>(E node) {
|
||||
var clone = super.cloneNode(node);
|
||||
_cloneProperties(clone, node);
|
||||
return clone;
|
||||
}
|
||||
|
||||
@override
|
||||
List<E> cloneNodeList<E extends AstNode>(List<E> list) {
|
||||
var clone = super.cloneNodeList(list);
|
||||
for (int i = 0, len = list.length; i < len; i++) {
|
||||
_cloneProperties(clone[i], list[i]);
|
||||
}
|
||||
return clone;
|
||||
}
|
||||
|
||||
// TODO(jmesserly): ResolutionCopier is not copying this yet.
|
||||
@override
|
||||
BlockFunctionBody visitBlockFunctionBody(BlockFunctionBody node) {
|
||||
var clone = super.visitBlockFunctionBody(node);
|
||||
(clone as FunctionBodyImpl).localVariableInfo =
|
||||
(node as FunctionBodyImpl).localVariableInfo;
|
||||
return clone;
|
||||
}
|
||||
|
||||
@override
|
||||
ExpressionFunctionBody visitExpressionFunctionBody(
|
||||
ExpressionFunctionBody node) {
|
||||
var clone = super.visitExpressionFunctionBody(node);
|
||||
(clone as FunctionBodyImpl).localVariableInfo =
|
||||
(node as FunctionBodyImpl).localVariableInfo;
|
||||
return clone;
|
||||
}
|
||||
|
||||
@override
|
||||
FunctionExpressionInvocation visitFunctionExpressionInvocation(
|
||||
FunctionExpressionInvocation node) {
|
||||
var clone = super.visitFunctionExpressionInvocation(node);
|
||||
(clone as FunctionExpressionInvocationImpl).typeArgumentTypes =
|
||||
node.typeArgumentTypes;
|
||||
return clone;
|
||||
}
|
||||
|
||||
@override
|
||||
MethodInvocation visitMethodInvocation(MethodInvocation node) {
|
||||
var clone = super.visitMethodInvocation(node);
|
||||
(clone as MethodInvocationImpl).typeArgumentTypes = node.typeArgumentTypes;
|
||||
return clone;
|
||||
}
|
||||
|
||||
// TODO(jmesserly): workaround for
|
||||
// https://github.com/dart-lang/sdk/issues/26368
|
||||
@override
|
||||
TypeName visitTypeName(TypeName node) {
|
||||
var clone = super.visitTypeName(node);
|
||||
clone.type = node.type;
|
||||
return clone;
|
||||
}
|
||||
}
|
|
@ -1,170 +0,0 @@
|
|||
// Copyright (c) 2015, 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/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/src/generated/constant.dart';
|
||||
import 'package:analyzer/error/listener.dart'
|
||||
show AnalysisErrorListener, ErrorReporter;
|
||||
import 'package:analyzer/src/generated/resolver.dart' show TypeProvider;
|
||||
import 'package:analyzer/src/generated/source.dart' show Source;
|
||||
import 'package:analyzer/src/dart/ast/ast.dart';
|
||||
|
||||
/// True is the expression can be evaluated multiple times without causing
|
||||
/// code execution. This is true for final fields. This can be true for local
|
||||
/// variables, if:
|
||||
///
|
||||
/// * they are not assigned within the [context] scope.
|
||||
/// * they are not assigned in a function closure anywhere.
|
||||
///
|
||||
/// This method is used to avoid creating temporaries in cases where we know
|
||||
/// we can safely re-evaluate [node] multiple times in [context]. This lets
|
||||
/// us generate prettier code.
|
||||
///
|
||||
/// This method is conservative: it should never return `true` unless it is
|
||||
/// certain the [node] is stateless, because generated code may rely on the
|
||||
/// correctness of a `true` value. However it may return `false` for things
|
||||
/// that are in fact, stateless.
|
||||
bool isStateless(FunctionBody function, Expression node, [AstNode context]) {
|
||||
// `this` and `super` cannot be reassigned.
|
||||
if (node is ThisExpression || node is SuperExpression) return true;
|
||||
if (node is Identifier) {
|
||||
var e = node.staticElement;
|
||||
if (e is PropertyAccessorElement) {
|
||||
e = (e as PropertyAccessorElement).variable;
|
||||
}
|
||||
if (e is VariableElement && !e.isSynthetic) {
|
||||
if (e.isFinal) return true;
|
||||
if (e is LocalVariableElement || e is ParameterElement) {
|
||||
// make sure the local isn't mutated in the context.
|
||||
return !isPotentiallyMutated(function, e, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns true if the local variable is potentially mutated within [context].
|
||||
/// This accounts for closures that may have been created outside of [context].
|
||||
bool isPotentiallyMutated(FunctionBody function, VariableElement e,
|
||||
[AstNode context]) {
|
||||
if (function is FunctionBodyImpl && function.localVariableInfo == null) {
|
||||
// TODO(jmesserly): this is a caching bug in Analyzer. They don't restore
|
||||
// this info in some cases.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (function.isPotentiallyMutatedInClosure(e)) return true;
|
||||
if (function.isPotentiallyMutatedInScope(e)) {
|
||||
// Need to visit the context looking for assignment to this local.
|
||||
if (context != null) {
|
||||
var visitor = _AssignmentFinder(e);
|
||||
context.accept(visitor);
|
||||
return visitor._potentiallyMutated;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Adapted from VariableResolverVisitor. Finds an assignment to a given
|
||||
/// local variable.
|
||||
class _AssignmentFinder extends RecursiveAstVisitor {
|
||||
final VariableElement _variable;
|
||||
bool _potentiallyMutated = false;
|
||||
|
||||
_AssignmentFinder(this._variable);
|
||||
|
||||
@override
|
||||
visitSimpleIdentifier(SimpleIdentifier node) {
|
||||
// Ignore if qualified.
|
||||
AstNode parent = node.parent;
|
||||
if (parent is PrefixedIdentifier && identical(parent.identifier, node)) {
|
||||
return;
|
||||
}
|
||||
if (parent is PropertyAccess && identical(parent.propertyName, node)) {
|
||||
return;
|
||||
}
|
||||
if (parent is MethodInvocation && identical(parent.methodName, node)) {
|
||||
return;
|
||||
}
|
||||
if (parent is ConstructorName) return;
|
||||
if (parent is Label) return;
|
||||
|
||||
if (node.inSetterContext() && node.staticElement == _variable) {
|
||||
_potentiallyMutated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ConstFieldVisitor {
|
||||
final ConstantVisitor constantVisitor;
|
||||
|
||||
ConstFieldVisitor(
|
||||
TypeProvider typeProvider, DeclaredVariables declaredVariables,
|
||||
{Source dummySource})
|
||||
: constantVisitor = ConstantVisitor(
|
||||
ConstantEvaluationEngine(typeProvider, declaredVariables),
|
||||
ErrorReporter(AnalysisErrorListener.NULL_LISTENER, dummySource));
|
||||
|
||||
// TODO(jmesserly): this is used to determine if the field initialization is
|
||||
// side effect free. We should make the check more general, as things like
|
||||
// list/map literals/regexp are also side effect free and fairly common
|
||||
// to use as field initializers.
|
||||
bool isFieldInitConstant(VariableDeclaration field) =>
|
||||
field.initializer == null || computeConstant(field) != null;
|
||||
|
||||
DartObject computeConstant(VariableDeclaration field) {
|
||||
// If the constant is already computed by ConstantEvaluator, just return it.
|
||||
VariableElement element = field.declaredElement;
|
||||
var result = element.computeConstantValue();
|
||||
if (result != null) return result;
|
||||
|
||||
// ConstantEvaluator will not compute constants for non-const fields,
|
||||
// so run ConstantVisitor for those to figure out if the initializer is
|
||||
// actually a constant (and therefore side effect free to evaluate).
|
||||
assert(!field.isConst);
|
||||
|
||||
var initializer = field.initializer;
|
||||
if (initializer == null) return null;
|
||||
return initializer.accept(constantVisitor);
|
||||
}
|
||||
}
|
||||
|
||||
class LabelContinueFinder extends SimpleAstVisitor {
|
||||
var found = false;
|
||||
visit(Statement s) {
|
||||
if (!found && s != null) s.accept(this);
|
||||
}
|
||||
|
||||
@override
|
||||
visitBlock(Block node) => node.statements.forEach(visit);
|
||||
@override
|
||||
visitWhileStatement(WhileStatement node) => visit(node.body);
|
||||
@override
|
||||
visitDoStatement(DoStatement node) => visit(node.body);
|
||||
@override
|
||||
visitForStatement(ForStatement node) => visit(node.body);
|
||||
@override
|
||||
visitLabeledStatement(LabeledStatement node) => visit(node.statement);
|
||||
@override
|
||||
visitContinueStatement(ContinueStatement node) => found = node.label != null;
|
||||
@override
|
||||
visitSwitchStatement(SwitchStatement node) {
|
||||
node.members.forEach((m) => m.statements.forEach(visit));
|
||||
}
|
||||
|
||||
@override
|
||||
visitIfStatement(IfStatement node) {
|
||||
visit(node.thenStatement);
|
||||
visit(node.elseStatement);
|
||||
}
|
||||
|
||||
@override
|
||||
visitTryStatement(TryStatement node) {
|
||||
node.body.accept(this);
|
||||
node.finallyBlock.accept(this);
|
||||
}
|
||||
}
|
|
@ -1,279 +0,0 @@
|
|||
// Copyright (c) 2015, 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 'dart:collection';
|
||||
|
||||
import 'package:analyzer/dart/element/element.dart';
|
||||
import 'package:analyzer/dart/element/nullability_suffix.dart';
|
||||
import 'package:analyzer/dart/element/type.dart';
|
||||
import 'package:analyzer/src/dart/element/element.dart';
|
||||
import 'package:analyzer/src/dart/element/member.dart' show TypeParameterMember;
|
||||
|
||||
import '../analyzer/element_helpers.dart';
|
||||
import '../compiler/js_names.dart' as js_ast;
|
||||
import '../js_ast/js_ast.dart' as js_ast;
|
||||
import '../js_ast/js_ast.dart' show js;
|
||||
|
||||
/// Return the [InterfaceType] that itself has the legacy nullability, and for
|
||||
/// every type parameter a [TypeParameterType] instance with the legacy
|
||||
/// nullability is used as the corresponding type argument.
|
||||
InterfaceType getLegacyRawClassType(ClassElement element) {
|
||||
var typeParameters = element.typeParameters;
|
||||
var typeArguments = typeParameters.map(getLegacyTypeParameterType).toList();
|
||||
return element.instantiate(
|
||||
typeArguments: typeArguments,
|
||||
nullabilitySuffix: NullabilitySuffix.star,
|
||||
);
|
||||
}
|
||||
|
||||
/// Return the [TypeParameterType] with the legacy nullability for the given
|
||||
/// type parameter [element].
|
||||
TypeParameterType getLegacyTypeParameterType(TypeParameterElement element) {
|
||||
return element.instantiate(nullabilitySuffix: NullabilitySuffix.star);
|
||||
}
|
||||
|
||||
/// Return the raw type (i.e. the type where type parameters are replaced with
|
||||
/// the corresponding [TypeParameterType]) for the given [element]. The type
|
||||
/// returned, and every [TypeParameterType] instance will have the legacy
|
||||
/// nullability suffix.
|
||||
DartType getLegacyElementType(TypeDefiningElement element) {
|
||||
if (element is ClassElement) {
|
||||
return getLegacyRawClassType(element);
|
||||
} else if (element is DynamicElementImpl) {
|
||||
return element.type;
|
||||
} else if (element is TypeParameterElement) {
|
||||
return getLegacyTypeParameterType(element);
|
||||
} else {
|
||||
throw StateError('Unsupported element: (${element.runtimeType}) $element');
|
||||
}
|
||||
}
|
||||
|
||||
Set<TypeParameterElement> freeTypeParameters(DartType t) {
|
||||
var result = Set<TypeParameterElement>();
|
||||
void find(DartType t) {
|
||||
if (t is TypeParameterType) {
|
||||
result.add(t.element);
|
||||
} else if (t is FunctionType) {
|
||||
if (t.name != '' && t.name != null) {
|
||||
// For a typedef type like `Foo<T>`, we only need to check if the
|
||||
// type argument `T` has or is a free type parameter.
|
||||
//
|
||||
// For example, if `Foo` is a typedef type and `S` is a type parameter,
|
||||
// then the type `Foo<List<S>>` has `S` as its free type parameter,
|
||||
// regardless of what function is declared by the typedef.
|
||||
//
|
||||
// Also we need to find free type parameters whether or not they're
|
||||
// actually used by the typedef's function type (for example,
|
||||
// `typedef Foo<T> = int Function()` does not use `T`). So we must visit
|
||||
// the type arguments, instead of the substituted parameter and return
|
||||
// types.
|
||||
t.typeArguments.forEach(find);
|
||||
} else {
|
||||
find(t.returnType);
|
||||
t.parameters.forEach((p) => find(p.type));
|
||||
t.typeFormals.forEach((p) => find(p.bound));
|
||||
t.typeFormals.forEach(result.remove);
|
||||
}
|
||||
} else if (t is InterfaceType) {
|
||||
t.typeArguments.forEach(find);
|
||||
}
|
||||
}
|
||||
|
||||
find(t);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// _CacheTable tracks cache variables for variables that
|
||||
/// are emitted in place with a hoisted variable for a cache.
|
||||
class _CacheTable {
|
||||
/// Mapping from types to their canonical names.
|
||||
// Use a LinkedHashMap to maintain key insertion order so the generated code
|
||||
// is stable under slight perturbation. (If this is not good enough we could
|
||||
// sort by name to canonicalize order.)
|
||||
final _names = LinkedHashMap<DartType, js_ast.TemporaryId>(
|
||||
equals: typesAreEqual, hashCode: typeHashCode);
|
||||
Iterable<DartType> get keys => _names.keys.toList();
|
||||
|
||||
js_ast.Statement _dischargeType(DartType type) {
|
||||
var name = _names.remove(type);
|
||||
if (name != null) {
|
||||
return js.statement('let #;', [name]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Emit a list of statements declaring the cache variables for
|
||||
/// types tracked by this table. If [typeFilter] is given,
|
||||
/// only emit the types listed in the filter.
|
||||
List<js_ast.Statement> discharge([Iterable<DartType> typeFilter]) {
|
||||
var decls = <js_ast.Statement>[];
|
||||
var types = typeFilter ?? keys;
|
||||
for (var t in types) {
|
||||
var stmt = _dischargeType(t);
|
||||
if (stmt != null) decls.add(stmt);
|
||||
}
|
||||
return decls;
|
||||
}
|
||||
|
||||
bool isNamed(DartType type) => _names.containsKey(type);
|
||||
|
||||
String _safeTypeName(String name) {
|
||||
if (name == "<bottom>") return "bottom";
|
||||
return name;
|
||||
}
|
||||
|
||||
String _typeString(DartType type, {bool flat = false}) {
|
||||
if (type is ParameterizedType && type.name != null) {
|
||||
var clazz = type.name;
|
||||
var params = type.typeArguments;
|
||||
if (params == null) return clazz;
|
||||
if (params.every((p) => p.isDynamic)) return clazz;
|
||||
var paramStrings = params.map(_typeString);
|
||||
var paramString = paramStrings.join("\$");
|
||||
return "${clazz}Of${paramString}";
|
||||
}
|
||||
if (type is FunctionType) {
|
||||
if (flat) return "Fn";
|
||||
var rType = _typeString(type.returnType, flat: true);
|
||||
var paramStrings = type.normalParameterTypes
|
||||
.take(3)
|
||||
.map((p) => _typeString(p, flat: true));
|
||||
var paramString = paramStrings.join("And");
|
||||
var count = type.normalParameterTypes.length;
|
||||
if (count > 3 ||
|
||||
type.namedParameterTypes.isNotEmpty ||
|
||||
type.optionalParameterTypes.isNotEmpty) {
|
||||
paramString = "${paramString}__";
|
||||
} else if (count == 0) {
|
||||
paramString = "Void";
|
||||
}
|
||||
return "${paramString}To${rType}";
|
||||
}
|
||||
if (type is TypeParameterType) return type.name;
|
||||
return _safeTypeName(type.name ?? "type");
|
||||
}
|
||||
|
||||
/// Heuristically choose a good name for the cache and generator
|
||||
/// variables.
|
||||
js_ast.TemporaryId chooseTypeName(DartType type) {
|
||||
return js_ast.TemporaryId(_typeString(type));
|
||||
}
|
||||
}
|
||||
|
||||
/// _GeneratorTable tracks types which have been
|
||||
/// named and hoisted.
|
||||
class _GeneratorTable extends _CacheTable {
|
||||
final _defs = LinkedHashMap<DartType, js_ast.Expression>(
|
||||
equals: typesAreEqual, hashCode: typeHashCode);
|
||||
|
||||
final js_ast.Identifier _runtimeModule;
|
||||
|
||||
_GeneratorTable(this._runtimeModule);
|
||||
|
||||
@override
|
||||
js_ast.Statement _dischargeType(DartType t) {
|
||||
var name = _names.remove(t);
|
||||
if (name != null) {
|
||||
js_ast.Expression init = _defs.remove(t);
|
||||
assert(init != null);
|
||||
return js.statement('let # = () => ((# = #.constFn(#))());',
|
||||
[name, name, _runtimeModule, init]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// If [type] does not already have a generator name chosen for it,
|
||||
/// assign it one, using [typeRep] as the initializer for it.
|
||||
/// Emit the generator name.
|
||||
js_ast.TemporaryId _nameType(DartType type, js_ast.Expression typeRep) {
|
||||
var temp = _names[type];
|
||||
if (temp == null) {
|
||||
_names[type] = temp = chooseTypeName(type);
|
||||
_defs[type] = typeRep;
|
||||
}
|
||||
return temp;
|
||||
}
|
||||
}
|
||||
|
||||
class TypeTable {
|
||||
/// Generator variable names for hoisted types.
|
||||
final _GeneratorTable _generators;
|
||||
|
||||
/// Mapping from type parameters to the types which must have their
|
||||
/// cache/generator variables discharged at the binding site for the
|
||||
/// type variable since the type definition depends on the type
|
||||
/// parameter.
|
||||
final _scopeDependencies = <TypeParameterElement, List<DartType>>{};
|
||||
|
||||
TypeTable(js_ast.Identifier runtime) : _generators = _GeneratorTable(runtime);
|
||||
|
||||
/// Emit a list of statements declaring the cache variables and generator
|
||||
/// definitions tracked by the table. If [formals] is present, only
|
||||
/// emit the definitions which depend on the formals.
|
||||
List<js_ast.Statement> discharge([List<TypeParameterElement> formals]) {
|
||||
var filter = formals?.expand((p) => _scopeDependencies[p] ?? <DartType>[]);
|
||||
var stmts = [_generators].expand((c) => c.discharge(filter)).toList();
|
||||
formals?.forEach(_scopeDependencies.remove);
|
||||
return stmts;
|
||||
}
|
||||
|
||||
/// Record the dependencies of the type on its free variables
|
||||
bool recordScopeDependencies(DartType type) {
|
||||
var freeVariables = freeTypeParameters(type);
|
||||
// TODO(leafp): This is a hack to avoid trying to hoist out of
|
||||
// generic functions and generic function types. This often degrades
|
||||
// readability to little or no benefit. It would be good to do this
|
||||
// when we know that we can hoist it to an outer scope, but for
|
||||
// now we just disable it.
|
||||
if (freeVariables.any((i) =>
|
||||
i.enclosingElement is FunctionTypedElement ||
|
||||
// Strict function types don't have element, so their type parameters
|
||||
// don't have any enclosing element. Analyzer started returning
|
||||
// strict function types for generic methods.
|
||||
i.enclosingElement == null)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (var free in freeVariables) {
|
||||
// If `free` is a promoted type parameter, get the original one so we can
|
||||
// find it in our map.
|
||||
var key = free is TypeParameterMember ? free.baseElement : free;
|
||||
_scopeDependencies.putIfAbsent(key, () => []).add(type);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Given a type [type], and a JS expression [typeRep] which implements it,
|
||||
/// add the type and its representation to the table, returning an
|
||||
/// expression which implements the type (but which caches the value).
|
||||
js_ast.Expression nameType(
|
||||
ParameterizedType type, js_ast.Expression typeRep) {
|
||||
if (!_generators.isNamed(type) && recordScopeDependencies(type)) {
|
||||
return typeRep;
|
||||
}
|
||||
var name = _generators._nameType(type, typeRep);
|
||||
return js.call('#()', [name]);
|
||||
}
|
||||
|
||||
/// Like [nameType] but for function types.
|
||||
///
|
||||
/// The boolean parameter [definite] distinguishes between definite function
|
||||
/// types and other types (since the same DartType may have different
|
||||
/// representations as definite and indefinite function types).
|
||||
///
|
||||
/// The boolean parameter [lazy] indicates that the resulting expression
|
||||
/// should be a function that is invoked to compute the type, rather than the
|
||||
/// type itself. This allows better integration with `lazyFn`, avoiding an
|
||||
/// extra level of indirection.
|
||||
js_ast.Expression nameFunctionType(
|
||||
FunctionType type, js_ast.Expression typeRep,
|
||||
{bool lazy = false}) {
|
||||
if (!_generators.isNamed(type) && recordScopeDependencies(type)) {
|
||||
return lazy ? js_ast.ArrowFun([], typeRep) : typeRep;
|
||||
}
|
||||
|
||||
var name = _generators._nameType(type, typeRep);
|
||||
return lazy ? name : js.call('#()', [name]);
|
||||
}
|
||||
}
|
|
@ -9,10 +9,12 @@ import 'package:front_end/src/api_unstable/ddc.dart'
|
|||
show InitializedCompilerState, parseExperimentalArguments;
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'module_builder.dart';
|
||||
import '../analyzer/driver.dart' show CompilerAnalysisDriver;
|
||||
import '../kernel/command.dart' as kernel_compiler;
|
||||
|
||||
/// Shared code between Analyzer and Kernel CLI interfaces.
|
||||
// TODO(nshahan) Merge all of this file the locations where they are used in
|
||||
// the kernel (only) version of DDC.
|
||||
|
||||
/// Previously was shared code between Analyzer and Kernel CLI interfaces.
|
||||
///
|
||||
/// This file should only implement functionality that does not depend on
|
||||
/// Analyzer/Kernel imports.
|
||||
|
@ -45,7 +47,7 @@ Map<String, String> sdkLibraryVariables = {
|
|||
'dart.library.web_sql': 'true',
|
||||
};
|
||||
|
||||
/// Shared compiler options between `dartdevc` kernel and analyzer backends.
|
||||
/// Compiler options for the `dartdevc` backend.
|
||||
class SharedCompilerOptions {
|
||||
/// Whether to emit the source mapping file.
|
||||
///
|
||||
|
@ -424,28 +426,15 @@ class CompilerResult {
|
|||
/// Optionally provides the front_end state from the previous compilation,
|
||||
/// which can be passed to [compile] to potentially speed up the next
|
||||
/// compilation.
|
||||
///
|
||||
/// This field is unused when using the Analyzer-backend for DDC.
|
||||
final InitializedCompilerState kernelState;
|
||||
|
||||
/// Optionally provides the analyzer state from the previous compilation,
|
||||
/// which can be passed to [compile] to potentially speeed up the next
|
||||
/// compilation.
|
||||
///
|
||||
/// This field is unused when using the Kernel-backend for DDC.
|
||||
// TODO(38777) Cleanup when we delete all the DDC code.
|
||||
final CompilerAnalysisDriver analyzerState;
|
||||
|
||||
/// The process exit code of the compiler.
|
||||
final int exitCode;
|
||||
|
||||
CompilerResult(this.exitCode, {this.kernelState, this.analyzerState}) {
|
||||
assert(kernelState == null || analyzerState == null,
|
||||
'kernel and analyzer state should not both be supplied');
|
||||
}
|
||||
CompilerResult(this.exitCode, {this.kernelState});
|
||||
|
||||
/// Gets the kernel or analyzer compiler state, if any.
|
||||
Object get compilerState => kernelState ?? analyzerState;
|
||||
/// Gets the kernel compiler state, if any.
|
||||
Object get compilerState => kernelState;
|
||||
|
||||
/// Whether the program compiled without any fatal errors (equivalent to
|
||||
/// [exitCode] == 0).
|
||||
|
@ -464,9 +453,6 @@ class CompilerResult {
|
|||
///
|
||||
/// [isBatch]/[isWorker] mode are preprocessed because they can combine
|
||||
/// argument lists from the initial invocation and from batch/worker jobs.
|
||||
///
|
||||
/// [isKernel] is also preprocessed because the Kernel backend supports
|
||||
/// different options compared to the Analyzer backend.
|
||||
class ParsedArguments {
|
||||
/// The user's arguments to the compiler for this compialtion.
|
||||
final List<String> rest;
|
||||
|
@ -485,8 +471,7 @@ class ParsedArguments {
|
|||
|
||||
/// Whether to use the Kernel-based back end for dartdevc.
|
||||
///
|
||||
/// This is similar to the Analyzer-based back end, but uses Kernel trees
|
||||
/// instead of Analyzer trees for representing the Dart code.
|
||||
/// This is always true now and will be removed in a future version.
|
||||
final bool isKernel;
|
||||
|
||||
/// Whether to re-use the last compiler result when in a worker.
|
||||
|
@ -539,8 +524,6 @@ class ParsedArguments {
|
|||
isWorker = true;
|
||||
} else if (arg == '--batch') {
|
||||
isBatch = true;
|
||||
} else if (arg == '--kernel' || arg == '-k') {
|
||||
isKernel = true;
|
||||
} else if (arg == '--reuse-compiler-result') {
|
||||
reuseResult = true;
|
||||
} else if (arg == '--use-incremental-compiler') {
|
||||
|
@ -579,7 +562,7 @@ class ParsedArguments {
|
|||
return ParsedArguments._(rest.toList()..addAll(newArgs.rest),
|
||||
isWorker: isWorker,
|
||||
isBatch: isBatch,
|
||||
isKernel: isKernel || newArgs.isKernel,
|
||||
isKernel: isKernel,
|
||||
reuseResult: reuseResult || newArgs.reuseResult,
|
||||
useIncrementalCompiler:
|
||||
useIncrementalCompiler || newArgs.useIncrementalCompiler);
|
||||
|
|
|
@ -108,9 +108,12 @@ Future<CompilerResult> _compile(List<String> args,
|
|||
..addOption('libraries-file',
|
||||
help: 'The path to the libraries.json file for the sdk.')
|
||||
..addOption('used-inputs-file',
|
||||
help: 'If set, the file to record inputs used.', hide: true);
|
||||
help: 'If set, the file to record inputs used.', hide: true)
|
||||
..addFlag('kernel',
|
||||
abbr: 'k',
|
||||
help: 'Deprecated and ignored. To be removed in a future release.',
|
||||
hide: true);
|
||||
SharedCompilerOptions.addArguments(argParser);
|
||||
|
||||
var declaredVariables = parseAndRemoveDeclaredVariables(args);
|
||||
ArgResults argResults;
|
||||
try {
|
||||
|
|
|
@ -1,328 +0,0 @@
|
|||
// Copyright (c) 2019, 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.
|
||||
|
||||
/// Test the modular compilation pipeline of ddc.
|
||||
///
|
||||
/// This is a shell that runs multiple tests, one per folder under `data/`.
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:modular_test/src/io_pipeline.dart';
|
||||
import 'package:modular_test/src/pipeline.dart';
|
||||
import 'package:modular_test/src/suite.dart';
|
||||
import 'package:modular_test/src/runner.dart';
|
||||
|
||||
// TODO(vsm): Hack until we have either:
|
||||
// (1) an NNBD version of the below
|
||||
// (2) the ability to compile the primary version with NNBD
|
||||
List<String> _nnbdOptOut = ['sdk'];
|
||||
String _test_package = 'ddc_modular_test';
|
||||
|
||||
Uri sdkRoot = Platform.script.resolve("../../../");
|
||||
Options _options;
|
||||
String _dartdevcScript;
|
||||
String _buildSdkScript;
|
||||
String _patchSdkScript;
|
||||
String _librariesJson;
|
||||
String _librariesJsonNnbd;
|
||||
|
||||
main(List<String> args) async {
|
||||
_options = Options.parse(args);
|
||||
await _resolveScripts();
|
||||
await runSuite(
|
||||
sdkRoot.resolve('tests/modular/'),
|
||||
'tests/modular',
|
||||
_options,
|
||||
IOPipeline([
|
||||
DDCStep(),
|
||||
RunD8(),
|
||||
], cacheSharedModules: true));
|
||||
|
||||
// DDC only test suite.
|
||||
await runSuite(
|
||||
sdkRoot.resolve('tests/compiler/dartdevc/modular/'),
|
||||
'tests/compiler/dartdevc/modular',
|
||||
_options,
|
||||
IOPipeline([
|
||||
DDCStep(),
|
||||
RunD8(),
|
||||
], cacheSharedModules: true));
|
||||
}
|
||||
|
||||
const sumId = DataId("sum");
|
||||
const jsId = DataId("js");
|
||||
const txtId = DataId("txt");
|
||||
|
||||
class DDCStep implements IOModularStep {
|
||||
@override
|
||||
List<DataId> get resultData => const [sumId, jsId];
|
||||
|
||||
@override
|
||||
bool get needsSources => true;
|
||||
|
||||
@override
|
||||
List<DataId> get dependencyDataNeeded => const [sumId];
|
||||
|
||||
@override
|
||||
List<DataId> get moduleDataNeeded => const [];
|
||||
|
||||
@override
|
||||
bool get onlyOnMain => false;
|
||||
|
||||
@override
|
||||
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
|
||||
List<String> flags) async {
|
||||
if (_options.verbose) print("\nstep: ddc on $module");
|
||||
|
||||
Set<Module> transitiveDependencies = computeTransitiveDependencies(module);
|
||||
await _createPackagesFile(module, root, transitiveDependencies);
|
||||
|
||||
ProcessResult result;
|
||||
|
||||
bool nnbd = flags.contains('non-nullable');
|
||||
bool allowErrors = nnbd && _nnbdOptOut.contains(module.name);
|
||||
|
||||
if (module.isSdk) {
|
||||
assert(transitiveDependencies.isEmpty);
|
||||
|
||||
// Apply patches.
|
||||
result = await _runProcess(
|
||||
Platform.resolvedExecutable,
|
||||
[
|
||||
_patchSdkScript,
|
||||
'--target',
|
||||
'dartdevc',
|
||||
'--libraries',
|
||||
if (nnbd) _librariesJsonNnbd else _librariesJson,
|
||||
'--out',
|
||||
'patched_sdk/',
|
||||
if (nnbd) '--nnbd'
|
||||
],
|
||||
root.toFilePath());
|
||||
_checkExitCode(result, this, module);
|
||||
|
||||
// Build the SDK.
|
||||
result = await _runProcess(
|
||||
Platform.resolvedExecutable,
|
||||
[
|
||||
_buildSdkScript,
|
||||
'--dart-sdk',
|
||||
'patched_sdk',
|
||||
'--dart-sdk-summary=build',
|
||||
'--summary-out',
|
||||
'${toUri(module, sumId)}',
|
||||
'--modules=es6',
|
||||
if (allowErrors) '--unsafe-force-compile',
|
||||
for (String flag in flags) '--enable-experiment=$flag',
|
||||
'-o',
|
||||
'${toUri(module, jsId)}',
|
||||
],
|
||||
root.toFilePath());
|
||||
_checkExitCode(result, this, module);
|
||||
} else {
|
||||
Module sdkModule = module.dependencies.firstWhere((m) => m.isSdk);
|
||||
List<String> sources = module.sources
|
||||
.map((relativeUri) => _sourceToImportUri(module, relativeUri))
|
||||
.toList();
|
||||
Map<String, String> _urlMappings = {};
|
||||
if (!module.isPackage) {
|
||||
for (var source in module.sources) {
|
||||
var importUri = _sourceToImportUri(module, source);
|
||||
_urlMappings[importUri] = '$source';
|
||||
}
|
||||
}
|
||||
for (var dependency in transitiveDependencies) {
|
||||
if (!dependency.isPackage && !dependency.isSdk) {
|
||||
for (var source in dependency.sources) {
|
||||
var importUri = _sourceToImportUri(dependency, source);
|
||||
_urlMappings[importUri] = '$source';
|
||||
}
|
||||
}
|
||||
}
|
||||
var extraArgs = [
|
||||
'--dart-sdk-summary',
|
||||
'${toUri(sdkModule, sumId)}',
|
||||
'--packages',
|
||||
'.packages',
|
||||
];
|
||||
|
||||
Uri output = toUri(module, jsId);
|
||||
|
||||
List<String> args = [
|
||||
'--packages=${sdkRoot.toFilePath()}.packages',
|
||||
_dartdevcScript,
|
||||
'--modules=es6',
|
||||
'--summarize',
|
||||
'--no-source-map',
|
||||
...sources,
|
||||
if (_urlMappings.isNotEmpty)
|
||||
'--url-mapping=${_urlMappings.entries.map((entry) => '${entry.key},${entry.value}').join(',')}',
|
||||
...extraArgs,
|
||||
for (String flag in flags) '--enable-experiment=$flag',
|
||||
...(transitiveDependencies
|
||||
.where((m) => !m.isSdk)
|
||||
.expand((m) => ['-s', '${toUri(m, sumId)}'])),
|
||||
'-o',
|
||||
'$output',
|
||||
];
|
||||
result = await _runProcess(
|
||||
Platform.resolvedExecutable, args, root.toFilePath());
|
||||
_checkExitCode(result, this, module);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void notifyCached(Module module) {
|
||||
if (_options.verbose) print("\ncached step: ddc on $module");
|
||||
}
|
||||
}
|
||||
|
||||
class RunD8 implements IOModularStep {
|
||||
@override
|
||||
List<DataId> get resultData => const [txtId];
|
||||
|
||||
@override
|
||||
bool get needsSources => false;
|
||||
|
||||
@override
|
||||
List<DataId> get dependencyDataNeeded => const [jsId];
|
||||
|
||||
@override
|
||||
List<DataId> get moduleDataNeeded => const [jsId];
|
||||
|
||||
@override
|
||||
bool get onlyOnMain => true;
|
||||
|
||||
@override
|
||||
Future<void> execute(Module module, Uri root, ModuleDataToRelativeUri toUri,
|
||||
List<String> flags) async {
|
||||
if (_options.verbose) print("\nstep: d8 on $module");
|
||||
|
||||
// Rename sdk.js to dart_sdk.js (the alternative, but more hermetic solution
|
||||
// would be to rename the import on all other .js files, but seems
|
||||
// overkill/unnecessary.
|
||||
if (await File.fromUri(root.resolve('dart_sdk.js')).exists()) {
|
||||
throw 'error: dart_sdk.js already exists.';
|
||||
}
|
||||
|
||||
await File.fromUri(root.resolve('sdk.js'))
|
||||
.copy(root.resolve('dart_sdk.js').toFilePath());
|
||||
var runjs = '''
|
||||
import { dart, _isolate_helper } from 'dart_sdk.js';
|
||||
import { main } from 'main.js';
|
||||
_isolate_helper.startRootIsolate(() => {}, []);
|
||||
main.main();
|
||||
''';
|
||||
|
||||
var wrapper =
|
||||
root.resolveUri(toUri(module, jsId)).toFilePath() + ".wrapper.js";
|
||||
await File(wrapper).writeAsString(runjs);
|
||||
List<String> d8Args = ['--module', wrapper];
|
||||
var result = await _runProcess(
|
||||
sdkRoot.resolve(_d8executable).toFilePath(), d8Args, root.toFilePath());
|
||||
|
||||
_checkExitCode(result, this, module);
|
||||
|
||||
await File.fromUri(root.resolveUri(toUri(module, txtId)))
|
||||
.writeAsString(result.stdout as String);
|
||||
}
|
||||
|
||||
@override
|
||||
void notifyCached(Module module) {
|
||||
if (_options.verbose) print("\ncached step: d8 on $module");
|
||||
}
|
||||
}
|
||||
|
||||
void _checkExitCode(ProcessResult result, IOModularStep step, Module module) {
|
||||
if (result.exitCode != 0 || _options.verbose) {
|
||||
stdout.write(result.stdout);
|
||||
stderr.write(result.stderr);
|
||||
}
|
||||
if (result.exitCode != 0) {
|
||||
throw "${step.runtimeType} failed on $module:\n\n"
|
||||
"stdout:\n${result.stdout}\n\n"
|
||||
"stderr:\n${result.stderr}";
|
||||
}
|
||||
}
|
||||
|
||||
Future<ProcessResult> _runProcess(
|
||||
String command, List<String> arguments, String workingDirectory) {
|
||||
if (_options.verbose) {
|
||||
print('command:\n$command ${arguments.join(' ')} from $workingDirectory');
|
||||
}
|
||||
return Process.run(command, arguments, workingDirectory: workingDirectory);
|
||||
}
|
||||
|
||||
String get _d8executable {
|
||||
if (Platform.isWindows) {
|
||||
return 'third_party/d8/windows/d8.exe';
|
||||
} else if (Platform.isLinux) {
|
||||
return 'third_party/d8/linux/d8';
|
||||
} else if (Platform.isMacOS) {
|
||||
return 'third_party/d8/macos/d8';
|
||||
}
|
||||
throw UnsupportedError('Unsupported platform.');
|
||||
}
|
||||
|
||||
Future<void> _createPackagesFile(
|
||||
Module module, Uri root, Set<Module> transitiveDependencies) async {
|
||||
// We create a .packages file which defines the location of this module if
|
||||
// it is a package. The CFE requires that if a `package:` URI of a
|
||||
// dependency is used in an import, then we need that package entry in the
|
||||
// .packages file. However, after it checks that the definition exists, the
|
||||
// CFE will not actually use the resolved URI if a library for the import
|
||||
// URI is already found in one of the provided .dill files of the
|
||||
// dependencies. For that reason, and to ensure that a step only has access
|
||||
// to the files provided in a module, we generate a .packages with invalid
|
||||
// folders for other packages.
|
||||
// TODO(sigmund): follow up with the CFE to see if we can remove the need
|
||||
// for the .packages entry altogether if they won't need to read the
|
||||
// sources.
|
||||
var packagesContents = StringBuffer();
|
||||
if (module.isPackage) {
|
||||
packagesContents.write('${module.name}:${module.packageBase}\n');
|
||||
}
|
||||
for (Module dependency in transitiveDependencies) {
|
||||
if (dependency.isPackage) {
|
||||
packagesContents.write('${dependency.name}:unused\n');
|
||||
}
|
||||
}
|
||||
|
||||
await File.fromUri(root.resolve('.packages'))
|
||||
.writeAsString('$packagesContents');
|
||||
}
|
||||
|
||||
String _sourceToImportUri(Module module, Uri relativeUri) {
|
||||
if (module.isPackage) {
|
||||
var basePath = module.packageBase.path;
|
||||
var packageRelativePath = basePath == "./"
|
||||
? relativeUri.path
|
||||
: relativeUri.path.substring(basePath.length);
|
||||
return 'package:${module.name}/$packageRelativePath';
|
||||
} else {
|
||||
return 'package:${_test_package}/$relativeUri';
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _resolveScripts() async {
|
||||
Future<String> resolve(String sdkSourcePath,
|
||||
[String relativeSnapshotPath]) async {
|
||||
String result = sdkRoot.resolve(sdkSourcePath).toFilePath();
|
||||
if (_options.useSdk && relativeSnapshotPath != null) {
|
||||
String snapshot = Uri.file(Platform.resolvedExecutable)
|
||||
.resolve(relativeSnapshotPath)
|
||||
.toFilePath();
|
||||
if (await File(snapshot).exists()) {
|
||||
return snapshot;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
_dartdevcScript = await resolve(
|
||||
'pkg/dev_compiler/bin/dartdevc.dart', 'snapshots/dartdevc.dart.snapshot');
|
||||
_buildSdkScript = await resolve('pkg/dev_compiler/tool/build_sdk.dart');
|
||||
_patchSdkScript = await resolve('pkg/dev_compiler/tool/patch_sdk.dart');
|
||||
_librariesJson = await resolve('sdk/lib/libraries.json');
|
||||
_librariesJsonNnbd = await resolve('sdk_nnbd/lib/libraries.json');
|
||||
}
|
|
@ -1,4 +0,0 @@
|
|||
analyzer:
|
||||
strong-mode: true
|
||||
errors:
|
||||
undefined_class: ignore
|
|
@ -1,4 +0,0 @@
|
|||
analyzer:
|
||||
strong-mode: true
|
||||
errors:
|
||||
duplicate_definition: ignore
|
|
@ -1,92 +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 'dart:io';
|
||||
|
||||
import 'package:analyzer/src/command_line/arguments.dart';
|
||||
import 'package:analyzer/src/error/codes.dart';
|
||||
import 'package:analyzer/src/summary/summary_sdk.dart';
|
||||
import 'package:dev_compiler/src/analyzer/context.dart';
|
||||
import 'package:dev_compiler/src/analyzer/command.dart';
|
||||
import 'package:dev_compiler/src/analyzer/driver.dart';
|
||||
import 'package:dev_compiler/src/analyzer/module_compiler.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../testing.dart' show repoDirectory, testDirectory;
|
||||
|
||||
/// The `test/options` directory.
|
||||
final optionsDir = p.join(testDirectory, 'options');
|
||||
|
||||
/// Summary file for testing.
|
||||
final sdkSummaryFile = p.join(repoDirectory, 'gen', 'sdk', 'ddc_sdk.sum');
|
||||
|
||||
final sdkSummaryArgs = ['--$sdkSummaryPathOption', sdkSummaryFile];
|
||||
|
||||
void main() {
|
||||
test('basic', () {
|
||||
var options = AnalyzerOptions.basic()..analysisRoot = optionsDir;
|
||||
var driver = CompilerAnalysisDriver(options);
|
||||
var processors = driver.analysisOptions.errorProcessors;
|
||||
expect(processors, hasLength(1));
|
||||
expect(processors[0].code, CompileTimeErrorCode.UNDEFINED_CLASS.name);
|
||||
});
|
||||
|
||||
test('basic sdk summary', () {
|
||||
expect(File(sdkSummaryFile).existsSync(), isTrue);
|
||||
var options = AnalyzerOptions.basic(dartSdkSummaryPath: sdkSummaryFile)
|
||||
..analysisRoot = optionsDir;
|
||||
var driver = CompilerAnalysisDriver(options);
|
||||
var sdk = driver.dartSdk;
|
||||
expect(sdk, const TypeMatcher<SummaryBasedDartSdk>());
|
||||
var processors = driver.analysisOptions.errorProcessors;
|
||||
expect(processors, hasLength(1));
|
||||
expect(processors[0].code, CompileTimeErrorCode.UNDEFINED_CLASS.name);
|
||||
});
|
||||
|
||||
test('fromArgs', () {
|
||||
var args = <String>[];
|
||||
//TODO(danrubel) remove sdkSummaryArgs once all SDKs have summary file
|
||||
args.addAll(sdkSummaryArgs);
|
||||
var argResults = ddcArgParser().parse(args);
|
||||
var options = AnalyzerOptions.fromArguments(argResults)
|
||||
..analysisRoot = optionsDir;
|
||||
var driver = CompilerAnalysisDriver(options);
|
||||
var processors = driver.analysisOptions.errorProcessors;
|
||||
expect(processors, hasLength(1));
|
||||
expect(processors[0].code, CompileTimeErrorCode.UNDEFINED_CLASS.name);
|
||||
});
|
||||
|
||||
test('fromArgs options file 2', () {
|
||||
var optionsFile2 = p.join(optionsDir, 'analysis_options_2.yaml');
|
||||
expect(File(optionsFile2).existsSync(), isTrue);
|
||||
var args = <String>['--$analysisOptionsFileOption', optionsFile2];
|
||||
//TODO(danrubel) remove sdkSummaryArgs once all SDKs have summary file
|
||||
args.addAll(sdkSummaryArgs);
|
||||
var argResults = ddcArgParser().parse(args);
|
||||
var options = AnalyzerOptions.fromArguments(argResults)
|
||||
..analysisRoot = optionsDir;
|
||||
var driver = CompilerAnalysisDriver(options);
|
||||
var processors = driver.analysisOptions.errorProcessors;
|
||||
expect(processors, hasLength(1));
|
||||
expect(processors[0].code, CompileTimeErrorCode.DUPLICATE_DEFINITION.name);
|
||||
});
|
||||
|
||||
test('custom module name for summary', () {
|
||||
var args = <String>[
|
||||
'-snormal',
|
||||
'-scustom/path=module',
|
||||
'-sanother',
|
||||
'--summary=custom/path2=module2'
|
||||
];
|
||||
|
||||
var argResults = ddcArgParser().parse(args);
|
||||
var options = CompilerOptions.fromArguments(argResults);
|
||||
expect(options.summaryModules.keys.toList(),
|
||||
orderedEquals(['normal', 'custom/path', 'another', 'custom/path2']));
|
||||
expect(options.summaryModules['custom/path'], equals('module'));
|
||||
expect(options.summaryModules['custom/path2'], equals('module2'));
|
||||
expect(options.summaryModules.containsKey('normal'), isFalse);
|
||||
});
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
# Copyright (c) 2017, 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.md file.
|
|
@ -1,92 +0,0 @@
|
|||
// Copyright (c) 2017, 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 'dart:io';
|
||||
|
||||
import 'package:dev_compiler/src/analyzer/command.dart';
|
||||
import 'package:testing/testing.dart';
|
||||
|
||||
import 'common.dart';
|
||||
import 'ddc_common.dart';
|
||||
|
||||
Future<ChainContext> createContext(
|
||||
Chain suite, Map<String, String> environment) async {
|
||||
return SourceMapContext(environment);
|
||||
}
|
||||
|
||||
class SourceMapContext extends ChainContextWithCleanupHelper {
|
||||
final Map<String, String> environment;
|
||||
SourceMapContext(this.environment);
|
||||
|
||||
List<Step> _steps;
|
||||
|
||||
@override
|
||||
List<Step> get steps => _steps ??= <Step>[
|
||||
const Setup(),
|
||||
Compile(DevCompilerRunner(debugging: environment.containsKey("debug"))),
|
||||
const StepWithD8(),
|
||||
CheckSteps(environment.containsKey("debug")),
|
||||
];
|
||||
|
||||
@override
|
||||
bool debugging() => environment.containsKey("debug");
|
||||
}
|
||||
|
||||
class DevCompilerRunner implements CompilerRunner {
|
||||
final bool debugging;
|
||||
final bool absoluteRoot;
|
||||
|
||||
const DevCompilerRunner({this.debugging = false, this.absoluteRoot = true});
|
||||
|
||||
@override
|
||||
Future<Null> run(Uri inputFile, Uri outputFile, Uri outWrapperFile) async {
|
||||
Uri outDir = outputFile.resolve(".");
|
||||
String outputFilename = outputFile.pathSegments.last;
|
||||
|
||||
File sdkJsFile = findInOutDir("gen/utils/dartdevc/js/es6/dart_sdk.js");
|
||||
var jsSdkPath = sdkJsFile.uri;
|
||||
|
||||
File ddcSdkSummary = findInOutDir("gen/utils/dartdevc/ddc_sdk.sum");
|
||||
|
||||
var ddc = getDdcDir().uri.resolve("bin/dartdevc.dart");
|
||||
|
||||
List<String> args = <String>[
|
||||
"--modules=es6",
|
||||
"--dart-sdk-summary=${ddcSdkSummary.path}",
|
||||
"--library-root",
|
||||
absoluteRoot ? "/" : outDir.toFilePath(),
|
||||
"-o",
|
||||
outputFile.toFilePath(),
|
||||
inputFile.toFilePath()
|
||||
];
|
||||
|
||||
var result = compile(args);
|
||||
var exitCode = result?.exitCode;
|
||||
if (exitCode != 0) {
|
||||
throw "Exit code: $exitCode from ddc when running something like "
|
||||
"$dartExecutable ${ddc.toFilePath()} "
|
||||
"${args.reduce((value, element) => '$value "$element"')}";
|
||||
}
|
||||
|
||||
var jsContent = File.fromUri(outputFile).readAsStringSync();
|
||||
File.fromUri(outputFile).writeAsStringSync(jsContent.replaceFirst(
|
||||
"from 'dart_sdk.js'", "from '${uriPathForwardSlashed(jsSdkPath)}'"));
|
||||
|
||||
if (debugging) {
|
||||
createHtmlWrapper(
|
||||
sdkJsFile, outputFile, jsContent, outputFilename, outDir);
|
||||
}
|
||||
|
||||
var inputFileName =
|
||||
absoluteRoot ? inputFile.path : inputFile.pathSegments.last;
|
||||
inputFileName = inputFileName.split(":").last;
|
||||
var inputFileNameNoExt = pathToJSIdentifier(
|
||||
inputFileName.substring(0, inputFileName.lastIndexOf(".")));
|
||||
File.fromUri(outWrapperFile).writeAsStringSync(
|
||||
getWrapperContent(jsSdkPath, inputFileNameNoExt, outputFilename));
|
||||
}
|
||||
}
|
||||
|
||||
void main(List<String> arguments) =>
|
||||
runMe(arguments, createContext, configurationPath: "testing.json");
|
|
@ -1,8 +0,0 @@
|
|||
# Copyright (c) 2017, 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.md file.
|
||||
|
||||
null_interceptor_field: Crash
|
||||
throw_in_constructor_from_async: Crash
|
||||
throw_in_instance_method: Crash
|
||||
throw_in_static_method: Crash # Test works, but DDC's hover support means the expected column in the test is wrong
|
|
@ -1,25 +0,0 @@
|
|||
import 'package:testing/testing.dart';
|
||||
|
||||
import 'common.dart';
|
||||
import 'ddc_common.dart';
|
||||
import 'sourcemaps_ddc_suite.dart' as ddc;
|
||||
|
||||
Future<ChainContext> createContext(
|
||||
Chain suite, Map<String, String> environment) async {
|
||||
return StackTraceContext();
|
||||
}
|
||||
|
||||
class StackTraceContext extends ChainContextWithCleanupHelper {
|
||||
@override
|
||||
final List<Step> steps = <Step>[
|
||||
const Setup(),
|
||||
const SetCwdToSdkRoot(),
|
||||
const TestStackTrace(
|
||||
ddc.DevCompilerRunner(debugging: false, absoluteRoot: false),
|
||||
"ddc",
|
||||
["ddc", "ddk"]),
|
||||
];
|
||||
}
|
||||
|
||||
void main(List<String> arguments) =>
|
||||
runMe(arguments, createContext, configurationPath: "testing.json");
|
|
@ -1,164 +0,0 @@
|
|||
#!/usr/bin/env dart
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'package:dev_compiler/src/compiler/shared_command.dart';
|
||||
|
||||
final String scriptDirectory = p.dirname(p.fromUri(Platform.script));
|
||||
|
||||
final String repoDirectory = p.normalize(p.join(scriptDirectory, "../../../"));
|
||||
|
||||
/// Path to the SDK analyzer summary file, "ddc_sdk.sum".
|
||||
String analyzerSummary;
|
||||
|
||||
/// Path to the SDK kernel summary file, "ddc_sdk.dill".
|
||||
String kernelSummary;
|
||||
|
||||
/// The directory that output is written to.
|
||||
///
|
||||
/// The DDC kernel SDK should be directly in this directory. The resulting
|
||||
/// packages will be placed in a "pkg" subdirectory of this.
|
||||
String outputDirectory;
|
||||
|
||||
/// List of language experiments to enable when building.
|
||||
List<String> experiments;
|
||||
|
||||
/// Whether to force the analyzer backend to generate code even if there are
|
||||
/// errors.
|
||||
bool unsafeForceCompile;
|
||||
|
||||
/// Compiles the packages that the DDC tests use to JS into the given output
|
||||
/// directory.
|
||||
///
|
||||
/// If "--travis" is passed, builds the all of the modules tested on Travis.
|
||||
/// Otherwise, only builds the modules needed by the tests.
|
||||
///
|
||||
/// If "--analyzer-sdk" is provided, uses that summary file and compiles the
|
||||
/// packages to JS and analyzer summaries against that SDK summary. Otherwise,
|
||||
/// it skips generating analyzer summaries and compiling to JS.
|
||||
///
|
||||
/// If "--kernel-sdk" is provided, uses that summary file and generates kernel
|
||||
/// summaries for the test packages against that SDK summary. Otherwise, skips
|
||||
/// generating kernel summaries.
|
||||
Future main(List<String> arguments) async {
|
||||
var argParser = ArgParser();
|
||||
argParser.addOption("analyzer-sdk",
|
||||
help: "Path to SDK analyzer summary '.sum' file");
|
||||
argParser.addOption("kernel-sdk",
|
||||
help: "Path to SDK Kernel summary '.dill' file");
|
||||
argParser.addOption("output",
|
||||
abbr: "o", help: "Directory to write output to.");
|
||||
argParser.addFlag("travis",
|
||||
help: "Build the additional packages tested on Travis.");
|
||||
argParser.addMultiOption("enable-experiment",
|
||||
help: "Enable experimental language features.");
|
||||
argParser.addFlag("unsafe-force-compile",
|
||||
help: "Generate output even if compile errors are reported.");
|
||||
|
||||
ArgResults argResults;
|
||||
try {
|
||||
argResults = argParser.parse(arguments);
|
||||
} on ArgParserException catch (ex) {
|
||||
_usageError(argParser, ex.message);
|
||||
}
|
||||
|
||||
if (argResults.rest.isNotEmpty) {
|
||||
_usageError(
|
||||
argParser, 'Unexpected arguments "${argResults.rest.join(' ')}".');
|
||||
}
|
||||
|
||||
var isTravis = argResults["travis"] as bool;
|
||||
analyzerSummary = argResults["analyzer-sdk"] as String;
|
||||
kernelSummary = argResults["kernel-sdk"] as String;
|
||||
outputDirectory = argResults["output"] as String;
|
||||
experiments = argResults["enable-experiment"] as List<String>;
|
||||
unsafeForceCompile = argResults["unsafe-force-compile"] as bool;
|
||||
|
||||
// Build leaf packages. These have no other package dependencies.
|
||||
|
||||
// Under pkg.
|
||||
await compileModule('async_helper');
|
||||
await compileModule('expect', libs: ['minitest']);
|
||||
await compileModule('js', libs: ['js_util']);
|
||||
await compileModule('meta');
|
||||
|
||||
// Under third_party/pkg.
|
||||
await compileModule('collection');
|
||||
await compileModule('path');
|
||||
if (isTravis) {
|
||||
await compileModule('args', libs: ['command_runner']);
|
||||
await compileModule('charcode');
|
||||
await compileModule('fixnum');
|
||||
await compileModule('logging');
|
||||
await compileModule('markdown');
|
||||
await compileModule('mime');
|
||||
await compileModule('plugin', libs: ['manager']);
|
||||
await compileModule('typed_data');
|
||||
await compileModule('usage');
|
||||
await compileModule('utf');
|
||||
}
|
||||
|
||||
// Composite packages with dependencies.
|
||||
await compileModule('stack_trace', deps: ['path']);
|
||||
await compileModule('matcher', deps: ['stack_trace']);
|
||||
if (isTravis) {
|
||||
await compileModule('async', deps: ['collection']);
|
||||
}
|
||||
|
||||
if (!isTravis) {
|
||||
await compileModule('unittest', deps: [
|
||||
'path',
|
||||
'stack_trace'
|
||||
], libs: [
|
||||
'html_config',
|
||||
'html_individual_config',
|
||||
'html_enhanced_config'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
void _usageError(ArgParser parser, [String message]) {
|
||||
if (message != null) {
|
||||
stderr.writeln(message);
|
||||
stderr.writeln();
|
||||
}
|
||||
|
||||
stderr.writeln("Usage: dart build_pkgs.dart ...");
|
||||
stderr.writeln();
|
||||
stderr.writeln(parser.usage);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/// Compiles a [module] with a single matching ".dart" library and additional
|
||||
/// [libs] and [deps] on other modules.
|
||||
Future compileModule(String module,
|
||||
{List<String> libs = const [], List<String> deps = const []}) async {
|
||||
makeArgs({bool kernel = false}) {
|
||||
var pkgDirectory = p.join(outputDirectory, kernel ? 'pkg_kernel' : 'pkg');
|
||||
Directory(pkgDirectory).createSync(recursive: true);
|
||||
|
||||
return [
|
||||
if (kernel) '-k',
|
||||
if (experiments.isNotEmpty)
|
||||
'--enable-experiment=${experiments.join(",")}',
|
||||
if (unsafeForceCompile && !kernel) '--unsafe-force-compile',
|
||||
'--dart-sdk-summary=${kernel ? kernelSummary : analyzerSummary}',
|
||||
'-o${pkgDirectory}/$module.js',
|
||||
'package:$module/$module.dart',
|
||||
for (var lib in libs) 'package:$module/$lib.dart',
|
||||
for (var dep in deps) '-s${pkgDirectory}/$dep.${kernel ? "dill" : "sum"}',
|
||||
];
|
||||
}
|
||||
|
||||
if (analyzerSummary != null) {
|
||||
var result = await compile(ParsedArguments.from(makeArgs()));
|
||||
if (!result.success) exit(result.exitCode);
|
||||
}
|
||||
if (kernelSummary != null) {
|
||||
var result = await compile(ParsedArguments.from(makeArgs(kernel: true)));
|
||||
if (!result.success) exit(result.exitCode);
|
||||
}
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
#!/usr/bin/env dart
|
||||
// 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.
|
||||
|
||||
/// A straightforward script that builds the SDK.
|
||||
///
|
||||
/// This would be easy enough to do in a shell script, as we go through the
|
||||
/// command line interface. But being able to build from a Dart library means
|
||||
/// we can call this during code coverage to get more realistic numbers.
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dev_compiler/src/analyzer/command.dart';
|
||||
|
||||
void main(List<String> arguments) async {
|
||||
var args = ['--no-source-map', '--no-emit-metadata'];
|
||||
args.addAll(arguments);
|
||||
args.addAll([
|
||||
'dart:_runtime',
|
||||
'dart:_debugger',
|
||||
'dart:_foreign_helper',
|
||||
'dart:_interceptors',
|
||||
'dart:_internal',
|
||||
'dart:_isolate_helper',
|
||||
'dart:_js_helper',
|
||||
'dart:_js_mirrors',
|
||||
'dart:_js_primitives',
|
||||
'dart:_metadata',
|
||||
'dart:_native_typed_data',
|
||||
'dart:async',
|
||||
'dart:collection',
|
||||
'dart:convert',
|
||||
'dart:core',
|
||||
'dart:developer',
|
||||
'dart:io',
|
||||
'dart:isolate',
|
||||
'dart:js',
|
||||
'dart:js_util',
|
||||
'dart:math',
|
||||
'dart:mirrors',
|
||||
'dart:typed_data',
|
||||
'dart:indexed_db',
|
||||
'dart:html',
|
||||
'dart:html_common',
|
||||
'dart:svg',
|
||||
'dart:web_audio',
|
||||
'dart:web_gl',
|
||||
'dart:web_sql'
|
||||
]);
|
||||
|
||||
exit((await compile(args)).exitCode);
|
||||
}
|
|
@ -1,510 +0,0 @@
|
|||
#!/usr/bin/env dart
|
||||
// Copyright (c) 2015, 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.
|
||||
|
||||
// TODO(sigmund): delete. This file is now replaced by `patch_sdk.dart`. We
|
||||
// are keeping this copy around because there is one remaining use of it in the
|
||||
// build system of our internal apps. That use is deprecated and will be
|
||||
// deleted, at that point this file will be deleted too.
|
||||
|
||||
/// Command line tool to merge the SDK libraries and our patch files.
|
||||
/// This is currently designed as an offline tool, but we could automate it.
|
||||
|
||||
import 'dart:io';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:analyzer/dart/analysis/features.dart';
|
||||
import 'package:analyzer/dart/analysis/results.dart';
|
||||
import 'package:analyzer/dart/analysis/utilities.dart';
|
||||
import 'package:analyzer/dart/ast/ast.dart';
|
||||
import 'package:analyzer/dart/ast/token.dart';
|
||||
import 'package:analyzer/dart/ast/visitor.dart';
|
||||
import 'package:analyzer/src/generated/sdk.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
void main(List<String> argv) {
|
||||
var self = p.relative(p.fromUri(Platform.script));
|
||||
if (argv.length < 3) {
|
||||
var toolDir = p.relative(p.dirname(p.fromUri(Platform.script)));
|
||||
var dartDir =
|
||||
p.dirname(p.dirname(p.dirname(p.dirname(p.fromUri(Platform.script)))));
|
||||
|
||||
var repoExample = p.join(toolDir, '..', '..', '..');
|
||||
var patchExample =
|
||||
p.join(dartDir, 'sdk', 'lib', '_internal', 'js_dev_runtime');
|
||||
var outExample = p.relative(p.normalize(p.join('gen', 'patched_sdk')));
|
||||
|
||||
print('Usage: $self DART_REPO_DIR PATCH_DIR OUTPUT_DIR');
|
||||
print('For example:');
|
||||
print('\$ $self $repoExample $patchExample $outExample');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
var sdk = 'sdk';
|
||||
var useNnbd = false;
|
||||
if (argv.length > 3) {
|
||||
sdk = argv[3];
|
||||
|
||||
// TODO(38701): While the core libraries have been forked for NNBD, use the
|
||||
// SDK directory name to determine whether to enable the NNBD experiment
|
||||
// when parsing the lib sources. Once the libraries have been unforked, we
|
||||
// should unconditionally enable the experiment flag since then the
|
||||
// canonical SDK libs will use NNBD syntax.
|
||||
useNnbd = sdk.contains("nnbd");
|
||||
}
|
||||
|
||||
var selfModifyTime = File(self).lastModifiedSync().millisecondsSinceEpoch;
|
||||
|
||||
var repoDir = argv[0];
|
||||
var patchDir = argv[1];
|
||||
var sdkLibIn = p.join(repoDir, sdk, 'lib');
|
||||
var patchIn = p.join(patchDir, 'patch');
|
||||
var privateIn = p.join(patchDir, 'private');
|
||||
var sdkOut = p.join(argv[2], 'lib');
|
||||
|
||||
var INTERNAL_PATH = '_internal/js_runtime/lib/';
|
||||
|
||||
// Copy libraries.dart and version
|
||||
var librariesDart = p.join(patchDir, 'libraries.dart');
|
||||
var libContents = File(librariesDart).readAsStringSync();
|
||||
// TODO(jmesserly): can we remove this?
|
||||
_writeSync(p.join(sdkOut, '_internal', 'libraries.dart'), libContents);
|
||||
_writeSync(
|
||||
p.join(
|
||||
sdkOut, '_internal', 'sdk_library_metadata', 'lib', 'libraries.dart'),
|
||||
libContents);
|
||||
_writeSync(p.join(sdkOut, '..', 'version'),
|
||||
File(p.join(repoDir, 'tools', 'VERSION')).readAsStringSync());
|
||||
|
||||
// Parse libraries.dart
|
||||
var sdkLibraries = _getSdkLibraries(libContents, useNnbd: useNnbd);
|
||||
|
||||
// Enumerate core libraries and apply patches
|
||||
for (SdkLibrary library in sdkLibraries) {
|
||||
// TODO(jmesserly): analyzer does not handle the default case of
|
||||
// "both platforms" correctly, and treats it as being supported on neither.
|
||||
// So instead we skip explicitly marked as VM libs.
|
||||
if (library.isVmLibrary) continue;
|
||||
|
||||
var libraryOut = p.join(sdkLibIn, library.path);
|
||||
var libraryOverride = p.join(patchDir, 'lib', library.path);
|
||||
String libraryIn;
|
||||
if (library.path.contains(INTERNAL_PATH)) {
|
||||
libraryIn = p.join(privateIn, library.path.replaceAll(INTERNAL_PATH, ''));
|
||||
} else if (File(libraryOverride).existsSync()) {
|
||||
libraryIn = libraryOverride;
|
||||
} else {
|
||||
libraryIn = libraryOut;
|
||||
}
|
||||
|
||||
var libraryFile = File(libraryIn);
|
||||
if (libraryFile.existsSync()) {
|
||||
var outPaths = <String>[libraryOut];
|
||||
var libraryContents = libraryFile.readAsStringSync();
|
||||
|
||||
int inputModifyTime = math.max(selfModifyTime,
|
||||
libraryFile.lastModifiedSync().millisecondsSinceEpoch);
|
||||
var partFiles = <File>[];
|
||||
for (var part
|
||||
in _parseString(libraryContents, useNnbd: useNnbd).unit.directives) {
|
||||
if (part is PartDirective) {
|
||||
var partPath = part.uri.stringValue;
|
||||
outPaths.add(p.join(p.dirname(libraryOut), partPath));
|
||||
|
||||
var partFile = File(p.join(p.dirname(libraryIn), partPath));
|
||||
partFiles.add(partFile);
|
||||
inputModifyTime = math.max(inputModifyTime,
|
||||
partFile.lastModifiedSync().millisecondsSinceEpoch);
|
||||
}
|
||||
}
|
||||
|
||||
// See if we can find a patch file.
|
||||
var patchPath = p.join(
|
||||
patchIn, p.basenameWithoutExtension(libraryIn) + '_patch.dart');
|
||||
|
||||
var patchFile = File(patchPath);
|
||||
bool patchExists = patchFile.existsSync();
|
||||
if (patchExists) {
|
||||
inputModifyTime = math.max(inputModifyTime,
|
||||
patchFile.lastModifiedSync().millisecondsSinceEpoch);
|
||||
}
|
||||
|
||||
// Compute output paths
|
||||
outPaths = outPaths
|
||||
.map((path) => p.join(sdkOut, p.relative(path, from: sdkLibIn)))
|
||||
.toList();
|
||||
|
||||
// Compare output modify time with input modify time.
|
||||
bool needsUpdate = false;
|
||||
for (var outPath in outPaths) {
|
||||
var outFile = File(outPath);
|
||||
if (!outFile.existsSync() ||
|
||||
outFile.lastModifiedSync().millisecondsSinceEpoch <
|
||||
inputModifyTime) {
|
||||
needsUpdate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
var contents = <String>[libraryContents];
|
||||
contents.addAll(partFiles.map((f) => f.readAsStringSync()));
|
||||
if (patchExists) {
|
||||
var patchContents = patchFile.readAsStringSync();
|
||||
contents = _patchLibrary(contents, patchContents, useNnbd: useNnbd);
|
||||
}
|
||||
|
||||
if (contents != null) {
|
||||
for (var i = 0; i < outPaths.length; i++) {
|
||||
_writeSync(outPaths[i], contents[i]);
|
||||
}
|
||||
} else {
|
||||
exitCode = 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes a file, creating the directory if needed.
|
||||
void _writeSync(String filePath, String contents) {
|
||||
var outDir = Directory(p.dirname(filePath));
|
||||
if (!outDir.existsSync()) outDir.createSync(recursive: true);
|
||||
|
||||
File(filePath).writeAsStringSync(contents);
|
||||
}
|
||||
|
||||
/// Merges dart:* library code with code from *_patch.dart file.
|
||||
///
|
||||
/// Takes a list of the library's parts contents, with the main library contents
|
||||
/// first in the list, and the contents of the patch file.
|
||||
///
|
||||
/// The result will have `@patch` implementations merged into the correct place
|
||||
/// (e.g. the class or top-level function declaration) and all other
|
||||
/// declarations introduced by the patch will be placed into the main library
|
||||
/// file.
|
||||
///
|
||||
/// This is purely a syntactic transformation. Unlike dart2js patch files, there
|
||||
/// is no semantic meaning given to the *_patch files, and they do not magically
|
||||
/// get their own library scope, etc.
|
||||
///
|
||||
/// Editorializing: the dart2js approach requires a Dart front end such as
|
||||
/// package:analyzer to semantically model a feature beyond what is specified
|
||||
/// in the Dart language. Since this feature is only for the convenience of
|
||||
/// writing the dart:* libraries, and not a tool given to Dart developers, it
|
||||
/// seems like a non-ideal situation. Instead we keep the preprocessing simple.
|
||||
List<String> _patchLibrary(List<String> partsContents, String patchContents,
|
||||
{bool useNnbd = false}) {
|
||||
var results = <StringEditBuffer>[];
|
||||
|
||||
// Parse the patch first. We'll need to extract bits of this as we go through
|
||||
// the other files.
|
||||
var patchFinder = PatchFinder.parseAndVisit(patchContents, useNnbd: useNnbd);
|
||||
|
||||
// Merge `external` declarations with the corresponding `@patch` code.
|
||||
bool failed = false;
|
||||
for (var partContent in partsContents) {
|
||||
var partEdits = StringEditBuffer(partContent);
|
||||
var partUnit = _parseString(partContent, useNnbd: useNnbd).unit;
|
||||
var patcher = PatchApplier(partEdits, patchFinder);
|
||||
partUnit.accept(patcher);
|
||||
if (!failed) failed = patcher.patchWasMissing;
|
||||
results.add(partEdits);
|
||||
}
|
||||
if (failed) return null;
|
||||
return List<String>.from(results.map((e) => e.toString()));
|
||||
}
|
||||
|
||||
/// Merge `@patch` declarations into `external` declarations.
|
||||
class PatchApplier extends GeneralizingAstVisitor {
|
||||
final StringEditBuffer edits;
|
||||
final PatchFinder patch;
|
||||
|
||||
bool _isLibrary = true; // until proven otherwise.
|
||||
bool patchWasMissing = false;
|
||||
|
||||
PatchApplier(this.edits, this.patch);
|
||||
|
||||
@override
|
||||
visitCompilationUnit(CompilationUnit node) {
|
||||
super.visitCompilationUnit(node);
|
||||
if (_isLibrary) _mergeUnpatched(node);
|
||||
}
|
||||
|
||||
void _merge(AstNode node, int pos) {
|
||||
var code = patch.contents.substring(node.offset, node.end);
|
||||
edits.insert(pos, '\n' + code);
|
||||
}
|
||||
|
||||
/// Merges directives and declarations that are not `@patch` into the library.
|
||||
void _mergeUnpatched(CompilationUnit unit) {
|
||||
// Merge imports from the patch
|
||||
// TODO(jmesserly): remove duplicate imports
|
||||
|
||||
// To patch a library, we must have a library directive
|
||||
var libDir = unit.directives.first as LibraryDirective;
|
||||
int importPos = unit.directives
|
||||
.lastWhere((d) => d is ImportDirective, orElse: () => libDir)
|
||||
.end;
|
||||
for (var d in patch.unit.directives.whereType<ImportDirective>()) {
|
||||
_merge(d, importPos);
|
||||
}
|
||||
|
||||
int partPos = unit.directives.last.end;
|
||||
for (var d in patch.unit.directives.whereType<PartDirective>()) {
|
||||
_merge(d, partPos);
|
||||
}
|
||||
|
||||
// Merge declarations from the patch
|
||||
int declPos = edits.original.length;
|
||||
for (var d in patch.mergeDeclarations) {
|
||||
_merge(d, declPos);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
visitPartOfDirective(PartOfDirective node) {
|
||||
_isLibrary = false;
|
||||
}
|
||||
|
||||
@override
|
||||
visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
_maybePatch(node);
|
||||
}
|
||||
|
||||
/// Merge patches and extensions into the class
|
||||
@override
|
||||
visitClassDeclaration(ClassDeclaration node) {
|
||||
node.members.forEach(_maybePatch);
|
||||
|
||||
var mergeMembers = patch.mergeMembers[_qualifiedName(node)];
|
||||
if (mergeMembers == null) return;
|
||||
|
||||
// Merge members from the patch
|
||||
var pos = node.members.last.end;
|
||||
for (var member in mergeMembers) {
|
||||
var code = patch.contents.substring(member.offset, member.end);
|
||||
edits.insert(pos, '\n\n ' + code);
|
||||
}
|
||||
}
|
||||
|
||||
void _maybePatch(Declaration node) {
|
||||
if (node is FieldDeclaration) return;
|
||||
|
||||
var externalKeyword = (node as dynamic).externalKeyword as Token;
|
||||
if (externalKeyword == null) return;
|
||||
|
||||
var name = _qualifiedName(node);
|
||||
var patchNode = patch.patches[name];
|
||||
if (patchNode == null) {
|
||||
print('warning: patch not found for $name: $node');
|
||||
patchWasMissing = true;
|
||||
return;
|
||||
}
|
||||
|
||||
Annotation patchMeta = patchNode.metadata.lastWhere(_isPatchAnnotation);
|
||||
int start = patchMeta.endToken.next.offset;
|
||||
var code = patch.contents.substring(start, patchNode.end);
|
||||
|
||||
// Const factory constructors can't be legally parsed from the patch file,
|
||||
// so we need to omit the "const" there, but still preserve it.
|
||||
if (node is ConstructorDeclaration &&
|
||||
node.constKeyword != null &&
|
||||
patchNode is ConstructorDeclaration &&
|
||||
patchNode.constKeyword == null) {
|
||||
code = 'const $code';
|
||||
}
|
||||
|
||||
// For some node like static fields, the node's offset doesn't include
|
||||
// the external keyword. Also starting from the keyword lets us preserve
|
||||
// documentation comments.
|
||||
edits.replace(externalKeyword.offset, node.end, code);
|
||||
}
|
||||
}
|
||||
|
||||
class PatchFinder extends GeneralizingAstVisitor {
|
||||
final String contents;
|
||||
final CompilationUnit unit;
|
||||
|
||||
final patches = <String, Declaration>{};
|
||||
final mergeMembers = <String, List<ClassMember>>{};
|
||||
final mergeDeclarations = <CompilationUnitMember>[];
|
||||
|
||||
PatchFinder.parseAndVisit(String contents, {bool useNnbd})
|
||||
: contents = contents,
|
||||
unit = _parseString(contents, useNnbd: useNnbd).unit {
|
||||
visitCompilationUnit(unit);
|
||||
}
|
||||
|
||||
@override
|
||||
visitCompilationUnitMember(CompilationUnitMember node) {
|
||||
mergeDeclarations.add(node);
|
||||
}
|
||||
|
||||
@override
|
||||
visitClassDeclaration(ClassDeclaration node) {
|
||||
if (_isPatch(node)) {
|
||||
var members = <ClassMember>[];
|
||||
for (var member in node.members) {
|
||||
if (_isPatch(member)) {
|
||||
patches[_qualifiedName(member)] = member;
|
||||
} else {
|
||||
members.add(member);
|
||||
}
|
||||
}
|
||||
if (members.isNotEmpty) {
|
||||
mergeMembers[_qualifiedName(node)] = members;
|
||||
}
|
||||
} else {
|
||||
mergeDeclarations.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
visitFunctionDeclaration(FunctionDeclaration node) {
|
||||
if (_isPatch(node)) {
|
||||
patches[_qualifiedName(node)] = node;
|
||||
} else {
|
||||
mergeDeclarations.add(node);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
visitFunctionBody(node) {} // skip method bodies
|
||||
}
|
||||
|
||||
String _qualifiedName(Declaration node) {
|
||||
var result = "";
|
||||
|
||||
var parent = node.parent;
|
||||
if (parent is ClassDeclaration) {
|
||||
result = "${parent.name.name}.";
|
||||
}
|
||||
|
||||
var name = (node as dynamic).name as SimpleIdentifier;
|
||||
if (name != null) result += name.name;
|
||||
|
||||
// Make sure setters and getters don't collide.
|
||||
if (node is FunctionDeclaration && node.isSetter ||
|
||||
node is MethodDeclaration && node.isSetter) {
|
||||
result += "=";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool _isPatch(AnnotatedNode node) => node.metadata.any(_isPatchAnnotation);
|
||||
|
||||
bool _isPatchAnnotation(Annotation m) =>
|
||||
m.name.name == 'patch' && m.constructorName == null && m.arguments == null;
|
||||
|
||||
/// Editable string buffer.
|
||||
///
|
||||
/// Applies a series of edits (insertions, removals, replacements) using
|
||||
/// original location information, and composes them into the edited string.
|
||||
///
|
||||
/// For example, starting with a parsed AST with original source locations,
|
||||
/// this type allows edits to be made without regards to other edits.
|
||||
class StringEditBuffer {
|
||||
final String original;
|
||||
final _edits = <_StringEdit>[];
|
||||
|
||||
/// Creates a new transaction.
|
||||
StringEditBuffer(this.original);
|
||||
|
||||
bool get hasEdits => _edits.isNotEmpty;
|
||||
|
||||
/// Edit the original text, replacing text on the range [begin] and
|
||||
/// exclusive [end] with the [replacement] string.
|
||||
void replace(int begin, int end, String replacement) {
|
||||
_edits.add(_StringEdit(begin, end, replacement));
|
||||
}
|
||||
|
||||
/// Insert [string] at [offset].
|
||||
/// Equivalent to `replace(offset, offset, string)`.
|
||||
void insert(int offset, String string) => replace(offset, offset, string);
|
||||
|
||||
/// Remove text from the range [begin] to exclusive [end].
|
||||
/// Equivalent to `replace(begin, end, '')`.
|
||||
void remove(int begin, int end) => replace(begin, end, '');
|
||||
|
||||
/// Applies all pending [edit]s and returns a new string.
|
||||
///
|
||||
/// This method is non-destructive: it does not discard existing edits or
|
||||
/// change the [original] string. Further edits can be added and this method
|
||||
/// can be called again.
|
||||
///
|
||||
/// Throws [UnsupportedError] if the edits were overlapping. If no edits were
|
||||
/// made, the original string will be returned.
|
||||
@override
|
||||
String toString() {
|
||||
var sb = StringBuffer();
|
||||
if (_edits.isEmpty) return original;
|
||||
|
||||
// Sort edits by start location.
|
||||
_edits.sort();
|
||||
|
||||
int consumed = 0;
|
||||
for (var edit in _edits) {
|
||||
if (consumed > edit.begin) {
|
||||
sb = StringBuffer();
|
||||
sb.write('overlapping edits. Insert at offset ');
|
||||
sb.write(edit.begin);
|
||||
sb.write(' but have consumed ');
|
||||
sb.write(consumed);
|
||||
sb.write(' input characters. List of edits:');
|
||||
for (var e in _edits) {
|
||||
sb.write('\n ');
|
||||
sb.write(e);
|
||||
}
|
||||
throw UnsupportedError(sb.toString());
|
||||
}
|
||||
|
||||
// Add characters from the original string between this edit and the last
|
||||
// one, if any.
|
||||
var betweenEdits = original.substring(consumed, edit.begin);
|
||||
sb.write(betweenEdits);
|
||||
sb.write(edit.replace);
|
||||
consumed = edit.end;
|
||||
}
|
||||
|
||||
// Add any text from the end of the original string that was not replaced.
|
||||
sb.write(original.substring(consumed));
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
class _StringEdit implements Comparable<_StringEdit> {
|
||||
final int begin;
|
||||
final int end;
|
||||
final String replace;
|
||||
|
||||
_StringEdit(this.begin, this.end, this.replace);
|
||||
|
||||
int get length => end - begin;
|
||||
|
||||
@override
|
||||
String toString() => '(Edit @ $begin,$end: "$replace")';
|
||||
|
||||
@override
|
||||
int compareTo(_StringEdit other) {
|
||||
int diff = begin - other.begin;
|
||||
if (diff != 0) return diff;
|
||||
return end - other.end;
|
||||
}
|
||||
}
|
||||
|
||||
List<SdkLibrary> _getSdkLibraries(String contents, {bool useNnbd}) {
|
||||
// TODO(jmesserly): fix SdkLibrariesReader_LibraryBuilder in Analyzer.
|
||||
// It doesn't understand optional new/const in Dart 2. For now, we keep
|
||||
// redundant `const` in tool/input_sdk/libraries.dart as a workaround.
|
||||
var libraryBuilder = SdkLibrariesReader_LibraryBuilder();
|
||||
_parseString(contents, useNnbd: useNnbd).unit.accept(libraryBuilder);
|
||||
return libraryBuilder.librariesMap.sdkLibraries;
|
||||
}
|
||||
|
||||
ParseStringResult _parseString(String source, {bool useNnbd}) {
|
||||
var features = FeatureSet.fromEnableFlags([if (useNnbd) "non-nullable"]);
|
||||
return parseString(content: source, featureSet: features);
|
||||
}
|
Loading…
Reference in a new issue