Merge branch 'es6Import' into es6ImportDts

This commit is contained in:
Sheetal Nandi
2015-02-10 19:09:00 -08:00
966 changed files with 13803 additions and 15140 deletions
+285 -171
View File
@@ -5886,10 +5886,74 @@ module ts {
return unknownSignature;
}
// Re-order candidate signatures into the result array. Assumes the result array to be empty.
// The candidate list orders groups in reverse, but within a group signatures are kept in declaration order
// A nit here is that we reorder only signatures that belong to the same symbol,
// so order how inherited signatures are processed is still preserved.
// interface A { (x: string): void }
// interface B extends A { (x: 'foo'): string }
// var b: B;
// b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void]
function reorderCandidates(signatures: Signature[], result: Signature[]): void {
var lastParent: Node;
var lastSymbol: Symbol;
var cutoffIndex: number = 0;
var index: number;
var specializedIndex: number = -1;
var spliceIndex: number;
Debug.assert(!result.length);
for (var i = 0; i < signatures.length; i++) {
var signature = signatures[i];
var symbol = signature.declaration && getSymbolOfNode(signature.declaration);
var parent = signature.declaration && signature.declaration.parent;
if (!lastSymbol || symbol === lastSymbol) {
if (lastParent && parent === lastParent) {
index++;
}
else {
lastParent = parent;
index = cutoffIndex;
}
}
else {
// current declaration belongs to a different symbol
// set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex
index = cutoffIndex = result.length;
lastParent = parent;
}
lastSymbol = symbol;
// specialized signatures always need to be placed before non-specialized signatures regardless
// of the cutoff position; see GH#1133
if (signature.hasStringLiterals) {
specializedIndex++;
spliceIndex = specializedIndex;
// The cutoff index always needs to be greater than or equal to the specialized signature index
// in order to prevent non-specialized signatures from being added before a specialized
// signature.
cutoffIndex++;
}
else {
spliceIndex = index;
}
result.splice(spliceIndex, 0, signature);
}
}
function getSpreadArgumentIndex(args: Expression[]): number {
for (var i = 0; i < args.length; i++) {
if (args[i].kind === SyntaxKind.SpreadElementExpression) {
return i;
}
}
return -1;
}
function hasCorrectArity(node: CallLikeExpression, args: Expression[], signature: Signature) {
var adjustedArgCount: number;
var typeArguments: NodeArray<TypeNode>;
var callIsIncomplete: boolean;
var adjustedArgCount: number; // Apparent number of arguments we will have in this call
var typeArguments: NodeArray<TypeNode>; // Type arguments (undefined if none)
var callIsIncomplete: boolean; // In incomplete call we want to be lenient when we have too few arguments
if (node.kind === SyntaxKind.TaggedTemplateExpression) {
var tagExpression = <TaggedTemplateExpression>node;
@@ -5934,35 +5998,29 @@ module ts {
typeArguments = callExpression.typeArguments;
}
Debug.assert(adjustedArgCount !== undefined, "'adjustedArgCount' undefined");
Debug.assert(callIsIncomplete !== undefined, "'callIsIncomplete' undefined");
return checkArity(adjustedArgCount, typeArguments, callIsIncomplete, signature);
/**
* @param adjustedArgCount The "apparent" number of arguments that we will have in this call.
* @param typeArguments Type arguments node of the call if it exists; undefined otherwise.
* @param callIsIncomplete Whether or not a call is unfinished, and we should be "lenient" when we have too few arguments.
* @param signature The signature whose arity we are comparing.
*/
function checkArity(adjustedArgCount: number, typeArguments: NodeArray<TypeNode>, callIsIncomplete: boolean, signature: Signature): boolean {
// Too many arguments implies incorrect arity.
if (!signature.hasRestParameter && adjustedArgCount > signature.parameters.length) {
return false;
}
// If the user supplied type arguments, but the number of type arguments does not match
// the declared number of type parameters, the call has an incorrect arity.
var hasRightNumberOfTypeArgs = !typeArguments ||
(signature.typeParameters && typeArguments.length === signature.typeParameters.length);
if (!hasRightNumberOfTypeArgs) {
return false;
}
// If the call is incomplete, we should skip the lower bound check.
var hasEnoughArguments = adjustedArgCount >= signature.minArgumentCount;
return callIsIncomplete || hasEnoughArguments;
// If the user supplied type arguments, but the number of type arguments does not match
// the declared number of type parameters, the call has an incorrect arity.
var hasRightNumberOfTypeArgs = !typeArguments ||
(signature.typeParameters && typeArguments.length === signature.typeParameters.length);
if (!hasRightNumberOfTypeArgs) {
return false;
}
// If spread arguments are present, check that they correspond to a rest parameter. If so, no
// further checking is necessary.
var spreadArgIndex = getSpreadArgumentIndex(args);
if (spreadArgIndex >= 0) {
return signature.hasRestParameter && spreadArgIndex >= signature.parameters.length - 1;
}
// Too many arguments implies incorrect arity.
if (!signature.hasRestParameter && adjustedArgCount > signature.parameters.length) {
return false;
}
// If the call is incomplete, we should skip the lower bound check.
var hasEnoughArguments = adjustedArgCount >= signature.minArgumentCount;
return callIsIncomplete || hasEnoughArguments;
}
// If type has a single call signature and no other members, return that signature. Otherwise, return undefined.
@@ -5995,18 +6053,20 @@ module ts {
// We perform two passes over the arguments. In the first pass we infer from all arguments, but use
// wildcards for all context sensitive function expressions.
for (var i = 0; i < args.length; i++) {
if (args[i].kind === SyntaxKind.OmittedExpression) {
continue;
var arg = args[i];
if (arg.kind !== SyntaxKind.OmittedExpression) {
var paramType = getTypeAtPosition(signature, arg.kind === SyntaxKind.SpreadElementExpression ? -1 : i);
if (i === 0 && args[i].parent.kind === SyntaxKind.TaggedTemplateExpression) {
var argType = globalTemplateStringsArrayType;
}
else {
// For context sensitive arguments we pass the identityMapper, which is a signal to treat all
// context sensitive function expressions as wildcards
var mapper = excludeArgument && excludeArgument[i] !== undefined ? identityMapper : inferenceMapper;
var argType = checkExpressionWithContextualType(arg, paramType, mapper);
}
inferTypes(context, argType, paramType);
}
var parameterType = getTypeAtPosition(signature, i);
if (i === 0 && args[i].parent.kind === SyntaxKind.TaggedTemplateExpression) {
inferTypes(context, globalTemplateStringsArrayType, parameterType);
continue;
}
// For context sensitive arguments we pass the identityMapper, which is a signal to treat all
// context sensitive function expressions as wildcards
var mapper = excludeArgument && excludeArgument[i] !== undefined ? identityMapper : inferenceMapper;
inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, mapper), parameterType);
}
// In the second pass we visit only context sensitive arguments, and only those that aren't excluded, this
@@ -6014,13 +6074,11 @@ module ts {
// as we construct types for contextually typed parameters)
if (excludeArgument) {
for (var i = 0; i < args.length; i++) {
if (args[i].kind === SyntaxKind.OmittedExpression) {
continue;
}
// No need to special-case tagged templates; their excludeArgument value will be 'undefined'.
// No need to check for omitted args and template expressions, their exlusion value is always undefined
if (excludeArgument[i] === false) {
var parameterType = getTypeAtPosition(signature, i);
inferTypes(context, checkExpressionWithContextualType(args[i], parameterType, inferenceMapper), parameterType);
var arg = args[i];
var paramType = getTypeAtPosition(signature, arg.kind === SyntaxKind.SpreadElementExpression ? -1 : i);
inferTypes(context, checkExpressionWithContextualType(arg, paramType, inferenceMapper), paramType);
}
}
}
@@ -6058,37 +6116,24 @@ module ts {
return typeArgumentsAreAssignable;
}
function checkApplicableSignature(node: CallLikeExpression, args: Node[], signature: Signature, relation: Map<RelationComparisonResult>, excludeArgument: boolean[], reportErrors: boolean) {
function checkApplicableSignature(node: CallLikeExpression, args: Expression[], signature: Signature, relation: Map<RelationComparisonResult>, excludeArgument: boolean[], reportErrors: boolean) {
for (var i = 0; i < args.length; i++) {
var arg = args[i];
var argType: Type;
if (arg.kind === SyntaxKind.OmittedExpression) {
continue;
}
var paramType = getTypeAtPosition(signature, i);
if (i === 0 && node.kind === SyntaxKind.TaggedTemplateExpression) {
// A tagged template expression has something of a
// "virtual" parameter with the "cooked" strings array type.
argType = globalTemplateStringsArrayType;
}
else {
// String literals get string literal types unless we're reporting errors
argType = arg.kind === SyntaxKind.StringLiteral && !reportErrors
? getStringLiteralType(<LiteralExpression>arg)
: checkExpressionWithContextualType(<LiteralExpression>arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined);
}
// Use argument expression as error location when reporting errors
var isValidArgument = checkTypeRelatedTo(argType, paramType, relation, reportErrors ? arg : undefined,
Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1);
if (!isValidArgument) {
return false;
if (arg.kind !== SyntaxKind.OmittedExpression) {
// Check spread elements against rest type (from arity check we know spread argument corresponds to a rest parameter)
var paramType = getTypeAtPosition(signature, arg.kind === SyntaxKind.SpreadElementExpression ? -1 : i);
// A tagged template expression provides a special first argument, and string literals get string literal types
// unless we're reporting errors
var argType = i === 0 && node.kind === SyntaxKind.TaggedTemplateExpression ? globalTemplateStringsArrayType :
arg.kind === SyntaxKind.StringLiteral && !reportErrors ? getStringLiteralType(<LiteralExpression>arg) :
checkExpressionWithContextualType(arg, paramType, excludeArgument && excludeArgument[i] ? identityMapper : undefined);
// Use argument expression as error location when reporting errors
if (!checkTypeRelatedTo(argType, paramType, relation, reportErrors ? arg : undefined,
Diagnostics.Argument_of_type_0_is_not_assignable_to_parameter_of_type_1)) {
return false;
}
}
}
return true;
}
@@ -6155,8 +6200,8 @@ module ts {
}
var candidates = candidatesOutArray || [];
// collectCandidates fills up the candidates array directly
collectCandidates();
// reorderCandidates fills up the candidates array directly
reorderCandidates(signatures, candidates);
if (!candidates.length) {
error(node, Diagnostics.Supplied_parameters_do_not_match_any_signature_of_call_target);
return resolveErrorCall(node);
@@ -6346,60 +6391,6 @@ module ts {
return undefined;
}
// The candidate list orders groups in reverse, but within a group signatures are kept in declaration order
// A nit here is that we reorder only signatures that belong to the same symbol,
// so order how inherited signatures are processed is still preserved.
// interface A { (x: string): void }
// interface B extends A { (x: 'foo'): string }
// var b: B;
// b('foo') // <- here overloads should be processed as [(x:'foo'): string, (x: string): void]
function collectCandidates(): void {
var result = candidates;
var lastParent: Node;
var lastSymbol: Symbol;
var cutoffIndex: number = 0;
var index: number;
var specializedIndex: number = -1;
var spliceIndex: number;
Debug.assert(!result.length);
for (var i = 0; i < signatures.length; i++) {
var signature = signatures[i];
var symbol = signature.declaration && getSymbolOfNode(signature.declaration);
var parent = signature.declaration && signature.declaration.parent;
if (!lastSymbol || symbol === lastSymbol) {
if (lastParent && parent === lastParent) {
index++;
}
else {
lastParent = parent;
index = cutoffIndex;
}
}
else {
// current declaration belongs to a different symbol
// set cutoffIndex so re-orderings in the future won't change result set from 0 to cutoffIndex
index = cutoffIndex = result.length;
lastParent = parent;
}
lastSymbol = symbol;
// specialized signatures always need to be placed before non-specialized signatures regardless
// of the cutoff position; see GH#1133
if (signature.hasStringLiterals) {
specializedIndex++;
spliceIndex = specializedIndex;
// The cutoff index always needs to be greater than or equal to the specialized signature index
// in order to prevent non-specialized signatures from being added before a specialized
// signature.
cutoffIndex++;
}
else {
spliceIndex = index;
}
result.splice(spliceIndex, 0, signature);
}
}
}
function resolveCallExpression(node: CallExpression, candidatesOutArray: Signature[]): Signature {
@@ -6455,6 +6446,13 @@ module ts {
}
function resolveNewExpression(node: NewExpression, candidatesOutArray: Signature[]): Signature {
if (node.arguments && languageVersion < ScriptTarget.ES6) {
var spreadIndex = getSpreadArgumentIndex(node.arguments);
if (spreadIndex >= 0) {
error(node.arguments[spreadIndex], Diagnostics.Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_6_and_higher);
}
}
var expressionType = checkExpression(node.expression);
// TS 1.0 spec: 4.11
// If ConstructExpr is of type Any, Args can be any argument
@@ -6600,9 +6598,14 @@ module ts {
}
function getTypeAtPosition(signature: Signature, pos: number): Type {
if (pos >= 0) {
return signature.hasRestParameter ?
pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) :
pos < signature.parameters.length ? getTypeOfSymbol(signature.parameters[pos]) : anyType;
}
return signature.hasRestParameter ?
pos < signature.parameters.length - 1 ? getTypeOfSymbol(signature.parameters[pos]) : getRestTypeOfSignature(signature) :
pos < signature.parameters.length ? getTypeOfSymbol(signature.parameters[pos]) : anyType;
getTypeOfSymbol(signature.parameters[signature.parameters.length - 1]) :
anyArrayType;
}
function assignContextualParameterTypes(signature: Signature, context: Signature, mapper: TypeMapper) {
@@ -10138,62 +10141,149 @@ module ts {
function isUniqueLocalName(name: string, container: Node): boolean {
for (var node = container; isNodeDescendentOf(node, container); node = node.nextContainer) {
if (node.locals && hasProperty(node.locals, name)) {
var symbolWithRelevantName = node.locals[name];
if (symbolWithRelevantName.flags & (SymbolFlags.Value | SymbolFlags.ExportValue)) {
// We conservatively include import symbols to cover cases where they're emitted as locals
if (node.locals[name].flags & (SymbolFlags.Value | SymbolFlags.ExportValue | SymbolFlags.Import)) {
return false;
}
// An import can be emitted too, if it is referenced as a value.
// Make sure the name in question does not collide with an import.
if (symbolWithRelevantName.flags & SymbolFlags.Import) {
var importEqualsDeclarationWithRelevantName = <ImportEqualsDeclaration>getDeclarationOfKind(symbolWithRelevantName, SyntaxKind.ImportEqualsDeclaration);
if (isReferencedImportDeclaration(importEqualsDeclarationWithRelevantName)) {
return false;
}
}
}
}
return true;
}
function getLocalNameOfContainer(container: ModuleDeclaration | EnumDeclaration): string {
var links = getNodeLinks(container);
if (!links.localModuleName) {
var prefix = "";
var name = unescapeIdentifier(container.name.text);
while (!isUniqueLocalName(escapeIdentifier(prefix + name), container)) {
prefix += "_";
}
links.localModuleName = prefix + getTextOfNode(container.name);
function getGeneratedNamesForSourceFile(sourceFile: SourceFile): Map<string> {
var links = getNodeLinks(sourceFile);
var generatedNames = links.generatedNames;
if (!generatedNames) {
generatedNames = links.generatedNames = {};
generateNames(sourceFile);
}
return generatedNames;
function generateNames(node: Node) {
switch (node.kind) {
case SyntaxKind.ModuleDeclaration:
generateNameForModuleOrEnum(<ModuleDeclaration>node);
generateNames((<ModuleDeclaration>node).body);
break;
case SyntaxKind.EnumDeclaration:
generateNameForModuleOrEnum(<EnumDeclaration>node);
break;
case SyntaxKind.ImportDeclaration:
generateNameForImportDeclaration(<ImportDeclaration>node);
break;
case SyntaxKind.SourceFile:
case SyntaxKind.ModuleBlock:
forEach((<SourceFile | ModuleBlock>node).statements, generateNames);
break;
}
}
function isExistingName(name: string) {
return hasProperty(sourceFile.identifiers, name) || hasProperty(generatedNames, name);
}
function makeUniqueName(baseName: string): string {
// First try '_name'
if (baseName.charCodeAt(0) !== CharacterCodes._) {
var baseName = "_" + baseName;
if (!isExistingName(baseName)) {
return baseName;
}
}
// Find the first unique '_name_n', where n is a positive number
if (baseName.charCodeAt(baseName.length - 1) !== CharacterCodes._) {
baseName += "_";
}
var i = 1;
while (true) {
name = baseName + i;
if (!isExistingName(name)) {
return name;
}
i++;
}
}
function assignGeneratedName(node: Node, name: string) {
generatedNames[name] = name;
getNodeLinks(node).generatedName = unescapeIdentifier(name);
}
function generateNameForModuleOrEnum(node: ModuleDeclaration | EnumDeclaration) {
if (node.name.kind === SyntaxKind.Identifier) {
var name = node.name.text;
// Use module/enum name itself if it is unique, otherwise make a unique variation
assignGeneratedName(node, isUniqueLocalName(name, node) ? name : makeUniqueName(name));
}
}
function generateNameForImportDeclaration(node: ImportDeclaration) {
if (node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamedImports) {
var expr = getImportedModuleName(node);
var baseName = expr.kind === SyntaxKind.StringLiteral ?
escapeIdentifier(makeIdentifierFromModuleName((<LiteralExpression>expr).text)) : "module";
assignGeneratedName(node, makeUniqueName(baseName));
}
}
return links.localModuleName;
}
function getLocalNameForSymbol(symbol: Symbol, location: Node): string {
function getGeneratedNameForNode(node: ModuleDeclaration | EnumDeclaration | ImportDeclaration) {
var links = getNodeLinks(node);
if (!links.generatedName) {
getGeneratedNamesForSourceFile(getSourceFile(node));
}
return links.generatedName;
}
function getLocalNameOfContainer(container: ModuleDeclaration | EnumDeclaration): string {
return getGeneratedNameForNode(container);
}
function getLocalNameForImportDeclaration(node: ImportDeclaration): string {
return getGeneratedNameForNode(node);
}
function getImportNameSubstitution(symbol: Symbol): string {
var declaration = getDeclarationOfImportSymbol(symbol);
if (declaration && declaration.kind === SyntaxKind.ImportSpecifier) {
var moduleName = getGeneratedNameForNode(<ImportDeclaration>declaration.parent.parent.parent);
var propertyName = (<ImportSpecifier>declaration).propertyName || (<ImportSpecifier>declaration).name;
return moduleName + "." + unescapeIdentifier(propertyName.text);
}
}
function getExportNameSubstitution(symbol: Symbol, location: Node): string {
if (isExternalModuleSymbol(symbol.parent)) {
return "exports." + unescapeIdentifier(symbol.name);
}
var node = location;
var containerSymbol = getParentOfSymbol(symbol);
while (node) {
if ((node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.EnumDeclaration) && getSymbolOfNode(node) === symbol) {
return getLocalNameOfContainer(<ModuleDeclaration | EnumDeclaration>node);
if ((node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.EnumDeclaration) && getSymbolOfNode(node) === containerSymbol) {
return getGeneratedNameForNode(<ModuleDeclaration | EnumDeclaration>node) + "." + unescapeIdentifier(symbol.name);
}
node = node.parent;
}
Debug.fail("getLocalNameForSymbol failed");
}
function getExpressionNamePrefix(node: Identifier): string {
function getExpressionNameSubstitution(node: Identifier): string {
var symbol = getNodeLinks(node).resolvedSymbol;
if (symbol) {
// In general, we need to prefix an identifier with its parent name if it references
// an exported entity from another module declaration. If we reference an exported
// entity within the same module declaration, then whether we prefix depends on the
// kind of entity. SymbolFlags.ExportHasLocal encompasses all the kinds that we
// do NOT prefix.
// Whan an identifier resolves to a parented symbol, it references an exported entity from
// another declaration of the same internal module.
if (symbol.parent) {
return getExportNameSubstitution(symbol, node.parent);
}
// If we reference an exported entity within the same module declaration, then whether
// we prefix depends on the kind of entity. SymbolFlags.ExportHasLocal encompasses all the
// kinds that we do NOT prefix.
var exportSymbol = getExportSymbolOfValueSymbolIfExported(symbol);
if (symbol !== exportSymbol && !(exportSymbol.flags & SymbolFlags.ExportHasLocal)) {
symbol = exportSymbol;
return getExportNameSubstitution(exportSymbol, node.parent);
}
if (symbol.parent) {
return isExternalModuleSymbol(symbol.parent) ? "exports" : getLocalNameForSymbol(getParentOfSymbol(symbol), node.parent);
// Named imports from ES6 import declarations are rewritten
if (symbol.flags & SymbolFlags.Import) {
return getImportNameSubstitution(symbol);
}
}
}
@@ -10299,13 +10389,14 @@ module ts {
}
function isUnknownIdentifier(location: Node, name: string): boolean {
return !resolveName(location, name, SymbolFlags.Value, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined);
return !resolveName(location, name, SymbolFlags.Value, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined) &&
!hasProperty(getGeneratedNamesForSourceFile(getSourceFile(location)), name);
}
function createResolver(): EmitResolver {
return {
getLocalNameOfContainer,
getExpressionNamePrefix,
getGeneratedNameForNode,
getExpressionNameSubstitution,
getExportAssignmentName,
isReferencedImportDeclaration,
getNodeCheckFlags,
@@ -11010,9 +11101,32 @@ module ts {
}
}
}
var checkLetConstNames = languageVersion >= ScriptTarget.ES6 && (isLet(node) || isConst(node));
// 1. LexicalDeclaration : LetOrConst BindingList ;
// It is a Syntax Error if the BoundNames of BindingList contains "let".
// 2. ForDeclaration: ForDeclaration : LetOrConst ForBinding
// It is a Syntax Error if the BoundNames of ForDeclaration contains "let".
// It is a SyntaxError if a VariableDeclaration or VariableDeclarationNoIn occurs within strict code
// and its Identifier is eval or arguments
return checkGrammarEvalOrArgumentsInStrictMode(node, <Identifier>node.name);
return (checkLetConstNames && checkGrammarNameInLetOrConstDeclarations(node.name)) ||
checkGrammarEvalOrArgumentsInStrictMode(node, <Identifier>node.name);
}
function checkGrammarNameInLetOrConstDeclarations(name: Identifier | BindingPattern): boolean {
if (name.kind === SyntaxKind.Identifier) {
if ((<Identifier>name).text === "let") {
return grammarErrorOnNode(name, Diagnostics.let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations);
}
}
else {
var elements = (<BindingPattern>name).elements;
for (var i = 0; i < elements.length; ++i) {
checkGrammarNameInLetOrConstDeclarations(elements[i].name);
}
}
}
function checkGrammarVariableDeclarationList(declarationList: VariableDeclarationList): boolean {
@@ -305,6 +305,7 @@ module ts {
this_cannot_be_referenced_in_a_computed_property_name: { code: 2465, category: DiagnosticCategory.Error, key: "'this' cannot be referenced in a computed property name." },
super_cannot_be_referenced_in_a_computed_property_name: { code: 2466, category: DiagnosticCategory.Error, key: "'super' cannot be referenced in a computed property name." },
A_computed_property_name_cannot_reference_a_type_parameter_from_its_containing_type: { code: 2466, category: DiagnosticCategory.Error, key: "A computed property name cannot reference a type parameter from its containing type." },
Spread_operator_in_new_expressions_is_only_available_when_targeting_ECMAScript_6_and_higher: { code: 2468, category: DiagnosticCategory.Error, key: "Spread operator in 'new' expressions is only available when targeting ECMAScript 6 and higher." },
Import_declaration_0_is_using_private_name_1: { code: 4000, category: DiagnosticCategory.Error, key: "Import declaration '{0}' is using private name '{1}'." },
Type_parameter_0_of_exported_class_has_or_is_using_private_name_1: { code: 4002, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported class has or is using private name '{1}'." },
Type_parameter_0_of_exported_interface_has_or_is_using_private_name_1: { code: 4004, category: DiagnosticCategory.Error, key: "Type parameter '{0}' of exported interface has or is using private name '{1}'." },
@@ -381,6 +382,7 @@ module ts {
const_enum_member_initializer_was_evaluated_to_a_non_finite_value: { code: 4086, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to a non-finite value." },
const_enum_member_initializer_was_evaluated_to_disallowed_value_NaN: { code: 4087, category: DiagnosticCategory.Error, key: "'const' enum member initializer was evaluated to disallowed value 'NaN'." },
Property_0_does_not_exist_on_const_enum_1: { code: 4088, category: DiagnosticCategory.Error, key: "Property '{0}' does not exist on 'const' enum '{1}'." },
let_is_not_allowed_to_be_used_as_a_name_in_let_or_const_declarations: { code: 4089, category: DiagnosticCategory.Error, key: "'let' is not allowed to be used as a name in 'let' or 'const' declarations." },
The_current_host_does_not_support_the_0_option: { code: 5001, category: DiagnosticCategory.Error, key: "The current host does not support the '{0}' option." },
Cannot_find_the_common_subdirectory_path_for_the_input_files: { code: 5009, category: DiagnosticCategory.Error, key: "Cannot find the common subdirectory path for the input files." },
Cannot_read_file_0_Colon_1: { code: 5012, category: DiagnosticCategory.Error, key: "Cannot read file '{0}': {1}" },
+8
View File
@@ -1212,6 +1212,10 @@
"category": "Error",
"code": 2466
},
"Spread operator in 'new' expressions is only available when targeting ECMAScript 6 and higher.": {
"category": "Error",
"code": 2468
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
@@ -1517,6 +1521,10 @@
"category": "Error",
"code": 4088
},
"'let' is not allowed to be used as a name in 'let' or 'const' declarations.": {
"category": "Error",
"code": 4089
},
"The current host does not support the '{0}' option.": {
"category": "Error",
"code": 5001
+190 -106
View File
@@ -20,7 +20,6 @@ module ts {
importNode: ImportDeclaration | ImportEqualsDeclaration;
declarationNode?: ImportEqualsDeclaration | ImportClause | NamespaceImport;
namedImports?: NamedImports;
tempName?: Identifier; // Temporary name for module instance
}
interface SymbolAccessibilityDiagnostic {
@@ -2482,12 +2481,13 @@ module ts {
}
function emitExpressionIdentifier(node: Identifier) {
var prefix = resolver.getExpressionNamePrefix(node);
if (prefix) {
write(prefix);
write(".");
var substitution = resolver.getExpressionNameSubstitution(node);
if (substitution) {
write(substitution);
}
else {
writeTextOfNode(currentSourceFile, node);
}
writeTextOfNode(currentSourceFile, node);
}
function emitIdentifier(node: Identifier) {
@@ -2575,22 +2575,10 @@ module ts {
return true;
}
function emitArrayLiteral(node: ArrayLiteralExpression) {
var elements = node.elements;
var length = elements.length;
if (length === 0) {
write("[]");
return;
}
if (languageVersion >= ScriptTarget.ES6) {
write("[");
emitList(elements, 0, elements.length, /*multiLine*/(node.flags & NodeFlags.MultiLine) !== 0,
/*trailingComma*/ elements.hasTrailingComma);
write("]");
return;
}
function emitListWithSpread(elements: Expression[], multiLine: boolean, trailingComma: boolean) {
var pos = 0;
var group = 0;
var length = elements.length;
while (pos < length) {
// Emit using the pattern <group0>.concat(<group1>, <group2>, ...)
if (group === 1) {
@@ -2611,8 +2599,7 @@ module ts {
i++;
}
write("[");
emitList(elements, pos, i - pos, /*multiLine*/ (node.flags & NodeFlags.MultiLine) !== 0,
/*trailingComma*/ elements.hasTrailingComma);
emitList(elements, pos, i - pos, multiLine, trailingComma && i === length);
write("]");
pos = i;
}
@@ -2623,6 +2610,23 @@ module ts {
}
}
function emitArrayLiteral(node: ArrayLiteralExpression) {
var elements = node.elements;
if (elements.length === 0) {
write("[]");
}
else if (languageVersion >= ScriptTarget.ES6) {
write("[");
emitList(elements, 0, elements.length, /*multiLine*/ (node.flags & NodeFlags.MultiLine) !== 0,
/*trailingComma*/ elements.hasTrailingComma);
write("]");
}
else {
emitListWithSpread(elements, /*multiLine*/ (node.flags & NodeFlags.MultiLine) !== 0,
/*trailingComma*/ elements.hasTrailingComma);
}
}
function emitObjectLiteral(node: ObjectLiteralExpression) {
write("{");
var properties = node.properties;
@@ -2670,7 +2674,7 @@ module ts {
// export var obj = { y };
// }
// The short-hand property in obj need to emit as such ... = { y : m.y } regardless of the TargetScript version
if (languageVersion < ScriptTarget.ES6 || resolver.getExpressionNamePrefix(node.name)) {
if (languageVersion < ScriptTarget.ES6 || resolver.getExpressionNameSubstitution(node.name)) {
// Emit identifier as an identifier
write(": ");
// Even though this is stored as identifier treat it as an expression
@@ -2717,7 +2721,80 @@ module ts {
write("]");
}
function hasSpreadElement(elements: Expression[]) {
return forEach(elements, e => e.kind === SyntaxKind.SpreadElementExpression);
}
function skipParentheses(node: Expression): Expression {
while (node.kind === SyntaxKind.ParenthesizedExpression || node.kind === SyntaxKind.TypeAssertionExpression) {
node = (<ParenthesizedExpression | TypeAssertion>node).expression;
}
return node;
}
function emitCallTarget(node: Expression): Expression {
if (node.kind === SyntaxKind.Identifier || node.kind === SyntaxKind.ThisKeyword || node.kind === SyntaxKind.SuperKeyword) {
emit(node);
return node;
}
var temp = createTempVariable(node);
recordTempDeclaration(temp);
write("(");
emit(temp);
write(" = ");
emit(node);
write(")");
return temp;
}
function emitCallWithSpread(node: CallExpression) {
var target: Expression;
var expr = skipParentheses(node.expression);
if (expr.kind === SyntaxKind.PropertyAccessExpression) {
// Target will be emitted as "this" argument
target = emitCallTarget((<PropertyAccessExpression>expr).expression);
write(".");
emit((<PropertyAccessExpression>expr).name);
}
else if (expr.kind === SyntaxKind.ElementAccessExpression) {
// Target will be emitted as "this" argument
target = emitCallTarget((<PropertyAccessExpression>expr).expression);
write("[");
emit((<ElementAccessExpression>expr).argumentExpression);
write("]");
}
else if (expr.kind === SyntaxKind.SuperKeyword) {
target = expr;
write("_super");
}
else {
emit(node.expression);
}
write(".apply(");
if (target) {
if (target.kind === SyntaxKind.SuperKeyword) {
// Calls of form super(...) and super.foo(...)
emitThis(target);
}
else {
// Calls of form obj.foo(...)
emit(target);
}
}
else {
// Calls of form foo(...)
write("void 0");
}
write(", ");
emitListWithSpread(node.arguments, /*multiLine*/ false, /*trailingComma*/ false);
write(")");
}
function emitCallExpression(node: CallExpression) {
if (languageVersion < ScriptTarget.ES6 && hasSpreadElement(node.arguments)) {
emitCallWithSpread(node);
return;
}
var superCall = false;
if (node.expression.kind === SyntaxKind.SuperKeyword) {
write("_super");
@@ -2865,7 +2942,21 @@ module ts {
emit(node.whenFalse);
}
function isSingleLineBlock(node: Node) {
if (node && node.kind === SyntaxKind.Block) {
var block = <Block>node;
return block.statements.length === 0 && nodeEndIsOnSameLineAsNodeStart(block, block);
}
}
function emitBlock(node: Block) {
if (isSingleLineBlock(node)) {
emitToken(SyntaxKind.OpenBraceToken, node.pos);
write(" ");
emitToken(SyntaxKind.CloseBraceToken, node.statements.end);
return;
}
emitToken(SyntaxKind.OpenBraceToken, node.pos);
increaseIndent();
scopeEmitStart(node.parent);
@@ -3038,6 +3129,11 @@ module ts {
getLineOfLocalPosition(currentSourceFile, skipTrivia(currentSourceFile.text, node2.pos));
}
function nodeEndIsOnSameLineAsNodeStart(node1: Node, node2: Node) {
return getLineOfLocalPosition(currentSourceFile, node1.end) ===
getLineOfLocalPosition(currentSourceFile, skipTrivia(currentSourceFile.text, node2.pos));
}
function emitCaseOrDefaultClause(node: CaseOrDefaultClause) {
if (node.kind === SyntaxKind.CaseClause) {
write("case ");
@@ -3108,7 +3204,7 @@ module ts {
emitStart(node.name);
if (getCombinedNodeFlags(node) & NodeFlags.Export) {
var container = getContainingModule(node);
write(container ? resolver.getLocalNameOfContainer(container) : "exports");
write(container ? resolver.getGeneratedNameForNode(container) : "exports");
write(".");
}
emitNode(node.name);
@@ -3537,73 +3633,79 @@ module ts {
emitSignatureParameters(node);
}
write(" {");
scopeEmitStart(node);
if (!node.body) {
writeLine();
write("}");
if (isSingleLineBlock(node.body)) {
write(" { }");
}
else {
increaseIndent();
write(" {");
scopeEmitStart(node);
emitDetachedComments(node.body.kind === SyntaxKind.Block ? (<Block>node.body).statements : node.body);
var startIndex = 0;
if (node.body.kind === SyntaxKind.Block) {
startIndex = emitDirectivePrologues((<Block>node.body).statements, /*startWithNewLine*/ true);
}
var outPos = writer.getTextPos();
emitCaptureThisForNodeIfNecessary(node);
emitDefaultValueAssignments(node);
emitRestParameter(node);
if (node.body.kind !== SyntaxKind.Block && outPos === writer.getTextPos()) {
decreaseIndent();
write(" ");
emitStart(node.body);
write("return ");
// Don't emit comments on this body. We'll have already taken care of it above
// when we called emitDetachedComments.
emitNode(node.body, /*disableComments:*/ true);
emitEnd(node.body);
write(";");
emitTempDeclarations(/*newLine*/ false);
write(" ");
emitStart(node.body);
if (!node.body) {
writeLine();
write("}");
emitEnd(node.body);
}
else {
increaseIndent();
emitDetachedComments(node.body.kind === SyntaxKind.Block ? (<Block>node.body).statements : node.body);
var startIndex = 0;
if (node.body.kind === SyntaxKind.Block) {
emitLinesStartingAt((<Block>node.body).statements, startIndex);
startIndex = emitDirectivePrologues((<Block>node.body).statements, /*startWithNewLine*/ true);
}
else {
writeLine();
emitLeadingComments(node.body);
var outPos = writer.getTextPos();
emitCaptureThisForNodeIfNecessary(node);
emitDefaultValueAssignments(node);
emitRestParameter(node);
if (node.body.kind !== SyntaxKind.Block && outPos === writer.getTextPos()) {
decreaseIndent();
write(" ");
emitStart(node.body);
write("return ");
emit(node.body, /*disableComments:*/ true);
// Don't emit comments on this body. We'll have already taken care of it above
// when we called emitDetachedComments.
emitNode(node.body, /*disableComments:*/ true);
emitEnd(node.body);
write(";");
emitTrailingComments(node.body);
}
emitTempDeclarations(/*newLine*/ true);
writeLine();
if (node.body.kind === SyntaxKind.Block) {
emitLeadingCommentsOfPosition((<Block>node.body).statements.end);
decreaseIndent();
emitToken(SyntaxKind.CloseBraceToken, (<Block>node.body).statements.end);
}
else {
decreaseIndent();
emitTempDeclarations(/*newLine*/ false);
write(" ");
emitStart(node.body);
write("}");
emitEnd(node.body);
}
else {
if (node.body.kind === SyntaxKind.Block) {
emitLinesStartingAt((<Block>node.body).statements, startIndex);
}
else {
writeLine();
emitLeadingComments(node.body);
write("return ");
emit(node.body, /*disableComments:*/ true);
write(";");
emitTrailingComments(node.body);
}
emitTempDeclarations(/*newLine*/ true);
writeLine();
if (node.body.kind === SyntaxKind.Block) {
emitLeadingCommentsOfPosition((<Block>node.body).statements.end);
decreaseIndent();
emitToken(SyntaxKind.CloseBraceToken, (<Block>node.body).statements.end);
}
else {
decreaseIndent();
emitStart(node.body);
write("}");
emitEnd(node.body);
}
}
}
scopeEmitEnd();
}
scopeEmitEnd();
if (node.flags & NodeFlags.Export) {
writeLine();
emitStart(node);
@@ -3915,7 +4017,7 @@ module ts {
emitStart(node);
write("(function (");
emitStart(node.name);
write(resolver.getLocalNameOfContainer(node));
write(resolver.getGeneratedNameForNode(node));
emitEnd(node.name);
write(") {");
increaseIndent();
@@ -3946,9 +4048,9 @@ module ts {
function emitEnumMember(node: EnumMember) {
var enumParent = <EnumDeclaration>node.parent;
emitStart(node);
write(resolver.getLocalNameOfContainer(enumParent));
write(resolver.getGeneratedNameForNode(enumParent));
write("[");
write(resolver.getLocalNameOfContainer(enumParent));
write(resolver.getGeneratedNameForNode(enumParent));
write("[");
emitExpressionForPropertyName(node.name);
write("] = ");
@@ -4004,7 +4106,7 @@ module ts {
emitStart(node);
write("(function (");
emitStart(node.name);
write(resolver.getLocalNameOfContainer(node));
write(resolver.getGeneratedNameForNode(node));
emitEnd(node.name);
write(") ");
if (node.body.kind === SyntaxKind.ModuleBlock) {
@@ -4062,23 +4164,6 @@ module ts {
emitRequire(moduleName);
}
function emitNamedImportAssignments(namedImports: NamedImports, moduleReference: Identifier) {
var elements = namedImports.elements;
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
if (resolver.isReferencedImportDeclaration(element)) {
writeLine();
if (!(element.flags & NodeFlags.Export)) write("var ");
emitModuleMemberName(element);
write(" = ");
emit(moduleReference);
write(".");
emit(element.propertyName || element.name);
write(";");
}
}
}
function emitImportDeclaration(node: ImportDeclaration | ImportEqualsDeclaration) {
var info = getExternalImportInfo(node);
if (info) {
@@ -4087,7 +4172,7 @@ module ts {
if (compilerOptions.module !== ModuleKind.AMD) {
emitLeadingComments(node);
emitStart(node);
var moduleName = getImportedModuleName(info.importNode);
var moduleName = getImportedModuleName(node);
if (declarationNode) {
if (!(declarationNode.flags & NodeFlags.Export)) write("var ");
emitModuleMemberName(declarationNode);
@@ -4096,10 +4181,9 @@ module ts {
}
else if (namedImports) {
write("var ");
emit(info.tempName);
write(resolver.getGeneratedNameForNode(<ImportDeclaration>node));
write(" = ");
emitRequire(moduleName);
emitNamedImportAssignments(namedImports, info.tempName);
}
else {
emitRequire(moduleName);
@@ -4116,9 +4200,6 @@ module ts {
write(";");
}
}
else if (namedImports) {
emitNamedImportAssignments(namedImports, info.tempName);
}
}
}
}
@@ -4171,7 +4252,8 @@ module ts {
}
return {
importNode: <ImportDeclaration>node,
namedImports: <NamedImports>importClause.namedBindings
namedImports: <NamedImports>importClause.namedBindings,
localName: resolver.getGeneratedNameForNode(<ImportDeclaration>node)
};
}
return {
@@ -4186,9 +4268,6 @@ module ts {
var info = createExternalImportInfo(node);
if (info) {
if ((!info.declarationNode && !info.namedImports) || resolver.isReferencedImportDeclaration(node)) {
if (!info.declarationNode) {
info.tempName = createTempVariable(sourceFile);
}
externalImports.push(info);
}
}
@@ -4239,7 +4318,12 @@ module ts {
write("], function (require, exports");
forEach(externalImports, info => {
write(", ");
emit(info.declarationNode ? info.declarationNode.name : info.tempName);
if (info.declarationNode) {
emit(info.declarationNode.name);
}
else {
write(resolver.getGeneratedNameForNode(<ImportDeclaration>info.importNode));
}
});
write(") {");
increaseIndent();
+164 -77
View File
@@ -352,11 +352,16 @@ module ts {
}
function fixupParentReferences(sourceFile: SourceFile) {
// normally parent references are set during binding.
// however here SourceFile data is used only for syntactic features so running the whole binding process is an overhead.
// walk over the nodes and set parent references
// normally parent references are set during binding. However, for clients that only need
// a syntax tree, and no semantic features, then the binding process is an unnecessary
// overhead. This functions allows us to set all the parents, without all the expense of
// binding.
var parent: Node = sourceFile;
function walk(n: Node): void {
forEachChild(sourceFile, visitNode);
return;
function visitNode(n: Node): void {
// walk down setting parents that differ from the parent we think it should be. This
// allows us to quickly bail out of setting parents for subtrees during incremental
// parsing
@@ -365,33 +370,53 @@ module ts {
var saveParent = parent;
parent = n;
forEachChild(n, walk);
forEachChild(n, visitNode);
parent = saveParent;
}
}
forEachChild(sourceFile, walk);
}
function moveElementEntirelyPastChangeRange(element: IncrementalElement, delta: number) {
if (element.length) {
function shouldCheckNode(node: Node) {
switch (node.kind) {
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
case SyntaxKind.Identifier:
return true;
}
return false;
}
function moveElementEntirelyPastChangeRange(element: IncrementalElement, isArray: boolean, delta: number, oldText: string, newText: string, aggressiveChecks: boolean) {
if (isArray) {
visitArray(<IncrementalNodeArray>element);
}
else {
visitNode(<IncrementalNode>element);
}
return;
function visitNode(node: IncrementalNode) {
if (aggressiveChecks && shouldCheckNode(node)) {
var text = oldText.substring(node.pos, node.end);
}
// Ditch any existing LS children we may have created. This way we can avoid
// moving them forward.
node._children = undefined;
node.pos += delta;
node.end += delta;
if (aggressiveChecks && shouldCheckNode(node)) {
Debug.assert(text === newText.substring(node.pos, node.end));
}
forEachChild(node, visitNode, visitArray);
checkNodePositions(node, aggressiveChecks);
}
function visitArray(array: IncrementalNodeArray) {
array._children = undefined;
array.pos += delta;
array.end += delta;
@@ -404,6 +429,7 @@ module ts {
function adjustIntersectingElement(element: IncrementalElement, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number) {
Debug.assert(element.end >= changeStart, "Adjusting an element that was entirely before the change range");
Debug.assert(element.pos <= changeRangeOldEnd, "Adjusting an element that was entirely after the change range");
Debug.assert(element.pos <= element.end);
// We have an element that intersects the change range in some way. It may have its
// start, or its end (or both) in the changed range. We want to adjust any part
@@ -475,14 +501,36 @@ module ts {
}
}
function updateTokenPositionsAndMarkElements(node: IncrementalNode, changeStart: number, changeRangeOldEnd: number, changeRangeNewEnd: number, delta: number): void {
visitNode(node);
function checkNodePositions(node: Node, aggressiveChecks: boolean) {
if (aggressiveChecks) {
var pos = node.pos;
forEachChild(node, child => {
Debug.assert(child.pos >= pos);
pos = child.end;
});
Debug.assert(pos <= node.end);
}
}
function updateTokenPositionsAndMarkElements(
sourceFile: IncrementalNode,
changeStart: number,
changeRangeOldEnd: number,
changeRangeNewEnd: number,
delta: number,
oldText: string,
newText: string,
aggressiveChecks: boolean): void {
visitNode(sourceFile);
return;
function visitNode(child: IncrementalNode) {
Debug.assert(child.pos <= child.end);
if (child.pos > changeRangeOldEnd) {
// Node is entirely past the change range. We need to move both its pos and
// end, forward or backward appropriately.
moveElementEntirelyPastChangeRange(child, delta);
moveElementEntirelyPastChangeRange(child, /*isArray:*/ false, delta, oldText, newText, aggressiveChecks);
return;
}
@@ -492,44 +540,50 @@ module ts {
var fullEnd = child.end;
if (fullEnd >= changeStart) {
child.intersectsChange = true;
child._children = undefined;
// Adjust the pos or end (or both) of the intersecting element accordingly.
adjustIntersectingElement(child, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta);
forEachChild(child, visitNode, visitArray);
checkNodePositions(child, aggressiveChecks);
return;
}
// Otherwise, the node is entirely before the change range. No need to do anything with it.
Debug.assert(fullEnd < changeStart);
}
function visitArray(array: IncrementalNodeArray) {
Debug.assert(array.pos <= array.end);
if (array.pos > changeRangeOldEnd) {
// Array is entirely after the change range. We need to move it, and move any of
// its children.
moveElementEntirelyPastChangeRange(array, delta);
moveElementEntirelyPastChangeRange(array, /*isArray:*/ true, delta, oldText, newText, aggressiveChecks);
return;
}
else {
// Check if the element intersects the change range. If it does, then it is not
// reusable. Also, we'll need to recurse to see what constituent portions we may
// be able to use.
var fullEnd = array.end;
if (fullEnd >= changeStart) {
array.intersectsChange = true;
// Adjust the pos or end (or both) of the intersecting array accordingly.
adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta);
for (var i = 0, n = array.length; i < n; i++) {
visitNode(array[i]);
}
// Check if the element intersects the change range. If it does, then it is not
// reusable. Also, we'll need to recurse to see what constituent portions we may
// be able to use.
var fullEnd = array.end;
if (fullEnd >= changeStart) {
array.intersectsChange = true;
array._children = undefined;
// Adjust the pos or end (or both) of the intersecting array accordingly.
adjustIntersectingElement(array, changeStart, changeRangeOldEnd, changeRangeNewEnd, delta);
for (var i = 0, n = array.length; i < n; i++) {
visitNode(array[i]);
}
// else {
// Otherwise, the array is entirely before the change range. No need to do anything with it.
// }
return;
}
// Otherwise, the array is entirely before the change range. No need to do anything with it.
Debug.assert(fullEnd < changeStart);
}
}
function extendToAffectedRange(sourceFile: SourceFile, changeRange: TextChangeRange): TextChangeRange {
// Consider the following code:
// void foo() { /; }
@@ -550,6 +604,7 @@ module ts {
// start of the tree.
for (var i = 0; start > 0 && i <= maxLookahead; i++) {
var nearestNode = findNearestNodeStartingBeforeOrAtPosition(sourceFile, start);
Debug.assert(nearestNode.pos <= start);
var position = nearestNode.pos;
start = Math.max(0, position - 1);
@@ -656,6 +711,22 @@ module ts {
}
}
function checkChangeRange(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks: boolean) {
var oldText = sourceFile.text;
if (textChangeRange) {
Debug.assert((oldText.length - textChangeRange.span.length + textChangeRange.newLength) === newText.length);
if (aggressiveChecks || Debug.shouldAssert(AssertionLevel.VeryAggressive)) {
var oldTextPrefix = oldText.substr(0, textChangeRange.span.start);
var newTextPrefix = newText.substr(0, textChangeRange.span.start);
Debug.assert(oldTextPrefix === newTextPrefix);
var oldTextSuffix = oldText.substring(textSpanEnd(textChangeRange.span), oldText.length);
var newTextSuffix = newText.substring(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.length);
Debug.assert(oldTextSuffix === newTextSuffix);
}
}
}
// Produces a new SourceFile for the 'newText' provided. The 'textChangeRange' parameter
// indicates what changed between the 'text' that this SourceFile has and the 'newText'.
@@ -666,7 +737,10 @@ module ts {
// from this SourceFile that are being held onto may change as a result (including
// becoming detached from any SourceFile). It is recommended that this SourceFile not
// be used once 'update' is called on it.
export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange): SourceFile {
export function updateSourceFile(sourceFile: SourceFile, newText: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile {
aggressiveChecks = aggressiveChecks || Debug.shouldAssert(AssertionLevel.Aggressive);
checkChangeRange(sourceFile, newText, textChangeRange, aggressiveChecks);
if (textChangeRangeIsUnchanged(textChangeRange)) {
// if the text didn't change, then we can just return our current source file as-is.
return sourceFile;
@@ -675,14 +749,32 @@ module ts {
if (sourceFile.statements.length === 0) {
// If we don't have any statements in the current source file, then there's no real
// way to incrementally parse. So just do a full parse instead.
return parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion,/*syntaxCursor*/ undefined, /*setNodeParents*/ true)
return parseSourceFile(sourceFile.fileName, newText, sourceFile.languageVersion, /*syntaxCursor*/ undefined, /*setNodeParents*/ true)
}
// Make sure we're not trying to incrementally update a source file more than once. Once
// we do an update the original source file is considered unusbale from that point onwards.
//
// This is because we do incremental parsing in-place. i.e. we take nodes from the old
// tree and give them new positions and parents. From that point on, trusting the old
// tree at all is not possible as far too much of it may violate invariants.
var incrementalSourceFile = <IncrementalNode><Node>sourceFile;
Debug.assert(!incrementalSourceFile.hasBeenIncrementallyParsed);
incrementalSourceFile.hasBeenIncrementallyParsed = true;
var oldText = sourceFile.text;
var syntaxCursor = createSyntaxCursor(sourceFile);
// Make the actual change larger so that we know to reparse anything whose lookahead
// might have intersected the change.
var changeRange = extendToAffectedRange(sourceFile, textChangeRange);
checkChangeRange(sourceFile, newText, changeRange, aggressiveChecks);
// Ensure that extending the affected range only moved the start of the change range
// earlier in the file.
Debug.assert(changeRange.span.start <= textChangeRange.span.start);
Debug.assert(textSpanEnd(changeRange.span) === textSpanEnd(textChangeRange.span));
Debug.assert(textSpanEnd(textChangeRangeNewSpan(changeRange)) === textSpanEnd(textChangeRangeNewSpan(textChangeRange)));
// The is the amount the nodes after the edit range need to be adjusted. It can be
// positive (if the edit added characters), negative (if the edit deleted characters)
@@ -690,8 +782,8 @@ module ts {
var delta = textChangeRangeNewSpan(changeRange).length - changeRange.span.length;
// If we added or removed characters during the edit, then we need to go and adjust all
// the nodes after the edit. Those nodes may move forward down (if we inserted chars)
// or they may move backward (if we deleted chars).
// the nodes after the edit. Those nodes may move forward (if we inserted chars) or they
// may move backward (if we deleted chars).
//
// Doing this helps us out in two ways. First, it means that any nodes/tokens we want
// to reuse are already at the appropriate position in the new text. That way when we
@@ -708,8 +800,8 @@ module ts {
//
// Also, mark any syntax elements that intersect the changed span. We know, up front,
// that we cannot reuse these elements.
updateTokenPositionsAndMarkElements(<IncrementalNode><Node>sourceFile,
changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta);
updateTokenPositionsAndMarkElements(incrementalSourceFile,
changeRange.span.start, textSpanEnd(changeRange.span), textSpanEnd(textChangeRangeNewSpan(changeRange)), delta, oldText, newText, aggressiveChecks);
// Now that we've set up our internal incremental state just proceed and parse the
// source file in the normal fashion. When possible the parser will retrieve and
@@ -749,6 +841,7 @@ module ts {
}
interface IncrementalNode extends Node, IncrementalElement {
hasBeenIncrementallyParsed: boolean
}
interface IncrementalNodeArray extends NodeArray<IncrementalNode>, IncrementalElement {
@@ -784,7 +877,7 @@ module ts {
// Much of the time the parser will need the very next node in the array that
// we just returned a node from.So just simply check for that case and move
// forward in the array instead of searching for the node again.
if (current && current.end === position && currentArrayIndex < currentArray.length) {
if (current && current.end === position && currentArrayIndex < (currentArray.length - 1)) {
currentArrayIndex++;
current = currentArray[currentArrayIndex];
}
@@ -820,6 +913,7 @@ module ts {
// Recurse into the source file to find the highest node at this position.
forEachChild(sourceFile, visitNode, visitArray);
return;
function visitNode(node: Node) {
if (position >= node.pos && position < node.end) {
@@ -879,7 +973,6 @@ module ts {
var identifiers: Map<string> = {};
var identifierCount = 0;
var nodeCount = 0;
var scanner: Scanner;
var token: SyntaxKind;
var sourceFile = <SourceFile>createNode(SyntaxKind.SourceFile, /*pos*/ 0);
@@ -972,7 +1065,7 @@ module ts {
var parseErrorBeforeNextFinishedNode: boolean = false;
// Create and prime the scanner before parsing the source elements.
scanner = createScanner(languageVersion, /*skipTrivia*/ true, sourceText, scanError);
var scanner = createScanner(languageVersion, /*skipTrivia*/ true, sourceText, scanError);
token = nextToken();
processReferenceComments(sourceFile);
@@ -991,6 +1084,7 @@ module ts {
fixupParentReferences(sourceFile);
}
syntaxCursor = undefined;
return sourceFile;
function setContextFlag(val: Boolean, flag: ParserContextFlags) {
@@ -1438,7 +1532,6 @@ module ts {
case ParsingContext.TypeParameters:
return isIdentifier();
case ParsingContext.ArgumentExpressions:
return token === SyntaxKind.CommaToken || isStartOfExpression();
case ParsingContext.ArrayLiteralMembers:
return token === SyntaxKind.CommaToken || token === SyntaxKind.DotDotDotToken || isStartOfExpression();
case ParsingContext.Parameters:
@@ -1593,8 +1686,8 @@ module ts {
return result;
}
function parseListElement<T extends Node>(kind: ParsingContext, parseElement: () => T): T {
var node = currentNode(kind);
function parseListElement<T extends Node>(parsingContext: ParsingContext, parseElement: () => T): T {
var node = currentNode(parsingContext);
if (node) {
return <T>consumeNode(node);
}
@@ -1751,29 +1844,10 @@ module ts {
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.EnumDeclaration:
// Keep in sync with isStatement:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.VariableStatement:
case SyntaxKind.Block:
case SyntaxKind.IfStatement:
case SyntaxKind.ExpressionStatement:
case SyntaxKind.ThrowStatement:
case SyntaxKind.ReturnStatement:
case SyntaxKind.SwitchStatement:
case SyntaxKind.BreakStatement:
case SyntaxKind.ContinueStatement:
case SyntaxKind.ForInStatement:
case SyntaxKind.ForStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.WithStatement:
case SyntaxKind.EmptyStatement:
case SyntaxKind.TryStatement:
case SyntaxKind.LabeledStatement:
case SyntaxKind.DoStatement:
case SyntaxKind.DebuggerStatement:
return true;
}
return isReusableStatement(node);
}
return false;
@@ -1879,9 +1953,13 @@ module ts {
}
function isReusableParameter(node: Node) {
// TODO: this most likely needs the same initializer check that
// isReusableVariableDeclaration has.
return node.kind === SyntaxKind.Parameter;
if (node.kind !== SyntaxKind.Parameter) {
return false;
}
// See the comment in isReusableVariableDeclaration for why we do this.
var parameter = <ParameterDeclaration>node;
return parameter.initializer === undefined;
}
// Returns true if we should abort parsing.
@@ -1896,7 +1974,7 @@ module ts {
}
// Parses a comma-delimited list of elements
function parseDelimitedList<T extends Node>(kind: ParsingContext, parseElement: () => T): NodeArray<T> {
function parseDelimitedList<T extends Node>(kind: ParsingContext, parseElement: () => T, considerSemicolonAsDelimeter?: boolean): NodeArray<T> {
var saveParsingContext = parsingContext;
parsingContext |= 1 << kind;
var result = <NodeArray<T>>[];
@@ -1910,11 +1988,24 @@ module ts {
if (parseOptional(SyntaxKind.CommaToken)) {
continue;
}
commaStart = -1; // Back to the state where the last token was not a comma
if (isListTerminator(kind)) {
break;
}
// We didn't get a comma, and the list wasn't terminated, explicitly parse
// out a comma so we give a good error message.
parseExpected(SyntaxKind.CommaToken);
// If the token was a semicolon, and the caller allows that, then skip it and
// continue. This ensures we get back on track and don't result in tons of
// parse errors. For example, this can happen when people do things like use
// a semicolon to delimit object literal members. Note: we'll have already
// reported an error when we called parseExpected above.
if (considerSemicolonAsDelimeter && token === SyntaxKind.SemicolonToken && !scanner.hasPrecedingLineBreak()) {
nextToken();
}
continue;
}
@@ -3531,12 +3622,6 @@ module ts {
return finishNode(node);
}
function parseAssignmentExpressionOrOmittedExpression(): Expression {
return token === SyntaxKind.CommaToken
? <Expression>createNode(SyntaxKind.OmittedExpression)
: parseAssignmentExpressionOrHigher();
}
function parseSpreadElement(): Expression {
var node = <SpreadElementExpression>createNode(SyntaxKind.SpreadElementExpression);
parseExpected(SyntaxKind.DotDotDotToken);
@@ -3544,19 +3629,21 @@ module ts {
return finishNode(node);
}
function parseArrayLiteralElement(): Expression {
return token === SyntaxKind.DotDotDotToken ? parseSpreadElement() : parseAssignmentExpressionOrOmittedExpression();
function parseArgumentOrArrayLiteralElement(): Expression {
return token === SyntaxKind.DotDotDotToken ? parseSpreadElement() :
token === SyntaxKind.CommaToken ? <Expression>createNode(SyntaxKind.OmittedExpression) :
parseAssignmentExpressionOrHigher();
}
function parseArgumentExpression(): Expression {
return allowInAnd(parseAssignmentExpressionOrOmittedExpression);
return allowInAnd(parseArgumentOrArrayLiteralElement);
}
function parseArrayLiteralExpression(): ArrayLiteralExpression {
var node = <ArrayLiteralExpression>createNode(SyntaxKind.ArrayLiteralExpression);
parseExpected(SyntaxKind.OpenBracketToken);
if (scanner.hasPrecedingLineBreak()) node.flags |= NodeFlags.MultiLine;
node.elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArrayLiteralElement);
node.elements = parseDelimitedList(ParsingContext.ArrayLiteralMembers, parseArgumentOrArrayLiteralElement);
parseExpected(SyntaxKind.CloseBracketToken);
return finishNode(node);
}
@@ -3616,7 +3703,7 @@ module ts {
node.flags |= NodeFlags.MultiLine;
}
node.properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement);
node.properties = parseDelimitedList(ParsingContext.ObjectLiteralMembers, parseObjectLiteralElement, /*considerSemicolonAsDelimeter:*/ true);
parseExpected(SyntaxKind.CloseBraceToken);
return finishNode(node);
}
+4 -3
View File
@@ -1165,8 +1165,8 @@ module ts {
}
export interface EmitResolver {
getLocalNameOfContainer(container: ModuleDeclaration | EnumDeclaration): string;
getExpressionNamePrefix(node: Identifier): string;
getGeneratedNameForNode(node: ModuleDeclaration | EnumDeclaration | ImportDeclaration): string;
getExpressionNameSubstitution(node: Identifier): string;
getExportAssignmentName(node: SourceFile): string;
isReferencedImportDeclaration(node: Node): boolean;
isTopLevelValueImportEqualsWithEntityName(node: ImportEqualsDeclaration): boolean;
@@ -1315,7 +1315,8 @@ module ts {
enumMemberValue?: number; // Constant value of enum member
isIllegalTypeReferenceInConstraint?: boolean; // Is type reference in constraint refers to the type parameter from the same list
isVisible?: boolean; // Is this node visible
localModuleName?: string; // Local name for module instance
generatedName?: string; // Generated name for module, enum, or import declaration
generatedNames?: Map<string>; // Generated names table for source file
assignmentChecks?: Map<boolean>; // Cache of assignment checks
hasReportedStatementInAmbientContext?: boolean; // Cache boolean if we report statements in ambient context
importOnRightSide?: Symbol; // for import declarations - import that appear on the right side
+6
View File
@@ -181,6 +181,12 @@ module ts {
return identifier.length >= 3 && identifier.charCodeAt(0) === CharacterCodes._ && identifier.charCodeAt(1) === CharacterCodes._ && identifier.charCodeAt(2) === CharacterCodes._ ? identifier.substr(1) : identifier;
}
// Make an identifier from an external module name by extracting the string after the last "/" and replacing
// all non-alphanumeric characters with underscores
export function makeIdentifierFromModuleName(moduleName: string): string {
return getBaseFileName(moduleName).replace(/\W/g, "_");
}
// Return display name of an identifier
// Computed property names will just be emitted as "[<expr>]", where <expr> is the source
// text of the expression in the computed property.
+89 -141
View File
@@ -16,6 +16,7 @@
/// <reference path='..\services\services.ts' />
/// <reference path='harnessLanguageService.ts' />
/// <reference path='harness.ts' />
/// <reference path='fourslashRunner.ts' />
module FourSlash {
ts.disableIncrementalParsing = false;
@@ -245,11 +246,9 @@ module FourSlash {
export class TestState {
// Language service instance
public languageServiceShimHost: Harness.LanguageService.TypeScriptLS;
private languageServiceAdapterHost: Harness.LanguageService.LanguageServiceAdapterHost;
private languageService: ts.LanguageService;
// A reference to the language service's compiler state's compiler instance
private compiler: () => { getSyntaxTree(fileName: string): ts.SourceFile };
private cancellationToken: TestCancellationToken;
// The current caret position in the active file
public currentCaretPosition = 0;
@@ -263,8 +262,6 @@ module FourSlash {
public formatCodeOptions: ts.FormatCodeOptions;
public cancellationToken: TestCancellationToken;
private scenarioActions: string[] = [];
private taoInvalidReason: string = null;
@@ -275,19 +272,31 @@ module FourSlash {
private addMatchedInputFile(referenceFilePath: string) {
var inputFile = this.inputFiles[referenceFilePath];
if (inputFile && !Harness.isLibraryFile(referenceFilePath)) {
this.languageServiceShimHost.addScript(referenceFilePath, inputFile);
this.languageServiceAdapterHost.addScript(referenceFilePath, inputFile);
}
}
constructor(public testData: FourSlashData) {
// Initialize the language service with all the scripts
private getLanguageServiceAdapter(testType: FourSlashTestType, cancellationToken: TestCancellationToken, compilationOptions: ts.CompilerOptions): Harness.LanguageService.LanguageServiceAdapter {
switch (testType) {
case FourSlashTestType.Native:
return new Harness.LanguageService.NativeLanugageServiceAdapter(cancellationToken, compilationOptions);
case FourSlashTestType.Shims:
return new Harness.LanguageService.ShimLanugageServiceAdapter(cancellationToken, compilationOptions);
default:
throw new Error("Unknown FourSlash test type: ");
}
}
constructor(private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) {
// Create a new Services Adapter
this.cancellationToken = new TestCancellationToken();
this.languageServiceShimHost = new Harness.LanguageService.TypeScriptLS(this.cancellationToken);
var compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions);
var languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions);
this.languageServiceAdapterHost = languageServiceAdapter.getHost();
this.languageService = languageServiceAdapter.getLanguageService();
var compilationSettings = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions);
this.languageServiceShimHost.setCompilationSettings(compilationSettings);
var startResolveFileRef: FourSlashFile = undefined;
// Initialize the language service with all the scripts
var startResolveFileRef: FourSlashFile;
ts.forEach(testData.files, file => {
// Create map between fileName and its content for easily looking up when resolveReference flag is specified
@@ -302,18 +311,16 @@ module FourSlash {
if (startResolveFileRef) {
// Add the entry-point file itself into the languageServiceShimHost
this.languageServiceShimHost.addScript(startResolveFileRef.fileName, startResolveFileRef.content);
this.languageServiceAdapterHost.addScript(startResolveFileRef.fileName, startResolveFileRef.content);
var jsonResolvedResult = JSON.parse(this.languageServiceShimHost.getCoreService().getPreProcessedFileInfo(startResolveFileRef.fileName,
createScriptSnapShot(startResolveFileRef.content)));
var resolvedResult = jsonResolvedResult.result;
var referencedFiles: ts.IFileReference[] = resolvedResult.referencedFiles;
var importedFiles: ts.IFileReference[] = resolvedResult.importedFiles;
var resolvedResult = languageServiceAdapter.getPreProcessedFileInfo(startResolveFileRef.fileName, startResolveFileRef.content);
var referencedFiles: ts.FileReference[] = resolvedResult.referencedFiles;
var importedFiles: ts.FileReference[] = resolvedResult.importedFiles;
// Add triple reference files into language-service host
ts.forEach(referencedFiles, referenceFile => {
// Fourslash insert tests/cases/fourslash into inputFile.unitName so we will properly append the same base directory to refFile path
var referenceFilePath = "tests/cases/fourslash/" + referenceFile.path;
var referenceFilePath = this.basePath + '/' + referenceFile.fileName;
this.addMatchedInputFile(referenceFilePath);
});
@@ -321,29 +328,24 @@ module FourSlash {
ts.forEach(importedFiles, importedFile => {
// Fourslash insert tests/cases/fourslash into inputFile.unitName and import statement doesn't require ".ts"
// so convert them before making appropriate comparison
var importedFilePath = "tests/cases/fourslash/" + importedFile.path + ".ts";
var importedFilePath = this.basePath + '/' + importedFile.fileName + ".ts";
this.addMatchedInputFile(importedFilePath);
});
// Check if no-default-lib flag is false and if so add default library
if (!resolvedResult.isLibFile) {
this.languageServiceShimHost.addDefaultLibrary();
this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.defaultLibSourceFile.text);
}
} else {
// resolveReference file-option is not specified then do not resolve any files and include all inputFiles
ts.forEachKey(this.inputFiles, fileName => {
if (!Harness.isLibraryFile(fileName)) {
this.languageServiceShimHost.addScript(fileName, this.inputFiles[fileName]);
this.languageServiceAdapterHost.addScript(fileName, this.inputFiles[fileName]);
}
});
this.languageServiceShimHost.addDefaultLibrary();
this.languageServiceAdapterHost.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.defaultLibSourceFile.text);
}
// Sneak into the language service and get its compiler so we can examine the syntax trees
this.languageService = this.languageServiceShimHost.getLanguageService().languageService;
var compilerState = (<any>this.languageService).compiler;
this.compiler = () => compilerState.compiler;
this.formatCodeOptions = {
IndentSize: 4,
TabSize: 4,
@@ -369,6 +371,11 @@ module FourSlash {
this.openFile(0);
}
private getFileContent(fileName: string): string {
var script = this.languageServiceAdapterHost.getScriptInfo(fileName);
return script.content;
}
// Entry points from fourslash.ts
public goToMarker(name = '') {
var marker = this.getMarkerByName(name);
@@ -376,8 +383,8 @@ module FourSlash {
this.openFile(marker.fileName);
}
var scriptSnapshot = this.languageServiceShimHost.getScriptSnapshot(marker.fileName);
if (marker.position === -1 || marker.position > scriptSnapshot.getLength()) {
var content = this.getFileContent(marker.fileName);
if (marker.position === -1 || marker.position > content.length) {
throw new Error('Marker "' + name + '" has been invalidated by unrecoverable edits to the file.');
}
this.lastKnownMarker = name;
@@ -387,14 +394,14 @@ module FourSlash {
public goToPosition(pos: number) {
this.currentCaretPosition = pos;
var lineStarts = ts.computeLineStarts(this.getCurrentFileContent());
var lineStarts = ts.computeLineStarts(this.getFileContent(this.activeFile.fileName));
var lineCharPos = ts.computeLineAndCharacterOfPosition(lineStarts, pos);
this.scenarioActions.push('<MoveCaretToLineAndChar LineNumber="' + lineCharPos.line + '" CharNumber="' + lineCharPos.character + '" />');
}
public moveCaretRight(count = 1) {
this.currentCaretPosition += count;
this.currentCaretPosition = Math.min(this.currentCaretPosition, this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getLength());
this.currentCaretPosition = Math.min(this.currentCaretPosition, this.getFileContent(this.activeFile.fileName).length);
if (count > 0) {
this.scenarioActions.push('<MoveCaretRight NumberOfChars="' + count + '" />');
} else {
@@ -453,7 +460,7 @@ module FourSlash {
private getAllDiagnostics(): ts.Diagnostic[] {
var diagnostics: ts.Diagnostic[] = [];
var fileNames = JSON.parse(this.languageServiceShimHost.getScriptFileNames());
var fileNames = this.languageServiceAdapterHost.getFilenames();
for (var i = 0, n = fileNames.length; i < n; i++) {
diagnostics.push.apply(this.getDiagnostics(fileNames[i]));
}
@@ -805,7 +812,6 @@ module FourSlash {
}
}
public verifyQuickInfoDisplayParts(kind: string, kindModifiers: string, textSpan: { start: number; length: number; },
displayParts: ts.SymbolDisplayPart[],
documentation: ts.SymbolDisplayPart[]) {
@@ -1211,8 +1217,7 @@ module FourSlash {
var file = this.testData.files[i];
var active = (this.activeFile === file);
Harness.IO.log('=== Script (' + file.fileName + ') ' + (active ? '(active, cursor at |)' : '') + ' ===');
var snapshot = this.languageServiceShimHost.getScriptSnapshot(file.fileName);
var content = snapshot.getText(0, snapshot.getLength());
var content = this.getFileContent(file.fileName);
if (active) {
content = content.substr(0, this.currentCaretPosition) + (makeCaretVisible ? '|' : "") + content.substr(this.currentCaretPosition);
}
@@ -1246,8 +1251,7 @@ module FourSlash {
}
public printContext() {
var fileNames: string[] = JSON.parse(this.languageServiceShimHost.getScriptFileNames());
ts.forEach(fileNames, Harness.IO.log);
ts.forEach(this.languageServiceAdapterHost.getFilenames(), Harness.IO.log);
}
public deleteChar(count = 1) {
@@ -1260,7 +1264,7 @@ module FourSlash {
for (var i = 0; i < count; i++) {
// Make the edit
this.languageServiceShimHost.editScript(this.activeFile.fileName, offset, offset + 1, ch);
this.languageServiceAdapterHost.editScript(this.activeFile.fileName, offset, offset + 1, ch);
this.updateMarkersForEdit(this.activeFile.fileName, offset, offset + 1, ch);
if (i % checkCadence === 0) {
@@ -1287,7 +1291,7 @@ module FourSlash {
public replace(start: number, length: number, text: string) {
this.taoInvalidReason = 'replace NYI';
this.languageServiceShimHost.editScript(this.activeFile.fileName, start, start + length, text);
this.languageServiceAdapterHost.editScript(this.activeFile.fileName, start, start + length, text);
this.updateMarkersForEdit(this.activeFile.fileName, start, start + length, text);
this.checkPostEditInvariants();
}
@@ -1302,7 +1306,7 @@ module FourSlash {
for (var i = 0; i < count; i++) {
offset--;
// Make the edit
this.languageServiceShimHost.editScript(this.activeFile.fileName, offset, offset + 1, ch);
this.languageServiceAdapterHost.editScript(this.activeFile.fileName, offset, offset + 1, ch);
this.updateMarkersForEdit(this.activeFile.fileName, offset, offset + 1, ch);
if (i % checkCadence === 0) {
@@ -1347,7 +1351,7 @@ module FourSlash {
for (var i = 0; i < text.length; i++) {
// Make the edit
var ch = text.charAt(i);
this.languageServiceShimHost.editScript(this.activeFile.fileName, offset, offset, ch);
this.languageServiceAdapterHost.editScript(this.activeFile.fileName, offset, offset, ch);
this.languageService.getBraceMatchingAtPosition(this.activeFile.fileName, offset);
this.updateMarkersForEdit(this.activeFile.fileName, offset, offset, ch);
@@ -1390,7 +1394,7 @@ module FourSlash {
var start = this.currentCaretPosition;
var offset = this.currentCaretPosition;
this.languageServiceShimHost.editScript(this.activeFile.fileName, offset, offset, text);
this.languageServiceAdapterHost.editScript(this.activeFile.fileName, offset, offset, text);
this.updateMarkersForEdit(this.activeFile.fileName, offset, offset, text);
this.checkPostEditInvariants();
offset += text.length;
@@ -1412,14 +1416,19 @@ module FourSlash {
}
private checkPostEditInvariants() {
if (this.testType !== FourSlashTestType.Native) {
// getSourcefile() results can not be serialized. Only perform these verifications
// if running against a native LS object.
return;
}
var incrementalSourceFile = this.languageService.getSourceFile(this.activeFile.fileName);
Utils.assertInvariants(incrementalSourceFile, /*parent:*/ undefined);
var incrementalSyntaxDiagnostics = incrementalSourceFile.parseDiagnostics;
// Check syntactic structure
var snapshot = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName);
var content = snapshot.getText(0, snapshot.getLength());
var content = this.getFileContent(this.activeFile.fileName);
var referenceSourceFile = ts.createLanguageServiceSourceFile(
this.activeFile.fileName, createScriptSnapShot(content), ts.ScriptTarget.Latest, /*version:*/ "0", /*setNodeParents:*/ false);
@@ -1433,7 +1442,7 @@ module FourSlash {
// The caret can potentially end up between the \r and \n, which is confusing. If
// that happens, move it back one character
if (this.currentCaretPosition > 0) {
var ch = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(this.currentCaretPosition - 1, this.currentCaretPosition);
var ch = this.getFileContent(this.activeFile.fileName).substring(this.currentCaretPosition - 1, this.currentCaretPosition);
if (ch === '\r') {
this.currentCaretPosition--;
}
@@ -1446,10 +1455,9 @@ module FourSlash {
var runningOffset = 0;
edits = edits.sort((a, b) => a.span.start - b.span.start);
// Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters
var snapshot = this.languageServiceShimHost.getScriptSnapshot(fileName);
var oldContent = snapshot.getText(0, snapshot.getLength());
var oldContent = this.getFileContent(this.activeFile.fileName);
for (var j = 0; j < edits.length; j++) {
this.languageServiceShimHost.editScript(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText);
this.languageServiceAdapterHost.editScript(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText);
this.updateMarkersForEdit(fileName, edits[j].span.start + runningOffset, ts.textSpanEnd(edits[j].span) + runningOffset, edits[j].newText);
var change = (edits[j].span.start - ts.textSpanEnd(edits[j].span)) + edits[j].newText.length;
runningOffset += change;
@@ -1458,8 +1466,7 @@ module FourSlash {
}
if (isFormattingEdit) {
snapshot = this.languageServiceShimHost.getScriptSnapshot(fileName);
var newContent = snapshot.getText(0, snapshot.getLength());
var newContent = this.getFileContent(fileName);
if (newContent.replace(/\s/g, '') !== oldContent.replace(/\s/g, '')) {
this.raiseError('Formatting operation destroyed non-whitespace content');
@@ -1506,7 +1513,7 @@ module FourSlash {
}
public goToEOF() {
var len = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getLength();
var len = this.getFileContent(this.activeFile.fileName).length;
this.goToPosition(len);
}
@@ -1627,7 +1634,7 @@ module FourSlash {
public verifyCurrentFileContent(text: string) {
this.taoInvalidReason = 'verifyCurrentFileContent NYI';
var actual = this.getCurrentFileContent();
var actual = this.getFileContent(this.activeFile.fileName);
var replaceNewlines = (str: string) => str.replace(/\r\n/g, "\n");
if (replaceNewlines(actual) !== replaceNewlines(text)) {
throw new Error('verifyCurrentFileContent\n' +
@@ -1639,7 +1646,7 @@ module FourSlash {
public verifyTextAtCaretIs(text: string) {
this.taoInvalidReason = 'verifyCurrentFileContent NYI';
var actual = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(this.currentCaretPosition, this.currentCaretPosition + text.length);
var actual = this.getFileContent(this.activeFile.fileName).substring(this.currentCaretPosition, this.currentCaretPosition + text.length);
if (actual !== text) {
throw new Error('verifyTextAtCaretIs\n' +
'\tExpected: "' + text + '"\n' +
@@ -1657,7 +1664,7 @@ module FourSlash {
'\t Actual: undefined');
}
var actual = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName).getText(span.start, ts.textSpanEnd(span));
var actual = this.getFileContent(this.activeFile.fileName).substring(span.start, ts.textSpanEnd(span));
if (actual !== text) {
this.raiseError('verifyCurrentNameOrDottedNameSpanText\n' +
'\tExpected: "' + text + '"\n' +
@@ -1731,8 +1738,8 @@ module FourSlash {
function jsonMismatchString() {
return ts.sys.newLine +
"expected: '" + ts.sys.newLine + JSON.stringify(expected,(k, v) => v, 2) + "'" + ts.sys.newLine +
"actual: '" + ts.sys.newLine + JSON.stringify(actual,(k, v) => v, 2) + "'";
"expected: '" + ts.sys.newLine + JSON.stringify(expected, (k, v) => v, 2) + "'" + ts.sys.newLine +
"actual: '" + ts.sys.newLine + JSON.stringify(actual, (k, v) => v, 2) + "'";
}
}
@@ -1744,7 +1751,7 @@ module FourSlash {
}
public verifySyntacticClassifications(expected: { classificationType: string; text: string }[]) {
var actual = this.languageService.getSyntacticClassifications(this.activeFile.fileName,
var actual = this.languageService.getSyntacticClassifications(this.activeFile.fileName,
ts.createTextSpan(0, this.activeFile.content.length));
this.verifyClassifications(expected, actual);
@@ -1820,69 +1827,6 @@ module FourSlash {
}
}
public verifyTypesAgainstFullCheckAtPositions(positions: number[]) {
this.taoInvalidReason = 'verifyTypesAgainstFullCheckAtPositions impossible';
// Create a from-scratch LS to check against
var referenceLanguageServiceShimHost = new Harness.LanguageService.TypeScriptLS();
var referenceLanguageServiceShim = referenceLanguageServiceShimHost.getLanguageService();
var referenceLanguageService = referenceLanguageServiceShim.languageService;
// Add lib.d.ts to the reference language service
referenceLanguageServiceShimHost.addDefaultLibrary();
for (var i = 0; i < this.testData.files.length; i++) {
var file = this.testData.files[i];
var snapshot = this.languageServiceShimHost.getScriptSnapshot(file.fileName);
var content = snapshot.getText(0, snapshot.getLength());
referenceLanguageServiceShimHost.addScript(this.testData.files[i].fileName, content);
}
for (i = 0; i < positions.length; i++) {
var nameOf = (type: ts.QuickInfo) => type ? ts.displayPartsToString(type.displayParts) : '(none)';
var pullName: string, refName: string;
var anyFailed = false;
var errMsg = '';
try {
var pullType = this.languageService.getQuickInfoAtPosition(this.activeFile.fileName, positions[i]);
pullName = nameOf(pullType);
} catch (err1) {
errMsg = 'Failed to get pull type check. Exception: ' + err1 + '\r\n';
if (err1.stack) errMsg = errMsg + err1.stack;
pullName = '(failed)';
anyFailed = true;
}
try {
var referenceType = referenceLanguageService.getQuickInfoAtPosition(this.activeFile.fileName, positions[i]);
refName = nameOf(referenceType);
} catch (err2) {
errMsg = 'Failed to get full type check. Exception: ' + err2 + '\r\n';
if (err2.stack) errMsg = errMsg + err2.stack;
refName = '(failed)';
anyFailed = true;
}
var failure = anyFailed || (refName !== pullName);
if (failure) {
snapshot = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName);
content = snapshot.getText(0, snapshot.getLength());
var textAtPosition = content.substr(positions[i], 10);
var positionDescription = 'Position ' + positions[i] + ' ("' + textAtPosition + '"...)';
if (anyFailed) {
throw new Error('Exception thrown in language service for ' + positionDescription + '\r\n' + errMsg);
} else if (refName !== pullName) {
throw new Error('Pull/Full disagreement failed at ' + positionDescription + ' - expected full typecheck type "' + refName + '" to equal pull type "' + pullName + '".');
}
}
}
}
/*
Check number of navigationItems which match both searchValue and matchKind.
Report an error if expected value and actual value do not match.
@@ -2069,12 +2013,11 @@ module FourSlash {
// The current caret position (in line/col terms)
var line = this.getCurrentCaretFilePosition().line;
// The line/col of the start of this line
var pos = this.languageServiceShimHost.lineColToPosition(this.activeFile.fileName, line, 1);
var pos = this.languageServiceAdapterHost.lineColToPosition(this.activeFile.fileName, line, 1);
// The index of the current file
// The text from the start of the line to the end of the file
var snapshot = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName);
var text = snapshot.getText(pos, snapshot.getLength());
var text = this.getFileContent(this.activeFile.fileName).substring(pos);
// Truncate to the first newline
var newlinePos = text.indexOf('\n');
@@ -2089,13 +2032,8 @@ module FourSlash {
}
}
private getCurrentFileContent() {
var snapshot = this.languageServiceShimHost.getScriptSnapshot(this.activeFile.fileName);
return snapshot.getText(0, snapshot.getLength());
}
private getCurrentCaretFilePosition() {
var result = this.languageServiceShimHost.positionToZeroBasedLineCol(this.activeFile.fileName, this.currentCaretPosition);
var result = this.languageServiceAdapterHost.positionToZeroBasedLineCol(this.activeFile.fileName, this.currentCaretPosition);
if (result.line >= 0) {
result.line++;
}
@@ -2153,8 +2091,10 @@ module FourSlash {
}
} else if (typeof indexOrName === 'string') {
var name = <string>indexOrName;
// names are stored in the compiler with this relative path, this allows people to use goTo.file on just the fileName
name = name.indexOf('/') === -1 ? 'tests/cases/fourslash/' + name : name;
name = name.indexOf('/') === -1 ? (this.basePath + '/' + name) : name;
var availableNames: string[] = [];
var foundIt = false;
for (var i = 0; i < this.testData.files.length; i++) {
@@ -2180,7 +2120,7 @@ module FourSlash {
}
private getLineColStringAtPosition(position: number) {
var pos = this.languageServiceShimHost.positionToZeroBasedLineCol(this.activeFile.fileName, position);
var pos = this.languageServiceAdapterHost.positionToZeroBasedLineCol(this.activeFile.fileName, position);
return 'line ' + (pos.line + 1) + ', col ' + pos.character;
}
@@ -2206,23 +2146,31 @@ module FourSlash {
originalName: ''
};
}
public setCancelled(numberOfCalls: number): void {
this.cancellationToken.setCancelled(numberOfCalls)
}
public resetCancelled(): void {
this.cancellationToken.resetCancelled();
}
}
// TOOD: should these just use the Harness's stdout/stderr?
var fsOutput = new Harness.Compiler.WriterAggregator();
var fsErrors = new Harness.Compiler.WriterAggregator();
export var xmlData: TestXmlData[] = [];
export function runFourSlashTest(fileName: string) {
export function runFourSlashTest(basePath: string, testType: FourSlashTestType, fileName: string) {
var content = Harness.IO.readFile(fileName);
var xml = runFourSlashTestContent(content, fileName);
var xml = runFourSlashTestContent(basePath, testType, content, fileName);
xmlData.push(xml);
}
export function runFourSlashTestContent(content: string, fileName: string): TestXmlData {
export function runFourSlashTestContent(basePath: string, testType: FourSlashTestType, content: string, fileName: string): TestXmlData {
// Parse out the files and their metadata
var testData = parseTestData(content, fileName);
var testData = parseTestData(basePath, content, fileName);
currentTestState = new TestState(testData);
currentTestState = new TestState(basePath, testType, testData);
var result = '';
var host = Harness.Compiler.createCompilerHost([{ unitName: Harness.Compiler.fourslashFileName, content: undefined },
@@ -2265,7 +2213,7 @@ module FourSlash {
return lines.map(s => s.substr(1)).join('\n');
}
function parseTestData(contents: string, fileName: string): FourSlashData {
function parseTestData(basePath: string, contents: string, fileName: string): FourSlashData {
// Regex for parsing options in the format "@Alpha: Value of any sort"
var optionRegex = /^\s*@(\w+): (.*)\s*/;
@@ -2333,7 +2281,7 @@ module FourSlash {
currentFileName = fileName;
}
currentFileName = 'tests/cases/fourslash/' + match[2];
currentFileName = basePath + '/' + match[2];
currentFileOptions[match[1]] = match[2];
} else {
// Add other fileMetadata flag
+26 -10
View File
@@ -2,19 +2,35 @@
///<reference path='harness.ts'/>
///<reference path='runnerbase.ts' />
class FourslashRunner extends RunnerBase {
public basePath = 'tests/cases/fourslash';
const enum FourSlashTestType {
Native,
Shims
}
constructor() {
class FourSlashRunner extends RunnerBase {
protected basePath: string;
protected testSuiteName: string;
constructor(private testType: FourSlashTestType) {
super();
switch (testType) {
case FourSlashTestType.Native:
this.basePath = 'tests/cases/fourslash';
this.testSuiteName = 'fourslash';
break;
case FourSlashTestType.Shims:
this.basePath = 'tests/cases/fourslash/shims';
this.testSuiteName = 'fourslash-shims';
break;
}
}
public initializeTests() {
if (this.tests.length === 0) {
this.tests = this.enumerateFiles(this.basePath, /\.ts/i);
this.tests = this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false });
}
describe("fourslash tests", () => {
describe(this.testSuiteName, () => {
this.tests.forEach((fn: string) => {
fn = ts.normalizeSlashes(fn);
var justName = fn.replace(/^.*[\\\/]/, '');
@@ -24,8 +40,8 @@ class FourslashRunner extends RunnerBase {
if (testIndex >= 0) fn = fn.substr(testIndex);
if (justName && !justName.match(/fourslash\.ts$/i) && !justName.match(/\.d\.ts$/i)) {
it('FourSlash test ' + justName + ' runs correctly', function () {
FourSlash.runFourSlashTest(fn);
it(this.testSuiteName + ' test ' + justName + ' runs correctly',() => {
FourSlash.runFourSlashTest(this.basePath, this.testType, fn);
});
}
});
@@ -82,9 +98,9 @@ class FourslashRunner extends RunnerBase {
}
}
class GeneratedFourslashRunner extends FourslashRunner {
constructor() {
super();
class GeneratedFourslashRunner extends FourSlashRunner {
constructor(testType: FourSlashTestType) {
super(testType);
this.basePath += '/generated/';
}
}
+12 -7
View File
@@ -19,6 +19,7 @@
/// <reference path='external\mocha.d.ts'/>
/// <reference path='external\chai.d.ts'/>
/// <reference path='sourceMapRecorder.ts'/>
/// <reference path='runnerbase.ts'/>
declare var require: any;
declare var process: any;
@@ -691,8 +692,6 @@ module Harness {
}
}
module Harness {
var tcServicesFileName = "typescriptServices.js";
@@ -796,9 +795,15 @@ module Harness {
}
}
export function createSourceFileAndAssertInvariants(fileName: string, sourceText: string, languageVersion: ts.ScriptTarget) {
var result = ts.createSourceFile(fileName, sourceText, languageVersion, /*setParentNodes:*/ true);
Utils.assertInvariants(result, /*parent:*/ undefined);
return result;
}
export var defaultLibFileName = 'lib.d.ts';
export var defaultLibSourceFile = ts.createSourceFile(defaultLibFileName, IO.readFile(libFolder + 'lib.core.d.ts'), /*languageVersion*/ ts.ScriptTarget.Latest);
export var defaultES6LibSourceFile = ts.createSourceFile(defaultLibFileName, IO.readFile(libFolder + 'lib.core.es6.d.ts'), /*languageVersion*/ ts.ScriptTarget.Latest);
export var defaultLibSourceFile = createSourceFileAndAssertInvariants(defaultLibFileName, IO.readFile(libFolder + 'lib.core.d.ts'), /*languageVersion*/ ts.ScriptTarget.Latest);
export var defaultES6LibSourceFile = createSourceFileAndAssertInvariants(defaultLibFileName, IO.readFile(libFolder + 'lib.core.es6.d.ts'), /*languageVersion*/ ts.ScriptTarget.Latest);
// Cache these between executions so we don't have to re-parse them for every test
@@ -828,7 +833,7 @@ module Harness {
function register(file: { unitName: string; content: string; }) {
if (file.content !== undefined) {
var fileName = ts.normalizeSlashes(file.unitName);
filemap[getCanonicalFileName(fileName)] = ts.createSourceFile(fileName, file.content, scriptTarget);
filemap[getCanonicalFileName(fileName)] = createSourceFileAndAssertInvariants(fileName, file.content, scriptTarget);
}
};
inputFiles.forEach(register);
@@ -845,7 +850,7 @@ module Harness {
}
else if (fn === fourslashFileName) {
var tsFn = 'tests/cases/fourslash/' + fourslashFileName;
fourslashSourceFile = fourslashSourceFile || ts.createSourceFile(tsFn, Harness.IO.readFile(tsFn), scriptTarget);
fourslashSourceFile = fourslashSourceFile || createSourceFileAndAssertInvariants(tsFn, Harness.IO.readFile(tsFn), scriptTarget);
return fourslashSourceFile;
}
else {
@@ -1069,7 +1074,7 @@ module Harness {
var register = (file: { unitName: string; content: string; }) => {
if (file.content !== undefined) {
var fileName = ts.normalizeSlashes(file.unitName);
filemap[getCanonicalFileName(fileName)] = ts.createSourceFile(fileName, file.content, options.target);
filemap[getCanonicalFileName(fileName)] = createSourceFileAndAssertInvariants(fileName, file.content, options.target);
}
};
inputFiles.forEach(register);
+282 -248
View File
@@ -1,5 +1,6 @@
/// <reference path='..\services\services.ts' />
/// <reference path='..\services\shims.ts' />
/// <reference path='harness.ts' />
module Harness.LanguageService {
export class ScriptInfo {
@@ -54,12 +55,11 @@ module Harness.LanguageService {
}
}
class ScriptSnapshotShim implements ts.ScriptSnapshotShim {
private lineMap: number[] = null;
private textSnapshot: string;
private version: number;
class ScriptSnapshot implements ts.IScriptSnapshot {
public textSnapshot: string;
public version: number;
constructor(private scriptInfo: ScriptInfo) {
constructor(public scriptInfo: ScriptInfo) {
this.textSnapshot = scriptInfo.content;
this.version = scriptInfo.version;
}
@@ -72,9 +72,28 @@ module Harness.LanguageService {
return this.textSnapshot.length;
}
public getChangeRange(oldScript: ts.IScriptSnapshot): ts.TextChangeRange {
var oldShim = <ScriptSnapshot>oldScript;
return this.scriptInfo.getTextChangeRangeBetweenVersions(oldShim.version, this.version);
}
}
class ScriptSnapshotProxy implements ts.ScriptSnapshotShim {
constructor(public scriptSnapshot: ts.IScriptSnapshot) {
}
public getText(start: number, end: number): string {
return this.scriptSnapshot.getText(start, end);
}
public getLength(): number {
return this.scriptSnapshot.getLength();
}
public getChangeRange(oldScript: ts.ScriptSnapshotShim): string {
var oldShim = <ScriptSnapshotShim>oldScript;
var range = this.scriptInfo.getTextChangeRangeBetweenVersions(oldShim.version, this.version);
var oldShim = <ScriptSnapshotProxy>oldScript;
var range = this.scriptSnapshot.getChangeRange(oldShim.scriptSnapshot);
if (range === null) {
return null;
}
@@ -94,75 +113,38 @@ module Harness.LanguageService {
}
}
export class NonCachingDocumentRegistry implements ts.DocumentRegistry {
public static Instance: ts.DocumentRegistry = new NonCachingDocumentRegistry();
public acquireDocument(
fileName: string,
compilationSettings: ts.CompilerOptions,
scriptSnapshot: ts.IScriptSnapshot,
version: string): ts.SourceFile {
var sourceFile = ts.createSourceFile(fileName, scriptSnapshot.getText(0, scriptSnapshot.getLength()), compilationSettings.target);
sourceFile.version = version;
return sourceFile;
}
public updateDocument(
document: ts.SourceFile,
fileName: string,
compilationSettings: ts.CompilerOptions,
scriptSnapshot: ts.IScriptSnapshot,
version: string,
textChangeRange: ts.TextChangeRange
): ts.SourceFile {
return ts.updateLanguageServiceSourceFile(document, scriptSnapshot, version, textChangeRange);
}
public releaseDocument(fileName: string, compilationSettings: ts.CompilerOptions): void {
// no op since this class doesn't cache anything
}
export interface LanguageServiceAdapter {
getHost(): LanguageServiceAdapterHost;
getLanguageService(): ts.LanguageService;
getClassifier(): ts.Classifier;
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo;
}
export class TypeScriptLS implements ts.LanguageServiceShimHost {
private ls: ts.LanguageServiceShim = null;
private fileNameToScript: ts.Map<ScriptInfo> = {};
private settings: ts.CompilerOptions = {};
constructor(private cancellationToken: ts.CancellationToken = CancellationToken.None) {
}
public trace(s: string) {
export class LanguageServiceAdapterHost {
protected fileNameToScript: ts.Map<ScriptInfo> = {};
constructor(protected cancellationToken: ts.CancellationToken = CancellationToken.None,
protected settings = ts.getDefaultCompilerOptions()) {
}
public getNewLine(): string {
return "\r\n";
}
public addDefaultLibrary() {
this.addScript(Harness.Compiler.defaultLibFileName, Harness.Compiler.defaultLibSourceFile.text);
public getFilenames(): string[] {
var fileNames: string[] = [];
ts.forEachKey(this.fileNameToScript,(fileName) => { fileNames.push(fileName); });
return fileNames;
}
public getHostIdentifier(): string {
return "TypeScriptLS";
}
public addFile(fileName: string) {
var code = Harness.IO.readFile(fileName);
this.addScript(fileName, code);
}
private getScriptInfo(fileName: string): ScriptInfo {
public getScriptInfo(fileName: string): ScriptInfo {
return ts.lookUp(this.fileNameToScript, fileName);
}
public addScript(fileName: string, content: string) {
public addScript(fileName: string, content: string): void {
this.fileNameToScript[fileName] = new ScriptInfo(fileName, content);
}
private contains(fileName: string): boolean {
return ts.hasProperty(this.fileNameToScript, fileName);
}
public updateScript(fileName: string, content: string) {
var script = this.getScriptInfo(fileName);
if (script !== null) {
@@ -183,107 +165,10 @@ module Harness.LanguageService {
throw new Error("No script with name '" + fileName + "'");
}
//////////////////////////////////////////////////////////////////////
// ILogger implementation
//
public information(): boolean { return false; }
public debug(): boolean { return true; }
public warning(): boolean { return true; }
public error(): boolean { return true; }
public fatal(): boolean { return true; }
public log(s: string): void {
// For debugging...
//TypeScript.Environment.standardOut.WriteLine("TypeScriptLS:" + s);
}
//////////////////////////////////////////////////////////////////////
// LanguageServiceShimHost implementation
//
/// Returns json for Tools.CompilationSettings
public getCompilationSettings(): string {
return JSON.stringify(this.settings);
}
public getCancellationToken(): ts.CancellationToken {
return this.cancellationToken;
}
public getCurrentDirectory(): string {
return "";
}
public getDefaultLibFileName(): string {
return "";
}
public getScriptFileNames(): string {
var fileNames: string[] = [];
ts.forEachKey(this.fileNameToScript,(fileName) => { fileNames.push(fileName); });
return JSON.stringify(fileNames);
}
public getScriptSnapshot(fileName: string): ts.ScriptSnapshotShim {
if (this.contains(fileName)) {
return new ScriptSnapshotShim(this.getScriptInfo(fileName));
}
return undefined;
}
public getScriptVersion(fileName: string): string {
if (this.contains(fileName)) {
return this.getScriptInfo(fileName).version.toString();
}
return undefined;
}
public getLocalizedDiagnosticMessages(): string {
return JSON.stringify({});
}
/** Return a new instance of the language service shim, up-to-date wrt to typecheck.
* To access the non-shim (i.e. actual) language service, use the "ls.languageService" property.
*/
public getLanguageService(): ts.LanguageServiceShim {
this.ls = new TypeScript.Services.TypeScriptServicesFactory().createLanguageServiceShim(this);
return this.ls;
}
public setCompilationSettings(settings: ts.CompilerOptions) {
for (var key in settings) {
if (settings.hasOwnProperty(key)) {
this.settings[key] = settings[key];
}
}
}
/** Return a new instance of the classifier service shim */
public getClassifier(): ts.ClassifierShim {
return new TypeScript.Services.TypeScriptServicesFactory().createClassifierShim(this);
}
public getCoreService(): ts.CoreServicesShim {
return new TypeScript.Services.TypeScriptServicesFactory().createCoreServicesShim(this);
}
/** Parse file given its source text */
public parseSourceText(fileName: string, sourceText: ts.IScriptSnapshot): ts.SourceFile {
var result = ts.createSourceFile(fileName, sourceText.getText(0, sourceText.getLength()), ts.ScriptTarget.Latest);
result.version = "1";
return result;
}
/** Parse a file on disk given its fileName */
public parseFile(fileName: string) {
var sourceText = ts.ScriptSnapshot.fromString(Harness.IO.readFile(fileName));
return this.parseSourceText(fileName, sourceText);
}
/**
* @param line 1 based index
* @param col 1 based index
*/
* @param line 1 based index
* @param col 1 based index
*/
public lineColToPosition(fileName: string, line: number, col: number): number {
var script: ScriptInfo = this.fileNameToScript[fileName];
assert.isNotNull(script);
@@ -294,9 +179,9 @@ module Harness.LanguageService {
}
/**
* @param line 0 based index
* @param col 0 based index
*/
* @param line 0 based index
* @param col 0 based index
*/
public positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter {
var script: ScriptInfo = this.fileNameToScript[fileName];
assert.isNotNull(script);
@@ -307,105 +192,254 @@ module Harness.LanguageService {
assert.isTrue(result.character >= 1);
return { line: result.line - 1, character: result.character - 1 };
}
}
/** Verify that applying edits to sourceFileName result in the content of the file baselineFileName */
public checkEdits(sourceFileName: string, baselineFileName: string, edits: ts.TextChange[]) {
var script = Harness.IO.readFile(sourceFileName);
var formattedScript = this.applyEdits(script, edits);
var baseline = Harness.IO.readFile(baselineFileName);
/// Native adapter
class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost {
getCompilationSettings(): ts.CompilerOptions { return this.settings; }
getCancellationToken(): ts.CancellationToken { return this.cancellationToken; }
getCurrentDirectory(): string { return ""; }
getDefaultLibFileName(): string { return ""; }
getScriptFileNames(): string[] { return this.getFilenames(); }
getScriptSnapshot(fileName: string): ts.IScriptSnapshot {
var script = this.getScriptInfo(fileName);
return script ? new ScriptSnapshot(script) : undefined;
}
getScriptVersion(fileName: string): string {
var script = this.getScriptInfo(fileName);
return script ? script.version.toString() : undefined;
}
log(s: string): void { }
trace(s: string): void { }
error(s: string): void { }
}
function noDiff(text1: string, text2: string) {
text1 = text1.replace(/^\s+|\s+$/g, "").replace(/\r\n?/g, "\n");
text2 = text2.replace(/^\s+|\s+$/g, "").replace(/\r\n?/g, "\n");
export class NativeLanugageServiceAdapter implements LanguageServiceAdapter {
private host: NativeLanguageServiceHost;
constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) {
this.host = new NativeLanguageServiceHost(cancellationToken, options);
}
getHost() { return this.host; }
getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); }
getClassifier(): ts.Classifier { return ts.createClassifier(); }
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents); }
}
if (text1 !== text2) {
var errorString = "";
var text1Lines = text1.split(/\n/);
var text2Lines = text2.split(/\n/);
for (var i = 0; i < text1Lines.length; i++) {
if (text1Lines[i] !== text2Lines[i]) {
errorString += "Difference at line " + (i + 1) + ":\n";
errorString += " Left File: " + text1Lines[i] + "\n";
errorString += " Right File: " + text2Lines[i] + "\n\n";
}
}
throw (new Error(errorString));
}
}
assert.isTrue(noDiff(formattedScript, baseline));
assert.equal(formattedScript, baseline);
/// Shim adapter
class ShimLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceShimHost {
private nativeHost: NativeLanguageServiceHost;
constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) {
super(cancellationToken, options);
this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options);
}
getFilenames(): string[] { return this.nativeHost.getFilenames(); }
getScriptInfo(fileName: string): ScriptInfo { return this.nativeHost.getScriptInfo(fileName); }
addScript(fileName: string, content: string): void { this.nativeHost.addScript(fileName, content); }
updateScript(fileName: string, content: string): void { return this.nativeHost.updateScript(fileName, content); }
editScript(fileName: string, minChar: number, limChar: number, newText: string): void { this.nativeHost.editScript(fileName, minChar, limChar, newText); }
lineColToPosition(fileName: string, line: number, col: number): number { return this.nativeHost.lineColToPosition(fileName, line, col); }
positionToZeroBasedLineCol(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToZeroBasedLineCol(fileName, position); }
/** Apply an array of text edits to a string, and return the resulting string. */
public applyEdits(content: string, edits: ts.TextChange[]): string {
var result = content;
edits = this.normalizeEdits(edits);
for (var i = edits.length - 1; i >= 0; i--) {
var edit = edits[i];
var prefix = result.substring(0, edit.span.start);
var middle = edit.newText;
var suffix = result.substring(ts.textSpanEnd(edit.span));
result = prefix + middle + suffix;
}
return result;
getCompilationSettings(): string { return JSON.stringify(this.nativeHost.getCompilationSettings()); }
getCancellationToken(): ts.CancellationToken { return this.nativeHost.getCancellationToken(); }
getCurrentDirectory(): string { return this.nativeHost.getCurrentDirectory(); }
getDefaultLibFileName(): string { return this.nativeHost.getDefaultLibFileName(); }
getScriptFileNames(): string { return JSON.stringify(this.nativeHost.getScriptFileNames()); }
getScriptSnapshot(fileName: string): ts.ScriptSnapshotShim {
var nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(fileName);
return nativeScriptSnapshot && new ScriptSnapshotProxy(nativeScriptSnapshot);
}
getScriptVersion(fileName: string): string { return this.nativeHost.getScriptVersion(fileName); }
getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); }
log(s: string): void { this.nativeHost.log(s); }
trace(s: string): void { this.nativeHost.trace(s); }
error(s: string): void { this.nativeHost.error(s); }
}
/** Normalize an array of edits by removing overlapping entries and sorting entries on the minChar position. */
private normalizeEdits(edits: ts.TextChange[]): ts.TextChange[] {
var result: ts.TextChange[] = [];
class ClassifierShimProxy implements ts.Classifier {
constructor(private shim: ts.ClassifierShim) {
}
getClassificationsForLine(text: string, lexState: ts.EndOfLineState, classifyKeywordsInGenerics?: boolean): ts.ClassificationResult {
var result = this.shim.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics).split('\n');
var entries: ts.ClassificationInfo[] = [];
var i = 0;
var position = 0;
function mapEdits(edits: ts.TextChange[]): { edit: ts.TextChange; index: number; }[] {
var result: { edit: ts.TextChange; index: number; }[] = [];
for (var i = 0; i < edits.length; i++) {
result.push({ edit: edits[i], index: i });
}
return result;
for (; i < result.length - 1; i += 2) {
var t = entries[i / 2] = {
length: parseInt(result[i]),
classification: parseInt(result[i + 1])
};
assert.isTrue(t.length > 0, "Result length should be greater than 0, got :" + t.length);
position += t.length;
}
var finalLexState = parseInt(result[result.length - 1]);
var temp = mapEdits(edits).sort(function (a, b) {
var result = a.edit.span.start - b.edit.span.start;
if (result === 0)
result = a.index - b.index;
return result;
assert.equal(position, text.length, "Expected cumulative length of all entries to match the length of the source. expected: " + text.length + ", but got: " + position);
return {
finalLexState,
entries
};
}
}
function unwrapJSONCallResult(result: string): any {
var parsedResult = JSON.parse(result);
if (parsedResult.error) {
throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error));
}
else if (parsedResult.canceled) {
throw new ts.OperationCanceledException();
}
return parsedResult.result;
}
class LanguageServiceShimProxy implements ts.LanguageService {
constructor(private shim: ts.LanguageServiceShim) {
}
private unwrappJSONCallResult(result: string): any {
var parsedResult = JSON.parse(result);
if (parsedResult.error) {
throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error));
}
return parsedResult.result;
}
cleanupSemanticCache(): void {
this.shim.cleanupSemanticCache();
}
getSyntacticDiagnostics(fileName: string): ts.Diagnostic[] {
return unwrapJSONCallResult(this.shim.getSyntacticDiagnostics(fileName));
}
getSemanticDiagnostics(fileName: string): ts.Diagnostic[] {
return unwrapJSONCallResult(this.shim.getSemanticDiagnostics(fileName));
}
getCompilerOptionsDiagnostics(): ts.Diagnostic[] {
return unwrapJSONCallResult(this.shim.getCompilerOptionsDiagnostics());
}
getSyntacticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] {
return unwrapJSONCallResult(this.shim.getSyntacticClassifications(fileName, span.start, span.length));
}
getSemanticClassifications(fileName: string, span: ts.TextSpan): ts.ClassifiedSpan[] {
return unwrapJSONCallResult(this.shim.getSemanticClassifications(fileName, span.start, span.length));
}
getCompletionsAtPosition(fileName: string, position: number): ts.CompletionInfo {
return unwrapJSONCallResult(this.shim.getCompletionsAtPosition(fileName, position));
}
getCompletionEntryDetails(fileName: string, position: number, entryName: string): ts.CompletionEntryDetails {
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName));
}
getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo {
return unwrapJSONCallResult(this.shim.getQuickInfoAtPosition(fileName, position));
}
getNameOrDottedNameSpan(fileName: string, startPos: number, endPos: number): ts.TextSpan {
return unwrapJSONCallResult(this.shim.getNameOrDottedNameSpan(fileName, startPos, endPos));
}
getBreakpointStatementAtPosition(fileName: string, position: number): ts.TextSpan {
return unwrapJSONCallResult(this.shim.getBreakpointStatementAtPosition(fileName, position));
}
getSignatureHelpItems(fileName: string, position: number): ts.SignatureHelpItems {
return unwrapJSONCallResult(this.shim.getSignatureHelpItems(fileName, position));
}
getRenameInfo(fileName: string, position: number): ts.RenameInfo {
return unwrapJSONCallResult(this.shim.getRenameInfo(fileName, position));
}
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean): ts.RenameLocation[] {
return unwrapJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments));
}
getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] {
return unwrapJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position));
}
getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
return unwrapJSONCallResult(this.shim.getReferencesAtPosition(fileName, position));
}
getOccurrencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
return unwrapJSONCallResult(this.shim.getOccurrencesAtPosition(fileName, position));
}
getNavigateToItems(searchValue: string): ts.NavigateToItem[] {
return unwrapJSONCallResult(this.shim.getNavigateToItems(searchValue));
}
getNavigationBarItems(fileName: string): ts.NavigationBarItem[] {
return unwrapJSONCallResult(this.shim.getNavigationBarItems(fileName));
}
getOutliningSpans(fileName: string): ts.OutliningSpan[] {
return unwrapJSONCallResult(this.shim.getOutliningSpans(fileName));
}
getTodoComments(fileName: string, descriptors: ts.TodoCommentDescriptor[]): ts.TodoComment[] {
return unwrapJSONCallResult(this.shim.getTodoComments(fileName, JSON.stringify(descriptors)));
}
getBraceMatchingAtPosition(fileName: string, position: number): ts.TextSpan[] {
return unwrapJSONCallResult(this.shim.getBraceMatchingAtPosition(fileName, position));
}
getIndentationAtPosition(fileName: string, position: number, options: ts.EditorOptions): number {
return unwrapJSONCallResult(this.shim.getIndentationAtPosition(fileName, position, JSON.stringify(options)));
}
getFormattingEditsForRange(fileName: string, start: number, end: number, options: ts.FormatCodeOptions): ts.TextChange[] {
return unwrapJSONCallResult(this.shim.getFormattingEditsForRange(fileName, start, end, JSON.stringify(options)));
}
getFormattingEditsForDocument(fileName: string, options: ts.FormatCodeOptions): ts.TextChange[] {
return unwrapJSONCallResult(this.shim.getFormattingEditsForDocument(fileName, JSON.stringify(options)));
}
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: ts.FormatCodeOptions): ts.TextChange[] {
return unwrapJSONCallResult(this.shim.getFormattingEditsAfterKeystroke(fileName, position, key, JSON.stringify(options)));
}
getEmitOutput(fileName: string): ts.EmitOutput {
return unwrapJSONCallResult(this.shim.getEmitOutput(fileName));
}
getProgram(): ts.Program {
throw new Error("Program can not be marshaled across the shim layer.");
}
getSourceFile(fileName: string): ts.SourceFile {
throw new Error("SourceFile can not be marshaled across the shim layer.");
}
dispose(): void { this.shim.dispose({}); }
}
export class ShimLanugageServiceAdapter implements LanguageServiceAdapter {
private host: ShimLanguageServiceHost;
private factory: ts.TypeScriptServicesFactory;
constructor(cancellationToken?: ts.CancellationToken, options?: ts.CompilerOptions) {
this.host = new ShimLanguageServiceHost(cancellationToken, options);
this.factory = new TypeScript.Services.TypeScriptServicesFactory();
}
getHost() { return this.host; }
getLanguageService(): ts.LanguageService { return new LanguageServiceShimProxy(this.factory.createLanguageServiceShim(this.host)); }
getClassifier(): ts.Classifier { return new ClassifierShimProxy(this.factory.createClassifierShim(this.host)); }
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo {
var shimResult: {
referencedFiles: ts.IFileReference[];
importedFiles: ts.IFileReference[];
isLibFile: boolean;
};
var coreServicesShim = this.factory.createCoreServicesShim(this.host);
shimResult = unwrapJSONCallResult(coreServicesShim.getPreProcessedFileInfo(fileName, ts.ScriptSnapshot.fromString(fileContents)));
var convertResult: ts.PreProcessedFileInfo = {
referencedFiles: [],
importedFiles: [],
isLibFile: shimResult.isLibFile
};
ts.forEach(shimResult.referencedFiles, refFile => {
convertResult.referencedFiles.push({
fileName: refFile.path,
pos: refFile.position,
end: refFile.position + refFile.length
});
});
var current = 0;
var next = 1;
while (current < temp.length) {
var currentEdit = temp[current].edit;
ts.forEach(shimResult.importedFiles, importedFile => {
convertResult.importedFiles.push({
fileName: importedFile.path,
pos: importedFile.position,
end: importedFile.position + importedFile.length
});
});
// Last edit
if (next >= temp.length) {
result.push(currentEdit);
current++;
continue;
}
var nextEdit = temp[next].edit;
var gap = nextEdit.span.start - ts.textSpanEnd(currentEdit.span);
// non-overlapping edits
if (gap >= 0) {
result.push(currentEdit);
current = next;
next++;
continue;
}
// overlapping edits: for now, we only support ignoring an next edit
// entirely contained in the current edit.
if (ts.textSpanEnd(currentEdit.span) >= ts.textSpanEnd(nextEdit.span)) {
next++;
continue;
}
else {
throw new Error("Trying to apply overlapping edits");
}
}
return result;
return convertResult;
}
}
}
+1 -1
View File
@@ -172,7 +172,7 @@ class ProjectRunner extends RunnerBase {
else {
var text = getSourceFileText(fileName);
if (text !== undefined) {
sourceFile = ts.createSourceFile(fileName, text, languageVersion);
sourceFile = Harness.Compiler.createSourceFileAndAssertInvariants(fileName, text, languageVersion);
}
}
+7 -3
View File
@@ -61,10 +61,13 @@ if (testConfigFile !== '') {
runners.push(new ProjectRunner());
break;
case 'fourslash':
runners.push(new FourslashRunner());
runners.push(new FourSlashRunner(FourSlashTestType.Native));
break;
case 'fourslash-shims':
runners.push(new FourSlashRunner(FourSlashTestType.Shims));
break;
case 'fourslash-generated':
runners.push(new GeneratedFourslashRunner());
runners.push(new GeneratedFourslashRunner(FourSlashTestType.Native));
break;
case 'rwc':
runners.push(new RWCRunner());
@@ -90,7 +93,8 @@ if (runners.length === 0) {
}
// language services
runners.push(new FourslashRunner());
runners.push(new FourSlashRunner(FourSlashTestType.Native));
runners.push(new FourSlashRunner(FourSlashTestType.Shims));
//runners.push(new GeneratedFourslashRunner());
}
+8 -24
View File
@@ -1624,31 +1624,14 @@ module ts {
export var disableIncrementalParsing = false;
export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange): SourceFile {
if (textChangeRange && Debug.shouldAssert(AssertionLevel.Normal)) {
var oldText = sourceFile.scriptSnapshot;
var newText = scriptSnapshot;
Debug.assert((oldText.getLength() - textChangeRange.span.length + textChangeRange.newLength) === newText.getLength());
if (Debug.shouldAssert(AssertionLevel.VeryAggressive)) {
var oldTextPrefix = oldText.getText(0, textChangeRange.span.start);
var newTextPrefix = newText.getText(0, textChangeRange.span.start);
Debug.assert(oldTextPrefix === newTextPrefix);
var oldTextSuffix = oldText.getText(textSpanEnd(textChangeRange.span), oldText.getLength());
var newTextSuffix = newText.getText(textSpanEnd(textChangeRangeNewSpan(textChangeRange)), newText.getLength());
Debug.assert(oldTextSuffix === newTextSuffix);
}
}
export function updateLanguageServiceSourceFile(sourceFile: SourceFile, scriptSnapshot: IScriptSnapshot, version: string, textChangeRange: TextChangeRange, aggressiveChecks?: boolean): SourceFile {
// If we were given a text change range, and our version or open-ness changed, then
// incrementally parse this file.
if (textChangeRange) {
if (version !== sourceFile.version) {
// Once incremental parsing is ready, then just call into this function.
if (!disableIncrementalParsing) {
var newSourceFile = updateSourceFile(sourceFile, scriptSnapshot.getText(0, scriptSnapshot.getLength()), textChangeRange);
var newSourceFile = updateSourceFile(sourceFile, scriptSnapshot.getText(0, scriptSnapshot.getLength()), textChangeRange, aggressiveChecks);
setSourceFileFields(newSourceFile, scriptSnapshot, version);
// after incremental parsing nameTable might not be up-to-date
// drop it so it can be lazily recreated later
@@ -2039,6 +2022,12 @@ module ts {
return;
}
// IMPORTANT - It is critical from this moment onward that we do not check
// cancellation tokens. We are about to mutate source files from a previous program
// instance. If we cancel midway through, we may end up in an inconsistent state where
// the program points to old source files that have been invalidated because of
// incremental parsing.
var oldSettings = program && program.getCompilerOptions();
var newSettings = hostCache.compilationSettings();
var changesInCompilationSettingsAffectSyntax = oldSettings && oldSettings.target !== newSettings.target;
@@ -2073,8 +2062,6 @@ module ts {
return;
function getOrCreateSourceFile(fileName: string): SourceFile {
cancellationToken.throwIfCancellationRequested();
// The program is asking for this file, check first if the host can locate it.
// If the host can not locate the file, then it does not exist. return undefined
// to the program to allow reporting of errors for missing files.
@@ -5380,9 +5367,6 @@ module ts {
cancellationToken.throwIfCancellationRequested();
var fileContents = sourceFile.text;
cancellationToken.throwIfCancellationRequested();
var result: TodoComment[] = [];
if (descriptors.length > 0) {
+1
View File
@@ -90,6 +90,7 @@ module ts {
getCompilerOptionsDiagnostics(): string;
getSyntacticClassifications(fileName: string, start: number, length: number): string;
getSemanticClassifications(fileName: string, start: number, length: number): string;
getCompletionsAtPosition(fileName: string, position: number): string;
getCompletionEntryDetails(fileName: string, position: number, entryName: string): string;