Merge remote-tracking branch 'upstream/master' into async-es2018

This commit is contained in:
Kagami Sascha Rosylight
2018-09-06 09:55:41 +09:00
1042 changed files with 126058 additions and 95996 deletions
+1
View File
@@ -2,6 +2,7 @@
"extends": "../tsconfig-base",
"compilerOptions": {
"outDir": "../../built/local/",
"rootDir": ".",
"composite": false,
"declaration": false,
"declarationMap": false,
+33 -29
View File
@@ -236,8 +236,9 @@ namespace ts {
if (symbolFlags & SymbolFlags.Value) {
const { valueDeclaration } = symbol;
if (!valueDeclaration ||
(isAssignmentDeclaration(valueDeclaration) && !isAssignmentDeclaration(node)) ||
(valueDeclaration.kind !== node.kind && isEffectiveModuleDeclaration(valueDeclaration))) {
// other kinds of value declarations take precedence over modules
// other kinds of value declarations take precedence over modules and assignment declarations
symbol.valueDeclaration = node;
}
}
@@ -259,7 +260,7 @@ namespace ts {
if (name.kind === SyntaxKind.ComputedPropertyName) {
const nameExpression = name.expression;
// treat computed property names where expression is string/numeric literal as just string/numeric literal
if (isStringOrNumericLiteral(nameExpression)) {
if (isStringOrNumericLiteralLike(nameExpression)) {
return escapeLeadingUnderscores(nameExpression.text);
}
@@ -373,7 +374,8 @@ namespace ts {
// prototype symbols like methods.
symbolTable.set(name, symbol = createSymbol(SymbolFlags.None, name));
}
else {
else if (!(includes & SymbolFlags.Variable && symbol.flags & SymbolFlags.JSContainer)) {
// JSContainers are allowed to merge with variables, no matter what other flags they have.
if (isNamedDeclaration(node)) {
node.name.parent = node;
}
@@ -723,6 +725,7 @@ namespace ts {
case SyntaxKind.Identifier:
case SyntaxKind.ThisKeyword:
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
return isNarrowableReference(expr);
case SyntaxKind.CallExpression:
return hasNarrowableArgument(<CallExpression>expr);
@@ -737,10 +740,11 @@ namespace ts {
}
function isNarrowableReference(expr: Expression): boolean {
return expr.kind === SyntaxKind.Identifier ||
expr.kind === SyntaxKind.ThisKeyword ||
expr.kind === SyntaxKind.SuperKeyword ||
expr.kind === SyntaxKind.PropertyAccessExpression && isNarrowableReference((<PropertyAccessExpression>expr).expression);
return expr.kind === SyntaxKind.Identifier || expr.kind === SyntaxKind.ThisKeyword || expr.kind === SyntaxKind.SuperKeyword ||
isPropertyAccessExpression(expr) && isNarrowableReference(expr.expression) ||
isElementAccessExpression(expr) && expr.argumentExpression &&
(isStringLiteral(expr.argumentExpression) || isNumericLiteral(expr.argumentExpression)) &&
isNarrowableReference(expr.expression);
}
function hasNarrowableArgument(expr: CallExpression) {
@@ -1727,10 +1731,6 @@ namespace ts {
}
}
function bindBlockScopedVariableDeclaration(node: Declaration) {
bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable, SymbolFlags.BlockScopedVariableExcludes);
}
function delayedBindJSDocTypedefTag() {
if (!delayedTypeAliases) {
return;
@@ -2066,6 +2066,7 @@ namespace ts {
}
return checkStrictModeIdentifier(<Identifier>node);
case SyntaxKind.PropertyAccessExpression:
case SyntaxKind.ElementAccessExpression:
if (currentFlow && isNarrowableReference(<Expression>node)) {
node.flowNode = currentFlow;
}
@@ -2075,8 +2076,8 @@ namespace ts {
if (isInJavaScriptFile(node) &&
file.commonJsModuleIndicator &&
isModuleExportsPropertyAccessExpression(node as PropertyAccessExpression) &&
!lookupSymbolForNameWorker(container, "module" as __String)) {
declareSymbol(container.locals!, /*parent*/ undefined, (node as PropertyAccessExpression).expression as Identifier,
!lookupSymbolForNameWorker(blockScopeContainer, "module" as __String)) {
declareSymbol(file.locals!, /*parent*/ undefined, (node as PropertyAccessExpression).expression as Identifier,
SymbolFlags.FunctionScopedVariable | SymbolFlags.ModuleExports, SymbolFlags.FunctionScopedVariableExcludes);
}
break;
@@ -2477,6 +2478,11 @@ namespace ts {
function bindSpecialPropertyAssignment(node: BinaryExpression) {
const lhs = node.left as PropertyAccessEntityNameExpression;
// Class declarations in Typescript do not allow property declarations
const parentSymbol = lookupSymbolForPropertyAccess(lhs.expression);
if (!isInJavaScriptFile(node) && !isFunctionSymbol(parentSymbol)) {
return;
}
// Fix up parent pointers since we're going to use these nodes before we bind into them
node.left.parent = node;
node.right.parent = node;
@@ -2502,11 +2508,10 @@ namespace ts {
function bindPropertyAssignment(name: EntityNameExpression, propertyAccess: PropertyAccessEntityNameExpression, isPrototypeProperty: boolean) {
let namespaceSymbol = lookupSymbolForPropertyAccess(name);
const isToplevelNamespaceableInitializer = isBinaryExpression(propertyAccess.parent)
? getParentOfBinaryExpression(propertyAccess.parent).parent.kind === SyntaxKind.SourceFile &&
!!getJavascriptInitializer(getInitializerOfBinaryExpression(propertyAccess.parent), isPrototypeAccess(propertyAccess.parent.left))
const isToplevel = isBinaryExpression(propertyAccess.parent)
? getParentOfBinaryExpression(propertyAccess.parent).parent.kind === SyntaxKind.SourceFile
: propertyAccess.parent.parent.kind === SyntaxKind.SourceFile;
if (!isPrototypeProperty && (!namespaceSymbol || !(namespaceSymbol.flags & SymbolFlags.Namespace)) && isToplevelNamespaceableInitializer) {
if (!isPrototypeProperty && (!namespaceSymbol || !(namespaceSymbol.flags & SymbolFlags.Namespace)) && isToplevel) {
// make symbols or add declarations for intermediate containers
const flags = SymbolFlags.Module | SymbolFlags.JSContainer;
const excludeFlags = SymbolFlags.ValueModuleExcludes & ~SymbolFlags.JSContainer;
@@ -2529,12 +2534,10 @@ namespace ts {
(namespaceSymbol.members || (namespaceSymbol.members = createSymbolTable())) :
(namespaceSymbol.exports || (namespaceSymbol.exports = createSymbolTable()));
// Declare the method/property
const jsContainerFlag = isToplevelNamespaceableInitializer ? SymbolFlags.JSContainer : 0;
const isMethod = isFunctionLikeDeclaration(getAssignedJavascriptInitializer(propertyAccess)!);
const symbolFlags = (isMethod ? SymbolFlags.Method : SymbolFlags.Property) | jsContainerFlag;
const symbolExcludes = (isMethod ? SymbolFlags.MethodExcludes : SymbolFlags.PropertyExcludes) & ~jsContainerFlag;
declareSymbol(symbolTable, namespaceSymbol, propertyAccess, symbolFlags, symbolExcludes);
const includes = isMethod ? SymbolFlags.Method : SymbolFlags.Property;
const excludes = isMethod ? SymbolFlags.MethodExcludes : SymbolFlags.PropertyExcludes;
declareSymbol(symbolTable, namespaceSymbol, propertyAccess, includes | SymbolFlags.JSContainer, excludes & ~SymbolFlags.JSContainer);
}
/**
@@ -2565,7 +2568,7 @@ namespace ts {
return false;
}
function getParentOfBinaryExpression(expr: BinaryExpression) {
function getParentOfBinaryExpression(expr: Node) {
while (isBinaryExpression(expr.parent)) {
expr = expr.parent;
}
@@ -2652,8 +2655,11 @@ namespace ts {
}
if (!isBindingPattern(node.name)) {
const isEnum = !!getJSDocEnumTag(node);
const enumFlags = (isEnum ? SymbolFlags.RegularEnum : SymbolFlags.None);
const enumExcludes = (isEnum ? SymbolFlags.RegularEnumExcludes : SymbolFlags.None);
if (isBlockOrCatchScoped(node)) {
bindBlockScopedVariableDeclaration(node);
bindBlockScopedDeclaration(node, SymbolFlags.BlockScopedVariable | enumFlags, SymbolFlags.BlockScopedVariableExcludes | enumExcludes);
}
else if (isParameterDeclaration(node)) {
// It is safe to walk up parent chain to find whether the node is a destructuring parameter declaration
@@ -2668,7 +2674,7 @@ namespace ts {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.ParameterExcludes);
}
else {
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable, SymbolFlags.FunctionScopedVariableExcludes);
declareSymbolAndAddToSymbolTable(node, SymbolFlags.FunctionScopedVariable | enumFlags, SymbolFlags.FunctionScopedVariableExcludes | enumExcludes);
}
}
}
@@ -2796,9 +2802,7 @@ namespace ts {
// report error on class declarations
node.kind === SyntaxKind.ClassDeclaration ||
// report error on instantiated modules or const-enums only modules if preserveConstEnums is set
(node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration(<ModuleDeclaration>node)) ||
// report error on regular enums and const enums if preserveConstEnums is set
(isEnumDeclaration(node) && (!isEnumConst(node) || options.preserveConstEnums));
(node.kind === SyntaxKind.ModuleDeclaration && shouldReportErrorOnModuleDeclaration(<ModuleDeclaration>node));
if (reportError) {
currentFlow = reportedUnreachableFlow;
@@ -2843,7 +2847,7 @@ namespace ts {
// As opposed to a pure declaration like an `interface`
function isExecutableStatement(s: Statement): boolean {
// Don't remove statements that can validly be used before they appear.
return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) &&
return !isFunctionDeclaration(s) && !isPurelyTypeDeclaration(s) && !isEnumDeclaration(s) &&
// `var x;` may declare a variable used above
!(isVariableStatement(s) && !(getCombinedNodeFlags(s) & (NodeFlags.Let | NodeFlags.Const)) && s.declarationList.declarations.some(d => !d.initializer));
}
+2 -1
View File
@@ -65,7 +65,8 @@ namespace ts {
}
state.changedFilesSet = createMap<true>();
const useOldState = BuilderState.canReuseOldState(state.referencedMap, oldState);
const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile;
const canCopySemanticDiagnostics = useOldState && oldState!.semanticDiagnosticsPerFile && !!state.semanticDiagnosticsPerFile &&
!compilerOptionsAffectSemanticDiagnostics(compilerOptions, oldState!.program.getCompilerOptions());
if (useOldState) {
// Verify the sanity of old state
if (!oldState!.currentChangedFilePath) {
+1458 -1364
View File
File diff suppressed because it is too large Load Diff
+136 -25
View File
@@ -62,9 +62,7 @@ namespace ts {
/* @internal */
export const libMap = createMapFromEntries(libEntries);
/* @internal */
export const optionDeclarations: CommandLineOption[] = [
// CommandLine only options
const commonOptionsWithBuild: CommandLineOption[] = [
{
name: "help",
shortName: "h",
@@ -78,6 +76,27 @@ namespace ts {
shortName: "?",
type: "boolean"
},
{
name: "preserveWatchOutput",
type: "boolean",
showInSimplifiedHelpView: false,
category: Diagnostics.Command_line_Options,
description: Diagnostics.Whether_to_keep_outdated_console_output_in_watch_mode_instead_of_clearing_the_screen,
},
{
name: "watch",
shortName: "w",
type: "boolean",
showInSimplifiedHelpView: true,
category: Diagnostics.Command_line_Options,
description: Diagnostics.Watch_input_files,
},
];
/* @internal */
export const optionDeclarations: CommandLineOption[] = [
// CommandLine only options
...commonOptionsWithBuild,
{
name: "all",
type: "boolean",
@@ -125,21 +144,6 @@ namespace ts {
category: Diagnostics.Command_line_Options,
description: Diagnostics.Stylize_errors_and_messages_using_color_and_context_experimental
},
{
name: "preserveWatchOutput",
type: "boolean",
showInSimplifiedHelpView: false,
category: Diagnostics.Command_line_Options,
description: Diagnostics.Whether_to_keep_outdated_console_output_in_watch_mode_instead_of_clearing_the_screen,
},
{
name: "watch",
shortName: "w",
type: "boolean",
showInSimplifiedHelpView: true,
category: Diagnostics.Command_line_Options,
description: Diagnostics.Watch_input_files,
},
// Basic
{
@@ -319,13 +323,15 @@ namespace ts {
{
name: "noImplicitAny",
type: "boolean",
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
description: Diagnostics.Raise_error_on_expressions_and_declarations_with_an_implied_any_type,
description: Diagnostics.Raise_error_on_expressions_and_declarations_with_an_implied_any_type
},
{
name: "strictNullChecks",
type: "boolean",
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
description: Diagnostics.Enable_strict_null_checks
@@ -333,6 +339,7 @@ namespace ts {
{
name: "strictFunctionTypes",
type: "boolean",
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
description: Diagnostics.Enable_strict_checking_of_function_types
@@ -340,6 +347,7 @@ namespace ts {
{
name: "strictPropertyInitialization",
type: "boolean",
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
description: Diagnostics.Enable_strict_checking_of_property_initialization_in_classes
@@ -347,6 +355,7 @@ namespace ts {
{
name: "noImplicitThis",
type: "boolean",
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
description: Diagnostics.Raise_error_on_this_expressions_with_an_implied_any_type,
@@ -354,6 +363,7 @@ namespace ts {
{
name: "alwaysStrict",
type: "boolean",
strictFlag: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Strict_Type_Checking_Options,
description: Diagnostics.Parse_in_strict_mode_and_emit_use_strict_for_each_source_file
@@ -363,6 +373,7 @@ namespace ts {
{
name: "noUnusedLocals",
type: "boolean",
affectsSemanticDiagnostics: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Additional_Checks,
description: Diagnostics.Report_errors_on_unused_locals,
@@ -370,6 +381,7 @@ namespace ts {
{
name: "noUnusedParameters",
type: "boolean",
affectsSemanticDiagnostics: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Additional_Checks,
description: Diagnostics.Report_errors_on_unused_parameters,
@@ -377,6 +389,7 @@ namespace ts {
{
name: "noImplicitReturns",
type: "boolean",
affectsSemanticDiagnostics: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Additional_Checks,
description: Diagnostics.Report_error_when_not_all_code_paths_in_function_return_a_value
@@ -384,6 +397,7 @@ namespace ts {
{
name: "noFallthroughCasesInSwitch",
type: "boolean",
affectsSemanticDiagnostics: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Additional_Checks,
description: Diagnostics.Report_errors_for_fallthrough_cases_in_switch_statement
@@ -455,12 +469,14 @@ namespace ts {
{
name: "allowSyntheticDefaultImports",
type: "boolean",
affectsSemanticDiagnostics: true,
category: Diagnostics.Module_Resolution_Options,
description: Diagnostics.Allow_default_imports_from_modules_with_no_default_export_This_does_not_affect_code_emit_just_typechecking
},
{
name: "esModuleInterop",
type: "boolean",
affectsSemanticDiagnostics: true,
showInSimplifiedHelpView: true,
category: Diagnostics.Module_Resolution_Options,
description: Diagnostics.Enables_emit_interoperability_between_CommonJS_and_ES_Modules_via_creation_of_namespace_objects_for_all_imports_Implies_allowSyntheticDefaultImports
@@ -640,6 +656,7 @@ namespace ts {
{
name: "noImplicitUseStrict",
type: "boolean",
affectsSemanticDiagnostics: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Do_not_emit_use_strict_directives_in_module_output
},
@@ -678,24 +695,28 @@ namespace ts {
{
name: "allowUnusedLabels",
type: "boolean",
affectsSemanticDiagnostics: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Do_not_report_errors_on_unused_labels
},
{
name: "allowUnreachableCode",
type: "boolean",
affectsSemanticDiagnostics: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Do_not_report_errors_on_unreachable_code
},
{
name: "suppressExcessPropertyErrors",
type: "boolean",
affectsSemanticDiagnostics: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Suppress_excess_property_checks_for_object_literals,
},
{
name: "suppressImplicitAnyIndexErrors",
type: "boolean",
affectsSemanticDiagnostics: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Suppress_noImplicitAny_errors_for_indexing_objects_lacking_index_signatures,
},
@@ -714,6 +735,7 @@ namespace ts {
{
name: "noStrictGenericChecks",
type: "boolean",
affectsSemanticDiagnostics: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Disable_strict_checking_of_generic_signatures_in_function_types,
},
@@ -736,6 +758,38 @@ namespace ts {
}
];
/* @internal */
export const buildOpts: CommandLineOption[] = [
...commonOptionsWithBuild,
{
name: "verbose",
shortName: "v",
category: Diagnostics.Command_line_Options,
description: Diagnostics.Enable_verbose_logging,
type: "boolean"
},
{
name: "dry",
shortName: "d",
category: Diagnostics.Command_line_Options,
description: Diagnostics.Show_what_would_be_built_or_deleted_if_specified_with_clean,
type: "boolean"
},
{
name: "force",
shortName: "f",
category: Diagnostics.Command_line_Options,
description: Diagnostics.Build_all_projects_including_those_that_appear_to_be_up_to_date,
type: "boolean"
},
{
name: "clean",
category: Diagnostics.Command_line_Options,
description: Diagnostics.Delete_the_outputs_of_all_projects,
type: "boolean"
}
];
/* @internal */
export const typeAcquisitionDeclarations: CommandLineOption[] = [
{
@@ -797,10 +851,11 @@ namespace ts {
}
function getOptionNameMap(): OptionNameMap {
if (optionNameMapCache) {
return optionNameMapCache;
}
return optionNameMapCache || (optionNameMapCache = createOptionNameMap(optionDeclarations));
}
/*@internal*/
export function createOptionNameMap(optionDeclarations: ReadonlyArray<CommandLineOption>): OptionNameMap {
const optionNameMap = createMap<CommandLineOption>();
const shortOptionNames = createMap<string>();
forEach(optionDeclarations, option => {
@@ -810,8 +865,7 @@ namespace ts {
}
});
optionNameMapCache = { optionNameMap, shortOptionNames };
return optionNameMapCache;
return { optionNameMap, shortOptionNames };
}
/* @internal */
@@ -961,7 +1015,12 @@ namespace ts {
}
/** @internal */
export function getOptionFromName(optionName: string, allowShort = false): CommandLineOption | undefined {
export function getOptionFromName(optionName: string, allowShort?: boolean): CommandLineOption | undefined {
return getOptionDeclarationFromName(getOptionNameMap, optionName, allowShort);
}
/*@internal*/
export function getOptionDeclarationFromName(getOptionNameMap: () => OptionNameMap, optionName: string, allowShort = false): CommandLineOption | undefined {
optionName = optionName.toLowerCase();
const { optionNameMap, shortOptionNames } = getOptionNameMap();
// Try to translate short option names to their full equivalents.
@@ -974,6 +1033,58 @@ namespace ts {
return optionNameMap.get(optionName);
}
/*@internal*/
export interface ParsedBuildCommand {
buildOptions: BuildOptions;
projects: string[];
errors: ReadonlyArray<Diagnostic>;
}
/*@internal*/
export function parseBuildCommand(args: string[]): ParsedBuildCommand {
let buildOptionNameMap: OptionNameMap | undefined;
const returnBuildOptionNameMap = () => (buildOptionNameMap || (buildOptionNameMap = createOptionNameMap(buildOpts)));
const buildOptions: BuildOptions = {};
const projects: string[] = [];
let errors: Diagnostic[] | undefined;
for (const arg of args) {
if (arg.charCodeAt(0) === CharacterCodes.minus) {
const opt = getOptionDeclarationFromName(returnBuildOptionNameMap, arg.slice(arg.charCodeAt(1) === CharacterCodes.minus ? 2 : 1), /*allowShort*/ true);
if (opt) {
buildOptions[opt.name as keyof BuildOptions] = true;
}
else {
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Unknown_build_option_0, arg));
}
}
else {
// Not a flag, parse as filename
projects.push(arg);
}
}
if (projects.length === 0) {
// tsc -b invoked with no extra arguments; act as if invoked with "tsc -b ."
projects.push(".");
}
// Nonsensical combinations
if (buildOptions.clean && buildOptions.force) {
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "force"));
}
if (buildOptions.clean && buildOptions.verbose) {
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "verbose"));
}
if (buildOptions.clean && buildOptions.watch) {
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "clean", "watch"));
}
if (buildOptions.watch && buildOptions.dry) {
(errors || (errors = [])).push(createCompilerDiagnostic(Diagnostics.Options_0_and_1_cannot_be_combined, "watch", "dry"));
}
return { buildOptions, projects, errors: errors || emptyArray };
}
function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string {
const diagnostic = createCompilerDiagnostic.apply(undefined, arguments);
+4 -10
View File
@@ -789,11 +789,8 @@ namespace ts {
* @param comparer An optional `Comparer` used to sort entries before comparison, though the
* result will remain in the original order in `array`.
*/
export function deduplicate<T>(array: ReadonlyArray<T>, equalityComparer?: EqualityComparer<T>, comparer?: Comparer<T>): T[];
export function deduplicate<T>(array: ReadonlyArray<T> | undefined, equalityComparer?: EqualityComparer<T>, comparer?: Comparer<T>): T[] | undefined;
export function deduplicate<T>(array: ReadonlyArray<T> | undefined, equalityComparer: EqualityComparer<T>, comparer?: Comparer<T>): T[] | undefined {
return !array ? undefined :
array.length === 0 ? [] :
export function deduplicate<T>(array: ReadonlyArray<T>, equalityComparer: EqualityComparer<T>, comparer?: Comparer<T>): T[] {
return array.length === 0 ? [] :
array.length === 1 ? array.slice() :
comparer ? deduplicateRelational(array, equalityComparer, comparer) :
deduplicateEquality(array, equalityComparer);
@@ -802,10 +799,7 @@ namespace ts {
/**
* Deduplicates an array that has already been sorted.
*/
function deduplicateSorted<T>(array: ReadonlyArray<T>, comparer: EqualityComparer<T> | Comparer<T>): T[];
function deduplicateSorted<T>(array: ReadonlyArray<T> | undefined, comparer: EqualityComparer<T> | Comparer<T>): T[] | undefined;
function deduplicateSorted<T>(array: ReadonlyArray<T> | undefined, comparer: EqualityComparer<T> | Comparer<T>): T[] | undefined {
if (!array) return undefined;
function deduplicateSorted<T>(array: ReadonlyArray<T>, comparer: EqualityComparer<T> | Comparer<T>): T[] {
if (array.length === 0) return [];
let last = array[0];
@@ -1272,7 +1266,7 @@ namespace ts {
if (!left || !right) return false;
for (const key in left) {
if (hasOwnProperty.call(left, key)) {
if (!hasOwnProperty.call(right, key) === undefined) return false;
if (!hasOwnProperty.call(right, key)) return false;
if (!equalityComparer(left[key], right[key])) return false;
}
}
+54 -5
View File
@@ -987,6 +987,26 @@
"category": "Error",
"code": 1344
},
"An expression of type 'void' cannot be tested for truthiness": {
"category": "Error",
"code": 1345
},
"This parameter is not allowed with 'use strict' directive.": {
"category": "Error",
"code": 1346
},
"'use strict' directive cannot be used with non-simple parameter list.": {
"category": "Error",
"code": 1347
},
"Non-simple parameter declared here.": {
"category": "Error",
"code": 1348
},
"'use strict' directive used here.": {
"category": "Error",
"code": 1349
},
"Duplicate identifier '{0}'.": {
"category": "Error",
@@ -1960,10 +1980,6 @@
"category": "Error",
"code": 2549
},
"Generic type instantiation is excessively deep and possibly infinite.": {
"category": "Error",
"code": 2550
},
"Property '{0}' does not exist on type '{1}'. Did you mean '{2}'?": {
"category": "Error",
"code": 2551
@@ -2064,6 +2080,14 @@
"category": "Error",
"code": 2575
},
"Property '{0}' is a static member of type '{1}'": {
"category": "Error",
"code": 2576
},
"Return type annotation circularly references itself.": {
"category": "Error",
"code": 2577
},
"JSX element attributes type '{0}' may not be a union type.": {
"category": "Error",
"code": 2600
@@ -2429,6 +2453,10 @@
"category": "Error",
"code": 2732
},
"Index '{0}' is out-of-bounds in tuple of length {1}.": {
"category": "Error",
"code": 2733
},
"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
@@ -2888,6 +2916,11 @@
"category": "Error",
"code": 5071
},
"Unknown build option '{0}'.": {
"category": "Error",
"code": 5072
},
"Generates a sourcemap for each corresponding '.d.ts' file.": {
"category": "Message",
@@ -3929,6 +3962,10 @@
"category": "Error",
"code": 7041
},
"Module '{0}' was resolved to '{1}', but '--resolveJsonModule' is not used.": {
"category": "Error",
"code": 7042
},
"You cannot rename this element.": {
"category": "Error",
"code": 8000
@@ -4171,7 +4208,10 @@
"category": "Suggestion",
"code": 80005
},
"This may be converted to an async function.": {
"category": "Suggestion",
"code": 80006
},
"Add missing 'super()' call": {
"category": "Message",
"code": 90001
@@ -4544,6 +4584,7 @@
"category": "Message",
"code": 95062
},
"Add missing enum member '{0}'": {
"category": "Message",
"code": 95063
@@ -4551,5 +4592,13 @@
"Add all missing imports": {
"category": "Message",
"code": 95064
},
"Convert to async function":{
"category": "Message",
"code": 95065
},
"Convert all to async functions": {
"category": "Message",
"code": 95066
}
}
+7 -3
View File
@@ -2593,14 +2593,14 @@ namespace ts {
}
function emitSyntheticTripleSlashReferencesIfNeeded(node: Bundle) {
emitTripleSlashDirectives(!!node.hasNoDefaultLib, node.syntheticFileReferences || [], node.syntheticTypeReferences || []);
emitTripleSlashDirectives(!!node.hasNoDefaultLib, node.syntheticFileReferences || [], node.syntheticTypeReferences || [], node.syntheticLibReferences || []);
}
function emitTripleSlashDirectivesIfNeeded(node: SourceFile) {
if (node.isDeclarationFile) emitTripleSlashDirectives(node.hasNoDefaultLib, node.referencedFiles, node.typeReferenceDirectives);
if (node.isDeclarationFile) emitTripleSlashDirectives(node.hasNoDefaultLib, node.referencedFiles, node.typeReferenceDirectives, node.libReferenceDirectives);
}
function emitTripleSlashDirectives(hasNoDefaultLib: boolean, files: ReadonlyArray<FileReference>, types: ReadonlyArray<FileReference>) {
function emitTripleSlashDirectives(hasNoDefaultLib: boolean, files: ReadonlyArray<FileReference>, types: ReadonlyArray<FileReference>, libs: ReadonlyArray<FileReference>) {
if (hasNoDefaultLib) {
write(`/// <reference no-default-lib="true"/>`);
writeLine();
@@ -2628,6 +2628,10 @@ namespace ts {
write(`/// <reference types="${directive.fileName}" />`);
writeLine();
}
for (const directive of libs) {
write(`/// <reference lib="${directive.fileName}" />`);
writeLine();
}
}
function emitSourceFileWorker(node: SourceFile) {
+22 -13
View File
@@ -3894,6 +3894,20 @@ namespace ts {
return statementOffset;
}
export function findUseStrictPrologue(statements: ReadonlyArray<Statement>): Statement | undefined {
for (const statement of statements) {
if (isPrologueDirective(statement)) {
if (isUseStrictPrologue(statement)) {
return statement;
}
}
else {
break;
}
}
return undefined;
}
export function startsWithUseStrict(statements: ReadonlyArray<Statement>) {
const firstStatement = firstOrUndefined(statements);
return firstStatement !== undefined
@@ -3907,18 +3921,7 @@ namespace ts {
* @param statements An array of statements
*/
export function ensureUseStrict(statements: NodeArray<Statement>): NodeArray<Statement> {
let foundUseStrict = false;
for (const statement of statements) {
if (isPrologueDirective(statement)) {
if (isUseStrictPrologue(statement as ExpressionStatement)) {
foundUseStrict = true;
break;
}
}
else {
break;
}
}
const foundUseStrict = findUseStrictPrologue(statements);
if (!foundUseStrict) {
return setTextRange(
@@ -4709,7 +4712,7 @@ namespace ts {
/**
* Gets the property name of a BindingOrAssignmentElement
*/
export function getPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement) {
export function getPropertyNameOfBindingOrAssignmentElement(bindingElement: BindingOrAssignmentElement): PropertyName | undefined {
switch (bindingElement.kind) {
case SyntaxKind.BindingElement:
// `a` in `let { a: b } = ...`
@@ -4754,6 +4757,12 @@ namespace ts {
Debug.fail("Invalid property name for binding element.");
}
function isStringOrNumericLiteral(node: Node): node is StringLiteral | NumericLiteral {
const kind = node.kind;
return kind === SyntaxKind.StringLiteral
|| kind === SyntaxKind.NumericLiteral;
}
/**
* Gets the elements of a BindingOrAssignmentPattern
*/
+49 -45
View File
@@ -29,6 +29,15 @@ namespace ts {
path: string;
extension: Extension;
packageId: PackageId | undefined;
/**
* When the resolved is not created from cache, the value is
* - string if original Path if it is symbolic link to the resolved path
* - undefined if path is not a symbolic link
* When the resolved is created using value from cache of ResolvedModuleWithFailedLookupLocations, the value is:
* - string if original Path if it is symbolic link to the resolved path
* - true if path is not a symbolic link - this indicates that the originalPath calculation is already done and needs to be skipped
*/
originalPath?: string | true;
}
/** Result of trying to resolve a module at a file. Needs to have 'packageId' added later. */
@@ -62,9 +71,9 @@ namespace ts {
return { fileName: resolved.path, packageId: resolved.packageId };
}
function createResolvedModuleWithFailedLookupLocations(resolved: Resolved | undefined, originalPath: string | undefined, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations {
function createResolvedModuleWithFailedLookupLocations(resolved: Resolved | undefined, isExternalLibraryImport: boolean, failedLookupLocations: string[]): ResolvedModuleWithFailedLookupLocations {
return {
resolvedModule: resolved && { resolvedFileName: resolved.path, originalPath, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId },
resolvedModule: resolved && { resolvedFileName: resolved.path, originalPath: resolved.originalPath === true ? undefined : resolved.originalPath, extension: resolved.extension, isExternalLibraryImport, packageId: resolved.packageId },
failedLookupLocations
};
}
@@ -362,9 +371,7 @@ namespace ts {
}
function getOrCreateCacheForModuleName(nonRelativeModuleName: string): PerModuleNameCache {
if (isExternalModuleNameRelative(nonRelativeModuleName)) {
return undefined!; // TODO: GH#18217
}
Debug.assert(!isExternalModuleNameRelative(nonRelativeModuleName));
let perModuleNameCache = moduleNameToDirectoryMap.get(nonRelativeModuleName);
if (!perModuleNameCache) {
perModuleNameCache = createPerModuleNameCache();
@@ -401,46 +408,47 @@ namespace ts {
}
directoryPathMap.set(path, result);
const resolvedFileName = result.resolvedModule && result.resolvedModule.resolvedFileName;
const resolvedFileName = result.resolvedModule &&
(result.resolvedModule.originalPath || result.resolvedModule.resolvedFileName);
// find common prefix between directory and resolved file name
// this common prefix should be the shorted path that has the same resolution
// this common prefix should be the shortest path that has the same resolution
// directory: /a/b/c/d/e
// resolvedFileName: /a/b/foo.d.ts
const commonPrefix = getCommonPrefix(path, resolvedFileName);
// commonPrefix: /a/b
// for failed lookups cache the result for every directory up to root
const commonPrefix = resolvedFileName && getCommonPrefix(path, resolvedFileName);
let current = path;
while (true) {
while (current !== commonPrefix) {
const parent = getDirectoryPath(current);
if (parent === current || directoryPathMap.has(parent)) {
break;
}
directoryPathMap.set(parent, result);
current = parent;
if (current === commonPrefix) {
break;
}
}
}
function getCommonPrefix(directory: Path, resolution: string | undefined) {
if (resolution === undefined) {
return undefined;
}
function getCommonPrefix(directory: Path, resolution: string) {
const resolutionDirectory = toPath(getDirectoryPath(resolution), currentDirectory, getCanonicalFileName);
// find first position where directory and resolution differs
let i = 0;
while (i < Math.min(directory.length, resolutionDirectory.length) && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) {
const limit = Math.min(directory.length, resolutionDirectory.length);
while (i < limit && directory.charCodeAt(i) === resolutionDirectory.charCodeAt(i)) {
i++;
}
// find last directory separator before position i
const sep = directory.lastIndexOf(directorySeparator, i);
if (sep < 0) {
if (i === directory.length && (resolutionDirectory.length === i || resolutionDirectory[i] === directorySeparator)) {
return directory;
}
const rootLength = getRootLength(directory);
if (i < rootLength) {
return undefined;
}
return directory.substr(0, sep);
const sep = directory.lastIndexOf(directorySeparator, i - 1);
if (sep === -1) {
return undefined;
}
return directory.substr(0, Math.max(sep, rootLength));
}
}
}
@@ -492,10 +500,9 @@ namespace ts {
if (perFolderCache) {
perFolderCache.set(moduleName, result);
// put result in per-module name cache
const perModuleNameCache = cache!.getOrCreateCacheForModuleName(moduleName);
if (perModuleNameCache) {
perModuleNameCache.set(containingDirectory, result);
if (!isExternalModuleNameRelative(moduleName)) {
// put result in per-module name cache
cache!.getOrCreateCacheForModuleName(moduleName).set(containingDirectory, result);
}
}
}
@@ -753,16 +760,16 @@ namespace ts {
tryResolve(Extensions.JavaScript) ||
(compilerOptions.resolveJsonModule ? tryResolve(Extensions.Json) : undefined));
if (result && result.value) {
const { resolved, originalPath, isExternalLibraryImport } = result.value;
return createResolvedModuleWithFailedLookupLocations(resolved, originalPath, isExternalLibraryImport, failedLookupLocations);
const { resolved, isExternalLibraryImport } = result.value;
return createResolvedModuleWithFailedLookupLocations(resolved, isExternalLibraryImport, failedLookupLocations);
}
return { resolvedModule: undefined, failedLookupLocations };
function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, originalPath?: string, isExternalLibraryImport: boolean }> {
function tryResolve(extensions: Extensions): SearchResult<{ resolved: Resolved, isExternalLibraryImport: boolean }> {
const loader: ResolutionKindSpecificLoader = (extensions, candidate, failedLookupLocations, onlyRecordFailures, state) => nodeLoadModuleByRelativeName(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, /*considerPackageJson*/ true);
const resolved = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loader, failedLookupLocations, state);
if (resolved) {
return toSearchResult({ resolved, isExternalLibraryImport: false });
return toSearchResult({ resolved, isExternalLibraryImport: stringContains(resolved.path, nodeModulesPathPart) });
}
if (!isExternalModuleNameRelative(moduleName)) {
@@ -773,17 +780,13 @@ namespace ts {
if (!resolved) return undefined;
let resolvedValue = resolved.value;
let originalPath: string | undefined;
if (!compilerOptions.preserveSymlinks && resolvedValue) {
originalPath = resolvedValue.path;
if (!compilerOptions.preserveSymlinks && resolvedValue && !resolvedValue.originalPath) {
const path = realPath(resolvedValue.path, host, traceEnabled);
if (path === originalPath) {
originalPath = undefined;
}
resolvedValue = { ...resolvedValue, path };
const originalPath = path === resolvedValue.path ? undefined : resolvedValue.path;
resolvedValue = { ...resolvedValue, path, originalPath };
}
// For node_modules lookups, get the real path so that multiple accesses to an `npm link`-ed module do not create duplicate files.
return { value: resolvedValue && { resolved: resolvedValue, originalPath, isExternalLibraryImport: true } };
return { value: resolvedValue && { resolved: resolvedValue, isExternalLibraryImport: true } };
}
else {
const { path: candidate, parts } = normalizePathAndParts(combinePaths(containingDirectory, moduleName));
@@ -840,7 +843,8 @@ namespace ts {
return loadNodeModuleFromDirectory(extensions, candidate, failedLookupLocations, onlyRecordFailures, state, considerPackageJson);
}
const nodeModulesPathPart = "/node_modules/";
/*@internal*/
export const nodeModulesPathPart = "/node_modules/";
/**
* This will be called on the successfully resolved path from `loadModuleFromFile`.
@@ -1233,7 +1237,7 @@ namespace ts {
trace(host, Diagnostics.Resolution_for_module_0_was_found_in_cache_from_location_1, moduleName, containingDirectory);
}
failedLookupLocations.push(...result.failedLookupLocations);
return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } };
return { value: result.resolvedModule && { path: result.resolvedModule.resolvedFileName, originalPath: result.resolvedModule.originalPath || true, extension: result.resolvedModule.extension, packageId: result.resolvedModule.packageId } };
}
}
@@ -1245,16 +1249,16 @@ namespace ts {
const resolved = tryResolve(Extensions.TypeScript) || tryResolve(Extensions.JavaScript);
// No originalPath because classic resolution doesn't resolve realPath
return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*originalPath*/ undefined, /*isExternalLibraryImport*/ false, failedLookupLocations);
return createResolvedModuleWithFailedLookupLocations(resolved && resolved.value, /*isExternalLibraryImport*/ false, failedLookupLocations);
function tryResolve(extensions: Extensions): SearchResult<Resolved> {
const resolvedUsingSettings = tryLoadModuleUsingOptionalResolutionSettings(extensions, moduleName, containingDirectory, loadModuleFromFileNoPackageId, failedLookupLocations, state);
if (resolvedUsingSettings) {
return { value: resolvedUsingSettings };
}
const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName);
if (!isExternalModuleNameRelative(moduleName)) {
const perModuleNameCache = cache && cache.getOrCreateCacheForModuleName(moduleName);
// Climb up parent directories looking for a module.
const resolved = forEachAncestorDirectory(containingDirectory, directory => {
const resolutionFromCache = tryFindNonRelativeModuleNameInCache(perModuleNameCache, moduleName, directory, traceEnabled, host, failedLookupLocations);
@@ -1292,7 +1296,7 @@ namespace ts {
const state: ModuleResolutionState = { compilerOptions, host, traceEnabled };
const failedLookupLocations: string[] = [];
const resolved = loadModuleFromNodeModulesOneLevel(Extensions.DtsOnly, moduleName, globalCache, failedLookupLocations, state);
return createResolvedModuleWithFailedLookupLocations(resolved, /*originalPath*/ undefined, /*isExternalLibraryImport*/ true, failedLookupLocations);
return createResolvedModuleWithFailedLookupLocations(resolved, /*isExternalLibraryImport*/ true, failedLookupLocations);
}
/**
+131 -146
View File
@@ -1,8 +1,52 @@
// Used by importFixes, getEditsForFileRename, and declaration emit to synthesize import module specifiers.
/* @internal */
namespace ts.moduleSpecifiers {
export interface ModuleSpecifierPreferences {
readonly importModuleSpecifierPreference?: "relative" | "non-relative";
const enum RelativePreference { Relative, NonRelative, Auto }
// See UserPreferences#importPathEnding
const enum Ending { Minimal, Index, JsExtension }
// Processed preferences
interface Preferences {
readonly relativePreference: RelativePreference;
readonly ending: Ending;
}
function getPreferences({ importModuleSpecifierPreference, importModuleSpecifierEnding }: UserPreferences, compilerOptions: CompilerOptions, importingSourceFile: SourceFile): Preferences {
return {
relativePreference: importModuleSpecifierPreference === "relative" ? RelativePreference.Relative : importModuleSpecifierPreference === "non-relative" ? RelativePreference.NonRelative : RelativePreference.Auto,
ending: getEnding(),
};
function getEnding(): Ending {
switch (importModuleSpecifierEnding) {
case "minimal": return Ending.Minimal;
case "index": return Ending.Index;
case "js": return Ending.JsExtension;
default: return usesJsExtensionOnImports(importingSourceFile) ? Ending.JsExtension
: getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs ? Ending.Index : Ending.Minimal;
}
}
}
function getPreferencesForUpdate(compilerOptions: CompilerOptions, oldImportSpecifier: string): Preferences {
return {
relativePreference: isExternalModuleNameRelative(oldImportSpecifier) ? RelativePreference.Relative : RelativePreference.NonRelative,
ending: hasJavaScriptOrJsonFileExtension(oldImportSpecifier) ? Ending.JsExtension
: getEmitModuleResolutionKind(compilerOptions) !== ModuleResolutionKind.NodeJs || endsWith(oldImportSpecifier, "index") ? Ending.Index : Ending.Minimal,
};
}
export function updateModuleSpecifier(
compilerOptions: CompilerOptions,
importingSourceFileName: Path,
toFileName: string,
host: ModuleSpecifierResolutionHost,
files: ReadonlyArray<SourceFile>,
redirectTargetsMap: RedirectTargetsMap,
oldImportSpecifier: string,
): string | undefined {
const res = getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, files, redirectTargetsMap, getPreferencesForUpdate(compilerOptions, oldImportSpecifier));
if (res === oldImportSpecifier) return undefined;
return res;
}
// Note: importingSourceFile is just for usesJsExtensionOnImports
@@ -13,148 +57,98 @@ namespace ts.moduleSpecifiers {
toFileName: string,
host: ModuleSpecifierResolutionHost,
files: ReadonlyArray<SourceFile>,
preferences: ModuleSpecifierPreferences = {},
preferences: UserPreferences = {},
redirectTargetsMap: RedirectTargetsMap,
): string {
const info = getInfo(compilerOptions, importingSourceFile, importingSourceFileName, host);
const modulePaths = getAllModulePaths(files, importingSourceFileName, toFileName, info.getCanonicalFileName, host, redirectTargetsMap);
return firstDefined(modulePaths, moduleFileName => getGlobalModuleSpecifier(moduleFileName, info, host, compilerOptions)) ||
first(getLocalModuleSpecifiers(toFileName, info, compilerOptions, preferences));
return getModuleSpecifierWorker(compilerOptions, importingSourceFileName, toFileName, host, files, redirectTargetsMap, getPreferences(preferences, compilerOptions, importingSourceFile));
}
export function getModuleSpecifierForDeclarationFile(
moduleSymbol: Symbol,
function getModuleSpecifierWorker(
compilerOptions: CompilerOptions,
importingSourceFile: SourceFile,
importingSourceFileName: Path,
toFileName: string,
host: ModuleSpecifierResolutionHost,
files: ReadonlyArray<SourceFile>,
preferences: ModuleSpecifierPreferences,
redirectTargetsMap: RedirectTargetsMap,
preferences: Preferences
): string {
return first(first(getModuleSpecifiers(moduleSymbol, compilerOptions, importingSourceFile, host, files, preferences, redirectTargetsMap)));
const info = getInfo(importingSourceFileName, host);
const modulePaths = getAllModulePaths(files, importingSourceFileName, toFileName, info.getCanonicalFileName, host, redirectTargetsMap);
return firstDefined(modulePaths, moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions)) ||
getLocalModuleSpecifier(toFileName, info, compilerOptions, preferences);
}
// For each symlink/original for a module, returns a list of ways to import that file.
// Returns an import for each symlink and for the realpath.
export function getModuleSpecifiers(
moduleSymbol: Symbol,
compilerOptions: CompilerOptions,
importingSourceFile: SourceFile,
host: ModuleSpecifierResolutionHost,
files: ReadonlyArray<SourceFile>,
preferences: ModuleSpecifierPreferences,
userPreferences: UserPreferences,
redirectTargetsMap: RedirectTargetsMap,
): ReadonlyArray<ReadonlyArray<string>> {
): ReadonlyArray<string> {
const ambient = tryGetModuleNameFromAmbientModule(moduleSymbol);
if (ambient) return [[ambient]];
if (ambient) return [ambient];
const info = getInfo(compilerOptions, importingSourceFile, importingSourceFile.path, host);
if (!files) {
return Debug.fail("Files list must be present to resolve symlinks in specifier resolution");
}
const moduleSourceFile = getSourceFileOfNode(moduleSymbol.valueDeclaration);
const info = getInfo(importingSourceFile.path, host);
const moduleSourceFile = getSourceFileOfNode(moduleSymbol.valueDeclaration || getNonAugmentationDeclaration(moduleSymbol));
const modulePaths = getAllModulePaths(files, importingSourceFile.path, moduleSourceFile.fileName, info.getCanonicalFileName, host, redirectTargetsMap);
const global = mapDefined(modulePaths, moduleFileName => getGlobalModuleSpecifier(moduleFileName, info, host, compilerOptions));
return global.length ? global.map(g => [g]) : modulePaths.map(moduleFileName =>
getLocalModuleSpecifiers(moduleFileName, info, compilerOptions, preferences));
const preferences = getPreferences(userPreferences, compilerOptions, importingSourceFile);
const global = mapDefined(modulePaths, moduleFileName => tryGetModuleNameAsNodeModule(moduleFileName, info, host, compilerOptions));
return global.length ? global : modulePaths.map(moduleFileName => getLocalModuleSpecifier(moduleFileName, info, compilerOptions, preferences));
}
interface Info {
readonly moduleResolutionKind: ModuleResolutionKind;
readonly addJsExtension: boolean;
readonly getCanonicalFileName: GetCanonicalFileName;
readonly sourceDirectory: Path;
}
// importingSourceFileName is separate because getEditsForFileRename may need to specify an updated path
function getInfo(compilerOptions: CompilerOptions, importingSourceFile: SourceFile, importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Info {
const moduleResolutionKind = getEmitModuleResolutionKind(compilerOptions);
const addJsExtension = usesJsExtensionOnImports(importingSourceFile);
function getInfo(importingSourceFileName: Path, host: ModuleSpecifierResolutionHost): Info {
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames ? host.useCaseSensitiveFileNames() : true);
const sourceDirectory = getDirectoryPath(importingSourceFileName);
return { moduleResolutionKind, addJsExtension, getCanonicalFileName, sourceDirectory };
return { getCanonicalFileName, sourceDirectory };
}
function getGlobalModuleSpecifier(
moduleFileName: string,
{ addJsExtension, getCanonicalFileName, sourceDirectory }: Info,
host: ModuleSpecifierResolutionHost,
compilerOptions: CompilerOptions,
) {
return tryGetModuleNameFromTypeRoots(compilerOptions, host, getCanonicalFileName, moduleFileName, addJsExtension)
|| tryGetModuleNameAsNodeModule(compilerOptions, moduleFileName, host, getCanonicalFileName, sourceDirectory);
}
function getLocalModuleSpecifiers(
moduleFileName: string,
{ moduleResolutionKind, addJsExtension, getCanonicalFileName, sourceDirectory }: Info,
compilerOptions: CompilerOptions,
preferences: ModuleSpecifierPreferences,
): ReadonlyArray<string> {
function getLocalModuleSpecifier(moduleFileName: string, { getCanonicalFileName, sourceDirectory }: Info, compilerOptions: CompilerOptions, { ending, relativePreference }: Preferences): string {
const { baseUrl, paths, rootDirs } = compilerOptions;
const relativePath = rootDirs && tryGetModuleNameFromRootDirs(rootDirs, moduleFileName, sourceDirectory, getCanonicalFileName) ||
removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), moduleResolutionKind, addJsExtension);
if (!baseUrl || preferences.importModuleSpecifierPreference === "relative") {
return [relativePath];
removeExtensionAndIndexPostFix(ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, moduleFileName, getCanonicalFileName)), ending, compilerOptions);
if (!baseUrl || relativePreference === RelativePreference.Relative) {
return relativePath;
}
const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseUrl, getCanonicalFileName);
if (!relativeToBaseUrl) {
return [relativePath];
return relativePath;
}
const importRelativeToBaseUrl = removeExtensionAndIndexPostFix(relativeToBaseUrl, moduleResolutionKind, addJsExtension);
if (paths) {
const fromPaths = tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl), importRelativeToBaseUrl, paths);
if (fromPaths) {
return [fromPaths];
}
const importRelativeToBaseUrl = removeExtensionAndIndexPostFix(relativeToBaseUrl, ending, compilerOptions);
const fromPaths = paths && tryGetModuleNameFromPaths(removeFileExtension(relativeToBaseUrl), importRelativeToBaseUrl, paths);
const nonRelative = fromPaths === undefined ? importRelativeToBaseUrl : fromPaths;
if (relativePreference === RelativePreference.NonRelative) {
return nonRelative;
}
if (preferences.importModuleSpecifierPreference === "non-relative") {
return [importRelativeToBaseUrl];
if (relativePreference !== RelativePreference.Auto) Debug.assertNever(relativePreference);
// Prefer a relative import over a baseUrl import if it has fewer components.
return isPathRelativeToParent(nonRelative) || countPathComponents(relativePath) < countPathComponents(nonRelative) ? relativePath : nonRelative;
}
function countPathComponents(path: string): number {
let count = 0;
for (let i = startsWith(path, "./") ? 2 : 0; i < path.length; i++) {
if (path.charCodeAt(i) === CharacterCodes.slash) count++;
}
if (preferences.importModuleSpecifierPreference !== undefined) Debug.assertNever(preferences.importModuleSpecifierPreference);
if (isPathRelativeToParent(relativeToBaseUrl)) {
return [relativePath];
}
/*
Prefer a relative import over a baseUrl import if it doesn't traverse up to baseUrl.
Suppose we have:
baseUrl = /base
sourceDirectory = /base/a/b
moduleFileName = /base/foo/bar
Then:
relativePath = ../../foo/bar
getRelativePathNParents(relativePath) = 2
pathFromSourceToBaseUrl = ../../
getRelativePathNParents(pathFromSourceToBaseUrl) = 2
2 < 2 = false
In this case we should prefer using the baseUrl path "/a/b" instead of the relative path "../../foo/bar".
Suppose we have:
baseUrl = /base
sourceDirectory = /base/foo/a
moduleFileName = /base/foo/bar
Then:
relativePath = ../a
getRelativePathNParents(relativePath) = 1
pathFromSourceToBaseUrl = ../../
getRelativePathNParents(pathFromSourceToBaseUrl) = 2
1 < 2 = true
In this case we should prefer using the relative path "../a" instead of the baseUrl path "foo/a".
*/
const pathFromSourceToBaseUrl = ensurePathIsNonModuleName(getRelativePathFromDirectory(sourceDirectory, baseUrl, getCanonicalFileName));
const relativeFirst = getRelativePathNParents(relativePath) < getRelativePathNParents(pathFromSourceToBaseUrl);
return relativeFirst ? [relativePath, importRelativeToBaseUrl] : [importRelativeToBaseUrl, relativePath];
return count;
}
function usesJsExtensionOnImports({ imports }: SourceFile): boolean {
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? fileExtensionIs(text, Extension.Js) : undefined) || false;
return firstDefined(imports, ({ text }) => pathIsRelative(text) ? hasJavaScriptOrJsonFileExtension(text) : undefined) || false;
}
function stringsEqual(a: string, b: string, getCanonicalFileName: GetCanonicalFileName): boolean {
@@ -222,18 +216,11 @@ namespace ts.moduleSpecifiers {
return result;
}
function getRelativePathNParents(relativePath: string): number {
const components = getPathComponents(relativePath);
if (components[0] || components.length === 1) return 0;
for (let i = 1; i < components.length; i++) {
if (components[i] !== "..") return i - 1;
}
return components.length - 1;
}
function tryGetModuleNameFromAmbientModule(moduleSymbol: Symbol): string | undefined {
const decl = moduleSymbol.valueDeclaration;
if (isModuleDeclaration(decl) && isStringLiteral(decl.name)) {
const decl = find(moduleSymbol.declarations,
d => isNonGlobalAmbientModule(d) && (!isExternalModuleAugmentation(d) || !isExternalModuleNameRelative(getTextOfIdentifierOrLiteral(d.name)))
) as (ModuleDeclaration & { name: StringLiteral }) | undefined;
if (decl) {
return decl.name.text;
}
}
@@ -271,37 +258,8 @@ namespace ts.moduleSpecifiers {
return removeFileExtension(relativePath);
}
function tryGetModuleNameFromTypeRoots(
options: CompilerOptions,
host: GetEffectiveTypeRootsHost,
getCanonicalFileName: (file: string) => string,
moduleFileName: string,
addJsExtension: boolean,
): string | undefined {
const roots = getEffectiveTypeRoots(options, host);
return firstDefined(roots, unNormalizedTypeRoot => {
const typeRoot = toPath(unNormalizedTypeRoot, /*basePath*/ undefined, getCanonicalFileName);
if (startsWith(moduleFileName, typeRoot)) {
// For a type definition, we can strip `/index` even with classic resolution.
return removeExtensionAndIndexPostFix(moduleFileName.substring(typeRoot.length + 1), ModuleResolutionKind.NodeJs, addJsExtension);
}
});
}
function tryGetModuleNameAsNodeModule(
options: CompilerOptions,
moduleFileName: string,
host: ModuleSpecifierResolutionHost,
getCanonicalFileName: (file: string) => string,
sourceDirectory: Path,
): string | undefined {
if (getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs) {
// nothing to do here
return undefined;
}
function tryGetModuleNameAsNodeModule(moduleFileName: string, { getCanonicalFileName, sourceDirectory }: Info, host: ModuleSpecifierResolutionHost, options: CompilerOptions): string | undefined {
const parts: NodeModulePathParts = getNodeModulePathParts(moduleFileName)!;
if (!parts) {
return undefined;
}
@@ -313,8 +271,12 @@ namespace ts.moduleSpecifiers {
// Get a path that's relative to node_modules or the importing file's path
// if node_modules folder is in this folder or any of its parent folders, no need to keep it.
if (!startsWith(sourceDirectory, getCanonicalFileName(moduleSpecifier.substring(0, parts.topLevelNodeModulesIndex)))) return undefined;
// If the module was found in @types, get the actual Node package name
return getPackageNameFromAtTypesDirectory(moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1));
const nodeModulesDirectoryName = moduleSpecifier.substring(parts.topLevelPackageNameIndex + 1);
const packageName = getPackageNameFromAtTypesDirectory(nodeModulesDirectoryName);
// For classic resolution, only allow importing from node_modules/@types, not other node_modules
return getEmitModuleResolutionKind(options) !== ModuleResolutionKind.NodeJs && packageName === nodeModulesDirectoryName ? undefined : packageName;
function getDirectoryOrExtensionlessFileName(path: string): string {
// If the file is the main module, it can be imported by the package name
@@ -389,7 +351,7 @@ namespace ts.moduleSpecifiers {
partEnd = fullPath.indexOf("/", partStart + 1);
switch (state) {
case States.BeforeNodeModules:
if (fullPath.indexOf("/node_modules/", partStart) === partStart) {
if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) {
topLevelNodeModulesIndex = partStart;
topLevelPackageNameIndex = partEnd;
state = States.NodeModules;
@@ -406,7 +368,7 @@ namespace ts.moduleSpecifiers {
}
break;
case States.PackageContent:
if (fullPath.indexOf("/node_modules/", partStart) === partStart) {
if (fullPath.indexOf(nodeModulesPathPart, partStart) === partStart) {
state = States.NodeModules;
}
else {
@@ -428,13 +390,36 @@ namespace ts.moduleSpecifiers {
});
}
function removeExtensionAndIndexPostFix(fileName: string, moduleResolutionKind: ModuleResolutionKind, addJsExtension: boolean): string {
function removeExtensionAndIndexPostFix(fileName: string, ending: Ending, options: CompilerOptions): string {
if (fileExtensionIs(fileName, Extension.Json)) return fileName;
const noExtension = removeFileExtension(fileName);
return addJsExtension
? noExtension + ".js"
: moduleResolutionKind === ModuleResolutionKind.NodeJs
? removeSuffix(noExtension, "/index")
: noExtension;
switch (ending) {
case Ending.Minimal:
return removeSuffix(noExtension, "/index");
case Ending.Index:
return noExtension;
case Ending.JsExtension:
return noExtension + getJavaScriptExtensionForFile(fileName, options);
default:
return Debug.assertNever(ending);
}
}
function getJavaScriptExtensionForFile(fileName: string, options: CompilerOptions): Extension {
const ext = extensionFromPath(fileName);
switch (ext) {
case Extension.Ts:
case Extension.Dts:
return Extension.Js;
case Extension.Tsx:
return options.jsx === JsxEmit.Preserve ? Extension.Jsx : Extension.Js;
case Extension.Js:
case Extension.Jsx:
case Extension.Json:
return ext;
default:
return Debug.assertNever(ext);
}
}
function getRelativePathIfInDirectory(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined {
+39 -14
View File
@@ -86,6 +86,7 @@ namespace ts {
visitNodes(cbNode, cbNodes, node.modifiers) ||
visitNode(cbNode, (<ShorthandPropertyAssignment>node).name) ||
visitNode(cbNode, (<ShorthandPropertyAssignment>node).questionToken) ||
visitNode(cbNode, (<ShorthandPropertyAssignment>node).exclamationToken) ||
visitNode(cbNode, (<ShorthandPropertyAssignment>node).equalsToken) ||
visitNode(cbNode, (<ShorthandPropertyAssignment>node).objectAssignmentInitializer);
case SyntaxKind.SpreadAssignment:
@@ -156,6 +157,7 @@ namespace ts {
visitNode(cbNode, (<FunctionLikeDeclaration>node).asteriskToken) ||
visitNode(cbNode, (<FunctionLikeDeclaration>node).name) ||
visitNode(cbNode, (<FunctionLikeDeclaration>node).questionToken) ||
visitNode(cbNode, (<FunctionLikeDeclaration>node).exclamationToken) ||
visitNodes(cbNode, cbNodes, (<FunctionLikeDeclaration>node).typeParameters) ||
visitNodes(cbNode, cbNodes, (<FunctionLikeDeclaration>node).parameters) ||
visitNode(cbNode, (<FunctionLikeDeclaration>node).type) ||
@@ -475,7 +477,7 @@ namespace ts {
case SyntaxKind.JSDocAugmentsTag:
return visitNode(cbNode, (<JSDocAugmentsTag>node).class);
case SyntaxKind.JSDocTemplateTag:
return visitNodes(cbNode, cbNodes, (<JSDocTemplateTag>node).typeParameters);
return visitNode(cbNode, (<JSDocTemplateTag>node).constraint) || visitNodes(cbNode, cbNodes, (<JSDocTemplateTag>node).typeParameters);
case SyntaxKind.JSDocTypedefTag:
if ((node as JSDocTypedefTag).typeExpression &&
(node as JSDocTypedefTag).typeExpression!.kind === SyntaxKind.JSDocTypeExpression) {
@@ -515,7 +517,7 @@ namespace ts {
performance.mark("beforeParse");
let result: SourceFile;
if (languageVersion === ScriptTarget.JSON) {
result = Parser.parseJsonText(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes);
result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, ScriptKind.JSON);
}
else {
result = Parser.parseSourceFile(fileName, sourceText, languageVersion, /*syntaxCursor*/ undefined, setParentNodes, scriptKind);
@@ -689,8 +691,12 @@ namespace ts {
if (scriptKind === ScriptKind.JSON) {
const result = parseJsonText(fileName, sourceText, languageVersion, syntaxCursor, setParentNodes);
convertToObjectWorker(result, result.parseDiagnostics, /*returnValue*/ false, /*knownRootOptions*/ undefined, /*jsonConversionNotifier*/ undefined);
result.referencedFiles = emptyArray;
result.typeReferenceDirectives = emptyArray;
result.libReferenceDirectives = emptyArray;
result.amdDependencies = emptyArray;
result.hasNoDefaultLib = false;
result.pragmas = emptyMap;
return result;
}
@@ -2373,8 +2379,10 @@ namespace ts {
}
function parseJSDocType(): TypeNode {
scanner.setInJSDocType(true);
const dotdotdot = parseOptionalToken(SyntaxKind.DotDotDotToken);
let type = parseType();
let type = parseTypeOrTypePredicate();
scanner.setInJSDocType(false);
if (dotdotdot) {
const variadic = createNode(SyntaxKind.JSDocVariadicType, dotdotdot.pos) as JSDocVariadicType;
variadic.type = type;
@@ -4465,7 +4473,7 @@ namespace ts {
}
else {
const argument = allowInAnd(parseExpression);
if (isStringOrNumericLiteral(argument)) {
if (isStringOrNumericLiteralLike(argument)) {
argument.text = internIdentifier(argument.text);
}
indexedAccess.argumentExpression = argument;
@@ -4707,8 +4715,10 @@ namespace ts {
const asteriskToken = parseOptionalToken(SyntaxKind.AsteriskToken);
const tokenIsIdentifier = isIdentifier();
node.name = parsePropertyName();
// Disallowing of optional property assignments happens in the grammar checker.
// Disallowing of optional property assignments and definite assignment assertion happens in the grammar checker.
(<MethodDeclaration>node).questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
(<MethodDeclaration>node).exclamationToken = parseOptionalToken(SyntaxKind.ExclamationToken);
if (asteriskToken || token() === SyntaxKind.OpenParenToken || token() === SyntaxKind.LessThanToken) {
return parseMethodDeclaration(<MethodDeclaration>node, asteriskToken);
}
@@ -6507,6 +6517,25 @@ namespace ts {
}
}
function skipWhitespaceOrAsterisk(): void {
if (token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) {
if (lookAhead(isNextNonwhitespaceTokenEndOfFile)) {
return; // Don't skip whitespace prior to EoF (or end of comment) - that shouldn't be included in any node's range
}
}
let precedingLineBreak = scanner.hasPrecedingLineBreak();
while ((precedingLineBreak && token() === SyntaxKind.AsteriskToken) || token() === SyntaxKind.WhitespaceTrivia || token() === SyntaxKind.NewLineTrivia) {
if (token() === SyntaxKind.NewLineTrivia) {
precedingLineBreak = true;
}
else if (token() === SyntaxKind.AsteriskToken) {
precedingLineBreak = false;
}
nextJSDocToken();
}
}
function parseTag(indent: number) {
Debug.assert(token() === SyntaxKind.AtToken);
const atToken = <AtToken>createNode(SyntaxKind.AtToken, scanner.getTokenPos());
@@ -6514,7 +6543,7 @@ namespace ts {
nextJSDocToken();
const tagName = parseJSDocIdentifierName();
skipWhitespace();
skipWhitespaceOrAsterisk();
let tag: JSDocTag | undefined;
switch (tagName.escapedText) {
@@ -6658,7 +6687,7 @@ namespace ts {
}
function tryParseTypeExpression(): JSDocTypeExpression | undefined {
skipWhitespace();
skipWhitespaceOrAsterisk();
return token() === SyntaxKind.OpenBraceToken ? parseJSDocTypeExpression() : undefined;
}
@@ -6698,7 +6727,7 @@ namespace ts {
function parseParameterOrPropertyTag(atToken: AtToken, tagName: Identifier, target: PropertyLikeParse, indent: number | undefined): JSDocParameterTag | JSDocPropertyTag {
let typeExpression = tryParseTypeExpression();
let isNameFirst = !typeExpression;
skipWhitespace();
skipWhitespaceOrAsterisk();
const { name, isBracketed } = parseBracketNameInPropertyAndParamTag();
skipWhitespace();
@@ -6833,7 +6862,7 @@ namespace ts {
function parseTypedefTag(atToken: AtToken, tagName: Identifier, indent: number): JSDocTypedefTag {
const typeExpression = tryParseTypeExpression();
skipWhitespace();
skipWhitespaceOrAsterisk();
const typedefTag = <JSDocTypedefTag>createNode(SyntaxKind.JSDocTypedefTag, atToken.pos);
typedefTag.atToken = atToken;
@@ -7049,13 +7078,10 @@ namespace ts {
typeParameters.push(typeParameter);
} while (parseOptionalJsdoc(SyntaxKind.CommaToken));
if (constraint) {
first(typeParameters).constraint = constraint.type;
}
const result = <JSDocTemplateTag>createNode(SyntaxKind.JSDocTemplateTag, atToken.pos);
result.atToken = atToken;
result.tagName = tagName;
result.constraint = constraint;
result.typeParameters = createNodeArray(typeParameters, typeParametersPos);
finishNode(result);
return result;
@@ -7738,7 +7764,6 @@ namespace ts {
}
}
/*@internal*/
type PragmaDiagnosticReporter = (pos: number, length: number, message: DiagnosticMessage) => void;
/*@internal*/
+58 -63
View File
@@ -67,19 +67,24 @@ namespace ts {
}
export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost {
return createCompilerHostWorker(options, setParentNodes);
}
/*@internal*/
// TODO(shkamat): update this after reworking ts build API
export function createCompilerHostWorker(options: CompilerOptions, setParentNodes?: boolean, system = sys): CompilerHost {
const existingDirectories = createMap<boolean>();
function getCanonicalFileName(fileName: string): string {
// if underlying system can distinguish between two files whose names differs only in cases then file name already in canonical form.
// otherwise use toLowerCase as a canonical form.
return sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
return system.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
}
function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile | undefined {
let text: string | undefined;
try {
performance.mark("beforeIORead");
text = sys.readFile(fileName, options.charset);
text = system.readFile(fileName, options.charset);
performance.mark("afterIORead");
performance.measure("I/O Read", "beforeIORead", "afterIORead");
}
@@ -97,7 +102,7 @@ namespace ts {
if (existingDirectories.has(directoryPath)) {
return true;
}
if (sys.directoryExists(directoryPath)) {
if (system.directoryExists(directoryPath)) {
existingDirectories.set(directoryPath, true);
return true;
}
@@ -108,7 +113,7 @@ namespace ts {
if (directoryPath.length > getRootLength(directoryPath) && !directoryExists(directoryPath)) {
const parentDirectory = getDirectoryPath(directoryPath);
ensureDirectoriesExist(parentDirectory);
sys.createDirectory(directoryPath);
system.createDirectory(directoryPath);
}
}
@@ -119,8 +124,8 @@ namespace ts {
outputFingerprints = createMap<OutputFingerprint>();
}
const hash = sys.createHash!(data); // TODO: GH#18217
const mtimeBefore = sys.getModifiedTime!(fileName); // TODO: GH#18217
const hash = system.createHash!(data); // TODO: GH#18217
const mtimeBefore = system.getModifiedTime!(fileName); // TODO: GH#18217
if (mtimeBefore) {
const fingerprint = outputFingerprints.get(fileName);
@@ -133,9 +138,9 @@ namespace ts {
}
}
sys.writeFile(fileName, data, writeByteOrderMark);
system.writeFile(fileName, data, writeByteOrderMark);
const mtimeAfter = sys.getModifiedTime!(fileName) || missingFileModifiedTime; // TODO: GH#18217
const mtimeAfter = system.getModifiedTime!(fileName) || missingFileModifiedTime; // TODO: GH#18217
outputFingerprints.set(fileName, {
hash,
@@ -149,11 +154,11 @@ namespace ts {
performance.mark("beforeIOWrite");
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
if (isWatchSet(options) && sys.createHash && sys.getModifiedTime) {
if (isWatchSet(options) && system.createHash && system.getModifiedTime) {
writeFileIfUpdated(fileName, data, writeByteOrderMark);
}
else {
sys.writeFile(fileName, data, writeByteOrderMark);
system.writeFile(fileName, data, writeByteOrderMark);
}
performance.mark("afterIOWrite");
@@ -167,32 +172,29 @@ namespace ts {
}
function getDefaultLibLocation(): string {
return getDirectoryPath(normalizePath(sys.getExecutingFilePath()));
return getDirectoryPath(normalizePath(system.getExecutingFilePath()));
}
const newLine = getNewLineCharacter(options);
const realpath = sys.realpath && ((path: string) => sys.realpath!(path));
const newLine = getNewLineCharacter(options, () => system.newLine);
const realpath = system.realpath && ((path: string) => system.realpath!(path));
return {
getSourceFile,
getDefaultLibLocation,
getDefaultLibFileName: options => combinePaths(getDefaultLibLocation(), getDefaultLibFileName(options)),
writeFile,
getCurrentDirectory: memoize(() => sys.getCurrentDirectory()),
useCaseSensitiveFileNames: () => sys.useCaseSensitiveFileNames,
getCurrentDirectory: memoize(() => system.getCurrentDirectory()),
useCaseSensitiveFileNames: () => system.useCaseSensitiveFileNames,
getCanonicalFileName,
getNewLine: () => newLine,
fileExists: fileName => sys.fileExists(fileName),
readFile: fileName => sys.readFile(fileName),
trace: (s: string) => sys.write(s + newLine),
directoryExists: directoryName => sys.directoryExists(directoryName),
getEnvironmentVariable: name => sys.getEnvironmentVariable ? sys.getEnvironmentVariable(name) : "",
getDirectories: (path: string) => sys.getDirectories(path),
fileExists: fileName => system.fileExists(fileName),
readFile: fileName => system.readFile(fileName),
trace: (s: string) => system.write(s + newLine),
directoryExists: directoryName => system.directoryExists(directoryName),
getEnvironmentVariable: name => system.getEnvironmentVariable ? system.getEnvironmentVariable(name) : "",
getDirectories: (path: string) => system.getDirectories(path),
realpath,
readDirectory: (path, extensions, include, exclude, depth) => sys.readDirectory(path, extensions, include, exclude, depth),
getModifiedTime: sys.getModifiedTime && (path => sys.getModifiedTime!(path)),
setModifiedTime: sys.setModifiedTime && ((path, date) => sys.setModifiedTime!(path, date)),
deleteFile: sys.deleteFile && (path => sys.deleteFile!(path))
readDirectory: (path, extensions, include, exclude, depth) => system.readDirectory(path, extensions, include, exclude, depth)
};
}
@@ -951,8 +953,11 @@ namespace ts {
// If we change our policy of rechecking failed lookups on each program create,
// we should adjust the value returned here.
function moduleNameResolvesToAmbientModuleInNonModifiedFile(moduleName: string, oldProgramState: OldProgramState): boolean {
if (!oldProgramState.program) {
return false;
}
const resolutionToFile = getResolvedModule(oldProgramState.oldSourceFile!, moduleName); // TODO: GH#18217
const resolvedFile = resolutionToFile && oldProgramState.program && oldProgramState.program.getSourceFile(resolutionToFile.resolvedFileName);
const resolvedFile = resolutionToFile && oldProgramState.program.getSourceFile(resolutionToFile.resolvedFileName);
if (resolutionToFile && resolvedFile && !resolvedFile.externalModuleIndicator) {
// In the old program, we resolved to an ambient module that was in the same
// place as we expected to find an actual module file.
@@ -960,16 +965,11 @@ namespace ts {
// because the normal module resolution algorithm will find this anyway.
return false;
}
const ambientModule = oldProgramState.program && oldProgramState.program.getTypeChecker().tryFindAmbientModuleWithoutAugmentations(moduleName);
if (!(ambientModule && ambientModule.declarations)) {
return false;
}
// at least one of declarations should come from non-modified source file
const firstUnmodifiedFile = forEach(ambientModule.declarations, d => {
const f = getSourceFileOfNode(d);
return !contains(oldProgramState.modifiedFilePaths, f.path) && f;
});
const firstUnmodifiedFile = oldProgramState.program.getSourceFiles().find(
f => !contains(oldProgramState.modifiedFilePaths, f.path) && contains(f.ambientModuleNames, moduleName)
);
if (!firstUnmodifiedFile) {
return false;
@@ -1237,6 +1237,7 @@ namespace ts {
getSourceFile: program.getSourceFile,
getSourceFileByPath: program.getSourceFileByPath,
getSourceFiles: program.getSourceFiles,
getLibFileFromReference: program.getLibFileFromReference,
isSourceFileFromExternalLibrary,
writeFile: writeFileCallback || (
(fileName, data, writeByteOrderMark, onError, sourceFiles) => host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles)),
@@ -1256,8 +1257,6 @@ namespace ts {
}
function getProjectReferences() {
if (!resolvedProjectReferences) return;
return resolvedProjectReferences;
}
@@ -1476,10 +1475,7 @@ namespace ts {
function getSemanticDiagnosticsForFileNoCache(sourceFile: SourceFile, cancellationToken: CancellationToken): Diagnostic[] | undefined {
return runWithCancellationToken(() => {
// If skipLibCheck is enabled, skip reporting errors if file is a declaration file.
// If skipDefaultLibCheck is enabled, skip reporting errors if file contains a
// '/// <reference no-default-lib="true"/>' directive.
if (options.skipLibCheck && sourceFile.isDeclarationFile || options.skipDefaultLibCheck && sourceFile.hasNoDefaultLib) {
if (skipTypeChecking(sourceFile, options)) {
return emptyArray;
}
@@ -2321,27 +2317,20 @@ namespace ts {
}
function computeCommonSourceDirectory(sourceFiles: SourceFile[]): string {
const fileNames: string[] = [];
for (const file of sourceFiles) {
if (!file.isDeclarationFile) {
fileNames.push(file.fileName);
}
}
const fileNames = mapDefined(sourceFiles, file => file.isDeclarationFile ? undefined : file.fileName);
return computeCommonSourceDirectoryOfFilenames(fileNames, currentDirectory, getCanonicalFileName);
}
function checkSourceFilesBelongToPath(sourceFiles: SourceFile[], rootDirectory: string): boolean {
function checkSourceFilesBelongToPath(sourceFiles: ReadonlyArray<SourceFile>, rootDirectory: string): boolean {
let allFilesBelongToPath = true;
if (sourceFiles) {
const absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory));
const absoluteRootDirectoryPath = host.getCanonicalFileName(getNormalizedAbsolutePath(rootDirectory, currentDirectory));
for (const sourceFile of sourceFiles) {
if (!sourceFile.isDeclarationFile) {
const absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory));
if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, sourceFile.fileName, rootDirectory));
allFilesBelongToPath = false;
}
for (const sourceFile of sourceFiles) {
if (!sourceFile.isDeclarationFile) {
const absoluteSourceFilePath = host.getCanonicalFileName(getNormalizedAbsolutePath(sourceFile.fileName, currentDirectory));
if (absoluteSourceFilePath.indexOf(absoluteRootDirectoryPath) !== 0) {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_under_rootDir_1_rootDir_is_expected_to_contain_all_source_files, sourceFile.fileName, rootDirectory));
allFilesBelongToPath = false;
}
}
}
@@ -2441,12 +2430,14 @@ namespace ts {
}
// List of collected files is complete; validate exhautiveness if this is a project with a file list
if (options.composite && rootNames.length < files.length) {
const normalizedRootNames = rootNames.map(r => normalizePath(r).toLowerCase());
const sourceFiles = files.filter(f => !f.isDeclarationFile).map(f => normalizePath(f.path).toLowerCase());
for (const file of sourceFiles) {
if (normalizedRootNames.every(r => r !== file)) {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern, file));
if (options.composite) {
const sourceFiles = files.filter(f => !f.isDeclarationFile);
if (rootNames.length < sourceFiles.length) {
const normalizedRootNames = rootNames.map(r => normalizePath(r).toLowerCase());
for (const file of sourceFiles.map(f => normalizePath(f.path).toLowerCase())) {
if (normalizedRootNames.indexOf(file) === -1) {
programDiagnostics.add(createCompilerDiagnostic(Diagnostics.File_0_is_not_in_project_file_list_Projects_must_list_all_files_or_use_an_include_pattern, file));
}
}
}
}
@@ -2852,7 +2843,6 @@ namespace ts {
switch (extension) {
case Extension.Ts:
case Extension.Dts:
case Extension.Json: // Since module is resolved to json file only when --resolveJsonModule, we dont need further check
// These are always allowed.
return undefined;
case Extension.Tsx:
@@ -2861,6 +2851,8 @@ namespace ts {
return needJsx() || needAllowJs();
case Extension.Js:
return needAllowJs();
case Extension.Json:
return needResolveJsonModule();
}
function needJsx() {
@@ -2869,6 +2861,9 @@ namespace ts {
function needAllowJs() {
return options.allowJs || !getStrictOptionValue(options, "noImplicitAny") ? undefined : Diagnostics.Could_not_find_a_declaration_file_for_module_0_1_implicitly_has_an_any_type;
}
function needResolveJsonModule() {
return options.resolveJsonModule ? undefined : Diagnostics.Module_0_was_resolved_to_1_but_resolveJsonModule_is_not_used;
}
}
function getModuleNames({ imports, moduleAugmentations }: SourceFile): string[] {
+1 -1
View File
@@ -419,7 +419,7 @@ namespace ts {
function getDirectoryToWatchFromFailedLookupLocationDirectory(dir: string, dirPath: Path) {
// If directory path contains node module, get the most parent node_modules directory for watching
while (stringContains(dirPath, "/node_modules/")) {
while (stringContains(dirPath, nodeModulesPathPart)) {
dir = getDirectoryPath(dir);
dirPath = getDirectoryPath(dirPath);
}
+35
View File
@@ -42,6 +42,8 @@ namespace ts {
setScriptTarget(scriptTarget: ScriptTarget): void;
setLanguageVariant(variant: LanguageVariant): void;
setTextPos(textPos: number): void;
/* @internal */
setInJSDocType(inType: boolean): void;
// Invokes the provided callback then unconditionally restores the scanner to the state it
// was in immediately prior to invoking the callback. The result of invoking the callback
// is returned from this function.
@@ -824,6 +826,8 @@ namespace ts {
let tokenValue!: string;
let tokenFlags: TokenFlags;
let inJSDocType = 0;
setText(text, start, length);
return {
@@ -854,6 +858,7 @@ namespace ts {
setLanguageVariant,
setOnError,
setTextPos,
setInJSDocType,
tryScan,
lookAhead,
scanRange,
@@ -1350,6 +1355,7 @@ namespace ts {
function scan(): SyntaxKind {
startPos = pos;
tokenFlags = 0;
let asteriskSeen = false;
while (true) {
tokenPos = pos;
if (pos >= end) {
@@ -1390,6 +1396,24 @@ namespace ts {
case CharacterCodes.verticalTab:
case CharacterCodes.formFeed:
case CharacterCodes.space:
case CharacterCodes.nonBreakingSpace:
case CharacterCodes.ogham:
case CharacterCodes.enQuad:
case CharacterCodes.emQuad:
case CharacterCodes.enSpace:
case CharacterCodes.emSpace:
case CharacterCodes.threePerEmSpace:
case CharacterCodes.fourPerEmSpace:
case CharacterCodes.sixPerEmSpace:
case CharacterCodes.figureSpace:
case CharacterCodes.punctuationSpace:
case CharacterCodes.thinSpace:
case CharacterCodes.hairSpace:
case CharacterCodes.zeroWidthSpace:
case CharacterCodes.narrowNoBreakSpace:
case CharacterCodes.mathematicalSpace:
case CharacterCodes.ideographicSpace:
case CharacterCodes.byteOrderMark:
if (skipTrivia) {
pos++;
continue;
@@ -1447,6 +1471,11 @@ namespace ts {
return pos += 2, token = SyntaxKind.AsteriskAsteriskToken;
}
pos++;
if (inJSDocType && !asteriskSeen && (tokenFlags & TokenFlags.PrecedingLineBreak)) {
// decoration at the start of a JSDoc comment line
asteriskSeen = true;
continue;
}
return token = SyntaxKind.AsteriskToken;
case CharacterCodes.plus:
if (text.charCodeAt(pos + 1) === CharacterCodes.plus) {
@@ -1933,6 +1962,7 @@ namespace ts {
function scanJSDocToken(): JsDocSyntaxKind {
startPos = tokenPos = pos;
tokenFlags = 0;
if (pos >= end) {
return token = SyntaxKind.EndOfFileToken;
}
@@ -1952,6 +1982,7 @@ namespace ts {
return token = SyntaxKind.AtToken;
case CharacterCodes.lineFeed:
case CharacterCodes.carriageReturn:
tokenFlags |= TokenFlags.PrecedingLineBreak;
return token = SyntaxKind.NewLineTrivia;
case CharacterCodes.asterisk:
return token = SyntaxKind.AsteriskToken;
@@ -2076,5 +2107,9 @@ namespace ts {
tokenValue = undefined!;
tokenFlags = 0;
}
function setInJSDocType(inType: boolean) {
inJSDocType += inType ? 1 : -1;
}
}
}
+1 -2
View File
@@ -756,8 +756,7 @@ namespace ts {
if (recursive) {
return watchDirectoryRecursively(directoryName, callback);
}
watchDirectory(directoryName, callback);
return undefined!; // TODO: GH#18217
return watchDirectory(directoryName, callback);
};
}
+44 -9
View File
@@ -33,7 +33,7 @@ namespace ts {
let needsScopeFixMarker = false;
let resultHasScopeMarker = false;
let enclosingDeclaration: Node;
let necessaryTypeRefernces: Map<true> | undefined;
let necessaryTypeReferences: Map<true> | undefined;
let lateMarkedStatements: LateVisibilityPaintedStatement[] | undefined;
let lateStatementReplacementMap: Map<VisitResult<LateVisibilityPaintedStatement>>;
let suppressNewDiagnosticContexts: boolean;
@@ -53,6 +53,7 @@ namespace ts {
let currentSourceFile: SourceFile;
let refs: Map<SourceFile>;
let libs: Map<boolean>;
const resolver = context.getEmitResolver();
const options = context.getCompilerOptions();
const newLine = getNewLineCharacter(options);
@@ -63,9 +64,9 @@ namespace ts {
if (!typeReferenceDirectives) {
return;
}
necessaryTypeRefernces = necessaryTypeRefernces || createMap<true>();
necessaryTypeReferences = necessaryTypeReferences || createMap<true>();
for (const ref of typeReferenceDirectives) {
necessaryTypeRefernces.set(ref, true);
necessaryTypeReferences.set(ref, true);
}
}
@@ -163,6 +164,7 @@ namespace ts {
if (node.kind === SyntaxKind.Bundle) {
isBundledEmit = true;
refs = createMap<SourceFile>();
libs = createMap<boolean>();
let hasNoDefaultLib = false;
const bundle = createBundle(map(node.sourceFiles,
sourceFile => {
@@ -177,6 +179,7 @@ namespace ts {
needsScopeFixMarker = false;
resultHasScopeMarker = false;
collectReferences(sourceFile, refs);
collectLibs(sourceFile, libs);
if (isExternalModule(sourceFile)) {
resultHasExternalModuleIndicator = false; // unused in external module bundle emit (all external modules are within module blocks, therefore are known to be modules)
needsDeclare = false;
@@ -200,6 +203,7 @@ namespace ts {
}));
bundle.syntheticFileReferences = [];
bundle.syntheticTypeReferences = getFileReferencesForUsedTypeReferences();
bundle.syntheticLibReferences = getLibReferences();
bundle.hasNoDefaultLib = hasNoDefaultLib;
const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!));
const referenceVisitor = mapReferencesIntoArray(bundle.syntheticFileReferences as FileReference[], outputFilePath);
@@ -219,8 +223,9 @@ namespace ts {
suppressNewDiagnosticContexts = false;
lateMarkedStatements = undefined;
lateStatementReplacementMap = createMap();
necessaryTypeRefernces = undefined;
necessaryTypeReferences = undefined;
refs = collectReferences(currentSourceFile, createMap());
libs = collectLibs(currentSourceFile, createMap());
const references: FileReference[] = [];
const outputFilePath = getDirectoryPath(normalizeSlashes(getOutputPathsFor(node, host, /*forceDtsPaths*/ true).declarationFilePath!));
const referenceVisitor = mapReferencesIntoArray(references, outputFilePath);
@@ -231,12 +236,16 @@ namespace ts {
if (isExternalModule(node) && (!resultHasExternalModuleIndicator || (needsScopeFixMarker && !resultHasScopeMarker))) {
combinedStatements = setTextRange(createNodeArray([...combinedStatements, createExportDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, createNamedExports([]), /*moduleSpecifier*/ undefined)]), combinedStatements);
}
const updated = updateSourceFileNode(node, combinedStatements, /*isDeclarationFile*/ true, references, getFileReferencesForUsedTypeReferences(), node.hasNoDefaultLib);
const updated = updateSourceFileNode(node, combinedStatements, /*isDeclarationFile*/ true, references, getFileReferencesForUsedTypeReferences(), node.hasNoDefaultLib, getLibReferences());
updated.exportedModulesFromDeclarationEmit = exportedModulesFromDeclarationEmit;
return updated;
function getLibReferences() {
return map(arrayFrom(libs.keys()), lib => ({ fileName: lib, pos: -1, end: -1 }));
}
function getFileReferencesForUsedTypeReferences() {
return necessaryTypeRefernces ? mapDefined(arrayFrom(necessaryTypeRefernces.keys()), getFileReferenceForTypeName) : [];
return necessaryTypeReferences ? mapDefined(arrayFrom(necessaryTypeReferences.keys()), getFileReferenceForTypeName) : [];
}
function getFileReferenceForTypeName(typeName: string): FileReference | undefined {
@@ -297,6 +306,16 @@ namespace ts {
return ret;
}
function collectLibs(sourceFile: SourceFile, ret: Map<boolean>) {
forEach(sourceFile.libReferenceDirectives, ref => {
const lib = host.getLibFileFromReference(ref);
if (lib) {
ret.set(ref.fileName.toLocaleLowerCase(), true);
}
});
return ret;
}
function filterBindingPatternInitializers(name: BindingName) {
if (name.kind === SyntaxKind.Identifier) {
return name;
@@ -952,7 +971,7 @@ namespace ts {
}
case SyntaxKind.FunctionDeclaration: {
// Generators lose their generator-ness, excepting their return type
return cleanup(updateFunctionDeclaration(
const clean = cleanup(updateFunctionDeclaration(
input,
/*decorators*/ undefined,
ensureModifiers(input, isPrivate),
@@ -963,6 +982,21 @@ namespace ts {
ensureType(input, input.type),
/*body*/ undefined
));
if (clean && resolver.isJSContainerFunctionDeclaration(input)) {
const declarations = mapDefined(resolver.getPropertiesOfContainerFunction(input), p => {
if (!isPropertyAccessExpression(p.valueDeclaration)) {
return undefined;
}
const type = resolver.createTypeOfDeclaration(p.valueDeclaration, enclosingDeclaration, declarationEmitNodeBuilderFlags, symbolTracker);
const varDecl = createVariableDeclaration(unescapeLeadingUnderscores(p.escapedName), type, /*initializer*/ undefined);
return createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList([varDecl]));
});
const namespaceDecl = createModuleDeclaration(/*decorators*/ undefined, ensureModifiers(input, isPrivate), input.name!, createModuleBlock(declarations), NodeFlags.Namespace);
return [clean, namespaceDecl];
}
else {
return clean;
}
}
case SyntaxKind.ModuleDeclaration: {
needsDeclare = false;
@@ -1290,12 +1324,13 @@ namespace ts {
}
type CanHaveLiteralInitializer = VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration;
function canHaveLiteralInitializer(node: Node): node is CanHaveLiteralInitializer {
function canHaveLiteralInitializer(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.VariableDeclaration:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
return !hasModifier(node, ModifierFlags.Private);
case SyntaxKind.Parameter:
case SyntaxKind.VariableDeclaration:
return true;
}
return false;
+1 -1
View File
@@ -447,7 +447,7 @@ namespace ts {
const argumentExpression = ensureIdentifier(flattenContext, visitNode(propertyName.expression, flattenContext.visitor), /*reuseIdentifierExpressions*/ false, /*location*/ propertyName);
return createElementAccess(value, argumentExpression);
}
else if (isStringOrNumericLiteral(propertyName)) {
else if (isStringOrNumericLiteralLike(propertyName)) {
const argumentExpression = getSynthesizedClone(propertyName);
argumentExpression.text = argumentExpression.text;
return createElementAccess(value, argumentExpression);
+14 -6
View File
@@ -2060,14 +2060,11 @@ namespace ts {
setTextRange(declarationList, node);
setCommentRange(declarationList, node);
// If the first or last declaration is a binding pattern, we need to modify
// the source map range for the declaration list.
if (node.transformFlags & TransformFlags.ContainsBindingPattern
&& (isBindingPattern(node.declarations[0].name) || isBindingPattern(last(node.declarations).name))) {
// If the first or last declaration is a binding pattern, we need to modify
// the source map range for the declaration list.
const firstDeclaration = firstOrUndefined(declarations);
if (firstDeclaration) {
setSourceMapRange(declarationList, createRange(firstDeclaration.pos, last(declarations).end));
}
setSourceMapRange(declarationList, getRangeUnion(declarations));
}
return declarationList;
@@ -2075,6 +2072,17 @@ namespace ts {
return visitEachChild(node, visitor, context);
}
function getRangeUnion(declarations: ReadonlyArray<Node>): TextRange {
// declarations may not be sorted by position.
// pos should be the minimum* position over all nodes (that's not -1), end should be the maximum end over all nodes.
let pos = -1, end = -1;
for (const node of declarations) {
pos = pos === -1 ? node.pos : node.pos === -1 ? pos : Math.min(pos, node.pos);
end = Math.max(end, node.end);
}
return createRange(pos, end);
}
/**
* Gets a value indicating whether we should emit an explicit initializer for a variable
* declaration in a `let` declaration list.
+415 -407
View File
File diff suppressed because it is too large Load Diff
+48 -25
View File
@@ -759,6 +759,7 @@ namespace ts {
kind: SyntaxKind.TypeParameter;
parent: DeclarationWithTypeParameterChildren | InferTypeNode;
name: Identifier;
/** Note: Consider calling `getEffectiveConstraintOfTypeParameter` */
constraint?: TypeNode;
default?: TypeNode;
@@ -882,6 +883,7 @@ namespace ts {
kind: SyntaxKind.ShorthandPropertyAssignment;
name: Identifier;
questionToken?: QuestionToken;
exclamationToken?: ExclamationToken;
// used when ObjectLiteralExpression is used in ObjectAssignmentPattern
// it is grammar error to appear in actual object initializer
equalsToken?: Token<SyntaxKind.EqualsToken>;
@@ -940,6 +942,7 @@ namespace ts {
asteriskToken?: AsteriskToken;
questionToken?: QuestionToken;
exclamationToken?: ExclamationToken;
body?: Block | Expression;
}
@@ -1081,7 +1084,7 @@ namespace ts {
export interface TypePredicateNode extends TypeNode {
kind: SyntaxKind.TypePredicate;
parent: SignatureDeclaration;
parent: SignatureDeclaration | JSDocTypeExpression;
parameterName: Identifier | ThisTypeNode;
type: TypeNode;
}
@@ -2363,6 +2366,7 @@ namespace ts {
export interface JSDocTemplateTag extends JSDocTag {
kind: SyntaxKind.JSDocTemplateTag;
constraint: TypeNode | undefined;
typeParameters: NodeArray<TypeParameterDeclaration>;
}
@@ -2630,7 +2634,7 @@ namespace ts {
/* @internal */ ambientModuleNames: ReadonlyArray<string>;
/* @internal */ checkJsDirective?: CheckJsDirective;
/* @internal */ version: string;
/* @internal */ pragmas: PragmaMap;
/* @internal */ pragmas: ReadonlyPragmaMap;
/* @internal */ localJsxNamespace?: __String;
/* @internal */ localJsxFactory?: EntityName;
@@ -2646,6 +2650,7 @@ namespace ts {
sourceFiles: ReadonlyArray<SourceFile>;
/* @internal */ syntheticFileReferences?: ReadonlyArray<FileReference>;
/* @internal */ syntheticTypeReferences?: ReadonlyArray<FileReference>;
/* @internal */ syntheticLibReferences?: ReadonlyArray<FileReference>;
/* @internal */ hasNoDefaultLib?: boolean;
}
@@ -2909,6 +2914,8 @@ namespace ts {
getBaseTypes(type: InterfaceType): BaseType[];
getBaseTypeOfLiteralType(type: Type): Type;
getWidenedType(type: Type): Type;
/* @internal */
getPromisedTypeOfPromise(promise: Type, errorNode?: Node): Type | undefined;
getReturnTypeOfSignature(signature: Signature): Type;
/**
* Gets the type of a parameter at a given position in a signature.
@@ -2973,6 +2980,7 @@ namespace ts {
getAugmentedPropertiesOfType(type: Type): Symbol[];
getRootSymbols(symbol: Symbol): Symbol[];
getContextualType(node: Expression): Type | undefined;
/* @internal */ getContextualTypeForObjectLiteralElement(element: ObjectLiteralElementLike): Type | undefined;
/* @internal */ getContextualTypeForArgumentAtIndex(call: CallLikeExpression, argIndex: number): Type | undefined;
/* @internal */ getContextualTypeForJsxAttribute(attribute: JsxAttribute | JsxSpreadAttribute): Type | undefined;
/* @internal */ isContextSensitive(node: Expression | MethodDeclaration | ObjectLiteralElementLike | JsxAttributeLike): boolean;
@@ -3091,6 +3099,10 @@ namespace ts {
*/
/* @internal */ getAccessibleSymbolChain(symbol: Symbol, enclosingDeclaration: Node | undefined, meaning: SymbolFlags, useOnlyExternalAliasing: boolean): Symbol[] | undefined;
/* @internal */ getTypePredicateOfSignature(signature: Signature): TypePredicate;
/**
* An external module with an 'export =' declaration resolves to the target of the 'export =' declaration,
* and an external module with no 'export =' declaration resolves to the module itself.
*/
/* @internal */ resolveExternalModuleSymbol(symbol: Symbol): Symbol;
/** @param node A location where we might consider accessing `this`. Not necessarily a ThisExpression. */
/* @internal */ tryGetThisTypeAt(node: Node): Type | undefined;
@@ -3108,6 +3120,8 @@ namespace ts {
* and the operation is cancelled, then it should be discarded, otherwise it is safe to keep.
*/
runWithCancellationToken<T>(token: CancellationToken, cb: (checker: TypeChecker) => T): T;
/* @internal */ getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): ReadonlyArray<TypeParameter> | undefined;
}
/* @internal */
@@ -3368,7 +3382,9 @@ namespace ts {
isImplementationOfOverload(node: FunctionLike): boolean | undefined;
isRequiredInitializedParameter(node: ParameterDeclaration): boolean;
isOptionalUninitializedParameterProperty(node: ParameterDeclaration): boolean;
createTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean): TypeNode | undefined;
isJSContainerFunctionDeclaration(node: FunctionDeclaration): boolean;
getPropertiesOfContainerFunction(node: Declaration): Symbol[];
createTypeOfDeclaration(declaration: AccessorDeclaration | VariableLikeDeclaration | PropertyAccessExpression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker, addUndefined?: boolean): TypeNode | undefined;
createReturnTypeOfSignatureDeclaration(signatureDeclaration: SignatureDeclaration, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker): TypeNode | undefined;
createTypeOfExpression(expr: Expression, enclosingDeclaration: Node, flags: NodeBuilderFlags, tracker: SymbolTracker): TypeNode | undefined;
createLiteralConstValue(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): Expression;
@@ -3427,8 +3443,8 @@ namespace ts {
Enum = RegularEnum | ConstEnum,
Variable = FunctionScopedVariable | BlockScopedVariable,
Value = Variable | Property | EnumMember | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor | JSContainer,
Type = Class | Interface | Enum | EnumMember | TypeLiteral | ObjectLiteral | TypeParameter | TypeAlias | JSContainer,
Value = Variable | Property | EnumMember | ObjectLiteral | Function | Class | Enum | ValueModule | Method | GetAccessor | SetAccessor | JSContainer,
Type = Class | Interface | Enum | EnumMember | TypeLiteral | TypeParameter | TypeAlias | JSContainer,
Namespace = ValueModule | NamespaceModule | Enum,
Module = ValueModule | NamespaceModule,
Accessor = GetAccessor | SetAccessor,
@@ -3449,7 +3465,7 @@ namespace ts {
InterfaceExcludes = Type & ~(Interface | Class),
RegularEnumExcludes = (Value | Type) & ~(RegularEnum | ValueModule), // regular enums merge only with regular enums and modules
ConstEnumExcludes = (Value | Type) & ~ConstEnum, // const enums merge only with const enums
ValueModuleExcludes = Value & ~(Function | Class | RegularEnum | ValueModule),
ValueModuleExcludes = Value & ~(Function | Class | RegularEnum | ValueModule | JSContainer),
NamespaceModuleExcludes = 0,
MethodExcludes = Value & ~Method,
GetAccessorExcludes = Value & ~SetAccessor,
@@ -3666,6 +3682,7 @@ namespace ts {
superCall?: SuperCall; // Cached first super-call found in the constructor. Used in checking whether super is called before this-accessing
switchTypes?: Type[]; // Cached array of switch case expression types
jsxNamespace?: Symbol | false; // Resolved jsx namespace symbol for this node
contextFreeType?: Type; // Cached context-free type used by the first pass of inference; used when a function's return is partially contextually sensitive
}
export const enum TypeFlags {
@@ -3943,6 +3960,7 @@ namespace ts {
constraintType?: Type;
templateType?: Type;
modifiersType?: Type;
resolvedApparentType?: Type;
}
export interface EvolvingArrayType extends ObjectType {
@@ -4159,9 +4177,8 @@ namespace ts {
/* @internal */
export const enum InferenceFlags {
None = 0, // No special inference behaviors
InferUnionTypes = 1 << 0, // Infer union types for disjoint candidates (otherwise unknownType)
NoDefault = 1 << 1, // Infer unknownType for no inferences (otherwise anyType or emptyObjectType)
AnyDefault = 1 << 2, // Infer anyType for no inferences (otherwise emptyObjectType)
NoDefault = 1 << 0, // Infer unknownType for no inferences (otherwise anyType or emptyObjectType)
AnyDefault = 1 << 1, // Infer anyType for no inferences (otherwise emptyObjectType)
}
/**
@@ -4540,6 +4557,8 @@ namespace ts {
isCommandLineOnly?: boolean;
showInSimplifiedHelpView?: boolean;
category?: DiagnosticMessage;
strictFlag?: true; // true if the option is one of the flag under strict
affectsSemanticDiagnostics?: true; // true if option affects semantic diagnostics
}
/* @internal */
@@ -4705,15 +4724,6 @@ namespace ts {
verticalTab = 0x0B, // \v
}
export interface UpToDateHost {
fileExists(fileName: string): boolean;
getModifiedTime(fileName: string): Date | undefined;
getUnchangedTime?(fileName: string): Date | undefined;
getLastStatus?(fileName: string): UpToDateStatus | undefined;
setLastStatus?(fileName: string, status: UpToDateStatus): void;
parseConfigFile?(configFilePath: ResolvedConfigFileName): ParsedCommandLine | undefined;
}
export interface ModuleResolutionHost {
// TODO: GH#18217 Optional methods frequently used as non-optional
@@ -4844,10 +4854,6 @@ namespace ts {
/* @internal */ hasInvalidatedResolution?: HasInvalidatedResolution;
/* @internal */ hasChangedAutomaticTypeDirectiveNames?: boolean;
createHash?(data: string): string;
getModifiedTime?(fileName: string): Date | undefined;
setModifiedTime?(fileName: string, date: Date): void;
deleteFile?(fileName: string): void;
}
/* @internal */
@@ -5064,6 +5070,7 @@ namespace ts {
/* @internal */
isSourceFileFromExternalLibrary(file: SourceFile): boolean;
getLibFileFromReference(ref: FileReference): SourceFile | undefined;
getCommonSourceDirectory(): string;
getCanonicalFileName(fileName: string): string;
@@ -5331,7 +5338,6 @@ namespace ts {
useCaseSensitiveFileNames?(): boolean;
fileExists?(path: string): boolean;
readFile?(path: string): string | undefined;
getSourceFiles?(): ReadonlyArray<SourceFile>; // Used for cached resolutions to find symlinks without traversing the fs (again)
}
// Note: this used to be deprecated in our public API, but is still used internally
@@ -5344,7 +5350,7 @@ namespace ts {
reportInaccessibleThisError?(): void;
reportPrivateInBaseOfClassExpression?(propertyName: string): void;
reportInaccessibleUniqueSymbolError?(): void;
moduleResolverHost?: ModuleSpecifierResolutionHost;
moduleResolverHost?: EmitHost;
trackReferencedAmbientModule?(decl: ModuleDeclaration, symbol: Symbol): void;
trackExternalModuleSymbolOfImportTypeNode?(symbol: Symbol): void;
}
@@ -5583,15 +5589,32 @@ namespace ts {
/* @internal */
export type PragmaPsuedoMapEntry = {[K in keyof PragmaPsuedoMap]: {name: K, args: PragmaPsuedoMap[K]}}[keyof PragmaPsuedoMap];
/* @internal */
export interface ReadonlyPragmaMap extends ReadonlyMap<PragmaPsuedoMap[keyof PragmaPsuedoMap] | PragmaPsuedoMap[keyof PragmaPsuedoMap][]> {
get<TKey extends keyof PragmaPsuedoMap>(key: TKey): PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][];
forEach(action: <TKey extends keyof PragmaPsuedoMap>(value: PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][], key: TKey) => void): void;
}
/**
* A strongly-typed es6 map of pragma entries, the values of which are either a single argument
* value (if only one was found), or an array of multiple argument values if the pragma is present
* in multiple places
*/
/* @internal */
export interface PragmaMap extends Map<PragmaPsuedoMap[keyof PragmaPsuedoMap] | PragmaPsuedoMap[keyof PragmaPsuedoMap][]> {
export interface PragmaMap extends Map<PragmaPsuedoMap[keyof PragmaPsuedoMap] | PragmaPsuedoMap[keyof PragmaPsuedoMap][]>, ReadonlyPragmaMap {
set<TKey extends keyof PragmaPsuedoMap>(key: TKey, value: PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][]): this;
get<TKey extends keyof PragmaPsuedoMap>(key: TKey): PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][];
forEach(action: <TKey extends keyof PragmaPsuedoMap>(value: PragmaPsuedoMap[TKey] | PragmaPsuedoMap[TKey][], key: TKey) => void): void;
}
export interface UserPreferences {
readonly disableSuggestions?: boolean;
readonly quotePreference?: "double" | "single";
readonly includeCompletionsForModuleExports?: boolean;
readonly includeCompletionsWithInsertText?: boolean;
readonly importModuleSpecifierPreference?: "relative" | "non-relative";
/** Determines whether we import `foo/index.ts` as "foo", "foo/index", or "foo/index.js" */
readonly importModuleSpecifierEnding?: "minimal" | "index" | "js";
readonly allowTextChangesInNewFiles?: boolean;
}
}
+190 -74
View File
@@ -16,7 +16,7 @@ namespace ts {
namespace ts {
export const emptyArray: never[] = [] as never[];
export const resolvingEmptyArray: never[] = [] as never[];
export const emptyMap: ReadonlyMap<never> = createMap<never>();
export const emptyMap = createMap<never>() as ReadonlyMap<never> & ReadonlyPragmaMap;
export const emptyUnderscoreEscapedMap: ReadonlyUnderscoreEscapedMap<never> = emptyMap as ReadonlyUnderscoreEscapedMap<never>;
export const externalHelpersModuleNameText = "tslib";
@@ -490,12 +490,23 @@ namespace ts {
return getTextOfNodeFromSourceText(sourceFile.text, node, includeTrivia);
}
function isJSDocTypeExpressionOrChild(node: Node): boolean {
return node.kind === SyntaxKind.JSDocTypeExpression || (node.parent && isJSDocTypeExpressionOrChild(node.parent));
}
export function getTextOfNodeFromSourceText(sourceText: string, node: Node, includeTrivia = false): string {
if (nodeIsMissing(node)) {
return "";
}
return sourceText.substring(includeTrivia ? node.pos : skipTrivia(sourceText, node.pos), node.end);
let text = sourceText.substring(includeTrivia ? node.pos : skipTrivia(sourceText, node.pos), node.end);
if (isJSDocTypeExpressionOrChild(node)) {
// strip space + asterisk at line start
text = text.replace(/(^|\r?\n|\r)\s*\*\s*/g, "$1");
}
return text;
}
export function getTextOfNode(node: Node, includeTrivia = false): string {
@@ -638,6 +649,10 @@ namespace ts {
return false;
}
export function getNonAugmentationDeclaration(symbol: Symbol) {
return find(symbol.declarations, d => !isExternalModuleAugmentation(d) && !(isModuleDeclaration(d) && isGlobalScopeAugmentation(d)));
}
export function isEffectiveExternalModule(node: SourceFile, compilerOptions: CompilerOptions) {
return isExternalModule(node) || compilerOptions.isolatedModules || ((getEmitModuleKind(compilerOptions) === ModuleKind.CommonJS) && !!node.commonJsModuleIndicator);
}
@@ -767,9 +782,9 @@ namespace ts {
case SyntaxKind.NumericLiteral:
return escapeLeadingUnderscores(name.text);
case SyntaxKind.ComputedPropertyName:
return isStringOrNumericLiteral(name.expression) ? escapeLeadingUnderscores(name.expression.text) : undefined!; // TODO: GH#18217 Almost all uses of this assume the result to be defined!
return isStringOrNumericLiteralLike(name.expression) ? escapeLeadingUnderscores(name.expression.text) : undefined!; // TODO: GH#18217 Almost all uses of this assume the result to be defined!
default:
Debug.assertNever(name);
return Debug.assertNever(name);
}
}
@@ -907,6 +922,10 @@ namespace ts {
return !!(getCombinedModifierFlags(node) & ModifierFlags.Const);
}
export function isDeclarationReadonly(declaration: Declaration): boolean {
return !!(getCombinedModifierFlags(declaration) & ModifierFlags.Readonly && !isParameterPropertyDeclaration(declaration));
}
export function isVarConst(node: VariableDeclaration | VariableDeclarationList): boolean {
return !!(getCombinedNodeFlags(node) & NodeFlags.Const);
}
@@ -1016,6 +1035,8 @@ namespace ts {
return !isExpressionWithTypeArgumentsInClassExtendsClause(parent);
case SyntaxKind.TypeParameter:
return node === (<TypeParameterDeclaration>parent).constraint;
case SyntaxKind.JSDocTemplateTag:
return node === (<JSDocTemplateTag>parent).constraint;
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.Parameter:
@@ -1719,12 +1740,15 @@ namespace ts {
}
export function getDeclarationOfJSInitializer(node: Node): Node | undefined {
if (!isInJavaScriptFile(node) || !node.parent) {
if (!node.parent) {
return undefined;
}
let name: Expression | BindingName | undefined;
let decl: Node | undefined;
if (isVariableDeclaration(node.parent) && node.parent.initializer === node) {
if (!isInJavaScriptFile(node) && !isVarConst(node.parent)) {
return undefined;
}
name = node.parent.name;
decl = node.parent;
}
@@ -1753,6 +1777,10 @@ namespace ts {
return decl;
}
export function isAssignmentDeclaration(decl: Declaration) {
return isBinaryExpression(decl) || isPropertyAccessExpression(decl) || isIdentifier(decl);
}
/** Get the initializer, taking into account defaulted Javascript initializers */
export function getEffectiveInitializer(node: HasExpressionInitializer) {
if (isInJavaScriptFile(node) && node.initializer &&
@@ -1886,8 +1914,12 @@ namespace ts {
/// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property
/// assignments we treat as special in the binder
export function getSpecialPropertyAssignmentKind(expr: BinaryExpression): SpecialPropertyAssignmentKind {
if (!isInJavaScriptFile(expr) ||
expr.operatorToken.kind !== SyntaxKind.EqualsToken ||
const special = getSpecialPropertyAssignmentKindWorker(expr);
return special === SpecialPropertyAssignmentKind.Property || isInJavaScriptFile(expr) ? special : SpecialPropertyAssignmentKind.None;
}
function getSpecialPropertyAssignmentKindWorker(expr: BinaryExpression): SpecialPropertyAssignmentKind {
if (expr.operatorToken.kind !== SyntaxKind.EqualsToken ||
!isPropertyAccessExpression(expr.left)) {
return SpecialPropertyAssignmentKind.None;
}
@@ -1948,6 +1980,14 @@ namespace ts {
!!getJSDocTypeTag(expr.parent);
}
export function isFunctionSymbol(symbol: Symbol | undefined) {
if (!symbol || !symbol.valueDeclaration) {
return false;
}
const decl = symbol.valueDeclaration;
return decl.kind === SyntaxKind.FunctionDeclaration || isVariableDeclaration(decl) && decl.initializer && isFunctionLike(decl.initializer);
}
export function importFromModuleSpecifier(node: StringLiteralLike): AnyValidImportOrReExport {
return tryGetImportFromModuleSpecifier(node) || Debug.fail(Debug.showSyntaxKind(node.parent));
}
@@ -2091,6 +2131,10 @@ namespace ts {
result = addRange(result, getJSDocParameterTags(node as ParameterDeclaration));
break;
}
if (node.kind === SyntaxKind.TypeParameter) {
result = addRange(result, getJSDocTypeParameterTags(node as TypeParameterDeclaration));
break;
}
node = getNextJSDocCommentLocation(node);
}
return result || emptyArray;
@@ -2310,6 +2354,13 @@ namespace ts {
return node;
}
function skipParenthesesUp(node: Node): Node {
while (node.kind === SyntaxKind.ParenthesizedExpression) {
node = node.parent;
}
return node;
}
// a node is delete target iff. it is PropertyAccessExpression/ElementAccessExpression with parentheses skipped
export function isDeleteTarget(node: Node): boolean {
if (node.kind !== SyntaxKind.PropertyAccessExpression && node.kind !== SyntaxKind.ElementAccessExpression) {
@@ -2348,7 +2399,10 @@ namespace ts {
}
else {
const binExp = name.parent.parent;
return isBinaryExpression(binExp) && getSpecialPropertyAssignmentKind(binExp) !== SpecialPropertyAssignmentKind.None && getNameOfDeclaration(binExp) === name;
return isBinaryExpression(binExp) &&
getSpecialPropertyAssignmentKind(binExp) !== SpecialPropertyAssignmentKind.None &&
(binExp.left.symbol || binExp.symbol) &&
getNameOfDeclaration(binExp) === name;
}
}
default:
@@ -2560,10 +2614,8 @@ namespace ts {
return false;
}
export function isStringOrNumericLiteral(node: Node): node is StringLiteral | NumericLiteral {
const kind = node.kind;
return kind === SyntaxKind.StringLiteral
|| kind === SyntaxKind.NumericLiteral;
export function isStringOrNumericLiteralLike(node: Node): node is StringLiteralLike | NumericLiteral {
return isStringLiteralLike(node) || isNumericLiteral(node);
}
/**
@@ -2580,7 +2632,7 @@ namespace ts {
export function isDynamicName(name: DeclarationName): boolean {
return name.kind === SyntaxKind.ComputedPropertyName &&
!isStringOrNumericLiteral(name.expression) &&
!isStringOrNumericLiteralLike(name.expression) &&
!isWellKnownSymbolSyntactically(name.expression);
}
@@ -2593,24 +2645,25 @@ namespace ts {
return isPropertyAccessExpression(node) && isESSymbolIdentifier(node.expression);
}
export function getPropertyNameForPropertyNameNode(name: DeclarationName): __String | undefined {
if (name.kind === SyntaxKind.Identifier) {
return name.escapedText;
export function getPropertyNameForPropertyNameNode(name: PropertyName): __String | undefined {
switch (name.kind) {
case SyntaxKind.Identifier:
return name.escapedText;
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
return escapeLeadingUnderscores(name.text);
case SyntaxKind.ComputedPropertyName:
const nameExpression = name.expression;
if (isWellKnownSymbolSyntactically(nameExpression)) {
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>nameExpression).name));
}
else if (isStringOrNumericLiteralLike(nameExpression)) {
return escapeLeadingUnderscores(nameExpression.text);
}
return undefined;
default:
return Debug.assertNever(name);
}
if (name.kind === SyntaxKind.StringLiteral || name.kind === SyntaxKind.NumericLiteral) {
return escapeLeadingUnderscores(name.text);
}
if (name.kind === SyntaxKind.ComputedPropertyName) {
const nameExpression = name.expression;
if (isWellKnownSymbolSyntactically(nameExpression)) {
return getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>nameExpression).name));
}
else if (nameExpression.kind === SyntaxKind.StringLiteral || nameExpression.kind === SyntaxKind.NumericLiteral) {
return escapeLeadingUnderscores((<LiteralExpression>nameExpression).text);
}
}
return undefined;
}
export type PropertyNameLiteral = Identifier | StringLiteralLike | NumericLiteral;
@@ -3183,16 +3236,7 @@ namespace ts {
}
export function getDeclarationEmitOutputFilePath(fileName: string, host: EmitHost) {
// TODO: GH#25810 following should work but makes the build break:
// return getDeclarationEmitOutputFilePathWorker(fileName, host.getCompilerOptions(), host.getCurrentDirectory(), host.getCommonSourceDirectory(), f => host.getCanonicalFileName(f));
const options = host.getCompilerOptions();
const outputDir = options.declarationDir || options.outDir; // Prefer declaration folder if specified
const path = outputDir
? getSourceFilePathInNewDir(fileName, host, outputDir)
: fileName;
return removeFileExtension(path) + Extension.Dts;
return getDeclarationEmitOutputFilePathWorker(fileName, host.getCompilerOptions(), host.getCurrentDirectory(), host.getCommonSourceDirectory(), f => host.getCanonicalFileName(f));
}
export function getDeclarationEmitOutputFilePathWorker(fileName: string, options: CompilerOptions, currentDirectory: string, commonSourceDirectory: string, getCanonicalFileName: GetCanonicalFileName): string {
@@ -3325,17 +3369,17 @@ namespace ts {
}
}
else {
forEach(declarations, (member: Declaration) => {
if ((member.kind === SyntaxKind.GetAccessor || member.kind === SyntaxKind.SetAccessor)
forEach(declarations, member => {
if (isAccessor(member)
&& hasModifier(member, ModifierFlags.Static) === hasModifier(accessor, ModifierFlags.Static)) {
const memberName = getPropertyNameForPropertyNameNode((member as NamedDeclaration).name!);
const memberName = getPropertyNameForPropertyNameNode(member.name);
const accessorName = getPropertyNameForPropertyNameNode(accessor.name);
if (memberName === accessorName) {
if (!firstAccessor) {
firstAccessor = <AccessorDeclaration>member;
firstAccessor = member;
}
else if (!secondAccessor) {
secondAccessor = <AccessorDeclaration>member;
secondAccessor = member;
}
if (member.kind === SyntaxKind.GetAccessor && !getAccessor) {
@@ -3362,7 +3406,9 @@ namespace ts {
* parsed in a JavaScript file, gets the type annotation from JSDoc.
*/
export function getEffectiveTypeAnnotationNode(node: Node): TypeNode | undefined {
return (node as HasType).type || (isInJavaScriptFile(node) ? getJSDocType(node) : undefined);
const type = (node as HasType).type;
if (type || !isInJavaScriptFile(node)) return type;
return isJSDocPropertyLikeTag(node) ? node.typeExpression && node.typeExpression.type : getJSDocType(node);
}
export function getTypeAnnotationNode(node: Node): TypeNode | undefined {
@@ -3374,10 +3420,9 @@ namespace ts {
* JavaScript file, gets the return type annotation from JSDoc.
*/
export function getEffectiveReturnTypeNode(node: SignatureDeclaration | JSDocSignature): TypeNode | undefined {
if (isJSDocSignature(node)) {
return node.type && node.type.typeExpression && node.type.typeExpression.type;
}
return node.type || (isInJavaScriptFile(node) ? getJSDocReturnType(node) : undefined);
return isJSDocSignature(node) ?
node.type && node.type.typeExpression && node.type.typeExpression.type :
node.type || (isInJavaScriptFile(node) ? getJSDocReturnType(node) : undefined);
}
export function getJSDocTypeParameterDeclarations(node: DeclarationWithTypeParameters): ReadonlyArray<TypeParameterDeclaration> {
@@ -3721,7 +3766,7 @@ namespace ts {
return false;
}
export function isExpressionWithTypeArgumentsInClassExtendsClause(node: Node): boolean {
export function isExpressionWithTypeArgumentsInClassExtendsClause(node: Node): node is ExpressionWithTypeArguments {
return tryGetClassExtendingExpressionWithTypeArguments(node) !== undefined;
}
@@ -4015,7 +4060,8 @@ namespace ts {
* @param pos The start position.
* @param end The end position.
*/
export function createRange(pos: number, end: number): TextRange {
export function createRange(pos: number, end: number = pos): TextRange {
Debug.assert(end >= pos || end === -1);
return { pos, end };
}
@@ -4191,22 +4237,48 @@ namespace ts {
if (!parent) return AccessKind.Read;
switch (parent.kind) {
case SyntaxKind.ParenthesizedExpression:
return accessKind(parent);
case SyntaxKind.PostfixUnaryExpression:
case SyntaxKind.PrefixUnaryExpression:
const { operator } = parent as PrefixUnaryExpression | PostfixUnaryExpression;
return operator === SyntaxKind.PlusPlusToken || operator === SyntaxKind.MinusMinusToken ? writeOrReadWrite() : AccessKind.Read;
case SyntaxKind.BinaryExpression:
const { left, operatorToken } = parent as BinaryExpression;
return left === node && isAssignmentOperator(operatorToken.kind) ? writeOrReadWrite() : AccessKind.Read;
return left === node && isAssignmentOperator(operatorToken.kind) ?
operatorToken.kind === SyntaxKind.EqualsToken ? AccessKind.Write : writeOrReadWrite()
: AccessKind.Read;
case SyntaxKind.PropertyAccessExpression:
return (parent as PropertyAccessExpression).name !== node ? AccessKind.Read : accessKind(parent);
case SyntaxKind.PropertyAssignment: {
const parentAccess = accessKind(parent.parent);
// In `({ x: varname }) = { x: 1 }`, the left `x` is a read, the right `x` is a write.
return node === (parent as PropertyAssignment).name ? reverseAccessKind(parentAccess) : parentAccess;
}
case SyntaxKind.ShorthandPropertyAssignment:
// Assume it's the local variable being accessed, since we don't check public properties for --noUnusedLocals.
return node === (parent as ShorthandPropertyAssignment).objectAssignmentInitializer ? AccessKind.Read : accessKind(parent.parent);
case SyntaxKind.ArrayLiteralExpression:
return accessKind(parent);
default:
return AccessKind.Read;
}
function writeOrReadWrite(): AccessKind {
// If grandparent is not an ExpressionStatement, this is used as an expression in addition to having a side effect.
return parent.parent && parent.parent.kind === SyntaxKind.ExpressionStatement ? AccessKind.Write : AccessKind.ReadWrite;
return parent.parent && skipParenthesesUp(parent.parent).kind === SyntaxKind.ExpressionStatement ? AccessKind.Write : AccessKind.ReadWrite;
}
}
function reverseAccessKind(a: AccessKind): AccessKind {
switch (a) {
case AccessKind.Read:
return AccessKind.Write;
case AccessKind.Write:
return AccessKind.Read;
case AccessKind.ReadWrite:
return AccessKind.ReadWrite;
default:
return Debug.assertNever(a);
}
}
@@ -4453,12 +4525,6 @@ namespace ts {
return { start, length };
}
/* @internal */
export function createTextRange(pos: number, end: number = pos): TextRange {
Debug.assert(end >= pos);
return { pos, end };
}
export function createTextSpanFromBounds(start: number, end: number) {
return createTextSpan(start, end - start);
}
@@ -4836,13 +4902,13 @@ namespace ts {
if (isDeclaration(hostNode)) {
return getDeclarationIdentifier(hostNode);
}
// Covers remaining cases
// Covers remaining cases (returning undefined if none match).
switch (hostNode.kind) {
case SyntaxKind.VariableStatement:
if (hostNode.declarationList && hostNode.declarationList.declarations[0]) {
return getDeclarationIdentifier(hostNode.declarationList.declarations[0]);
}
return undefined;
break;
case SyntaxKind.ExpressionStatement:
const expr = hostNode.expression;
switch (expr.kind) {
@@ -4854,9 +4920,7 @@ namespace ts {
return arg;
}
}
return undefined;
case SyntaxKind.EndOfFileToken:
return undefined;
break;
case SyntaxKind.ParenthesizedExpression: {
return getDeclarationIdentifier(hostNode.expression);
}
@@ -4864,10 +4928,8 @@ namespace ts {
if (isDeclaration(hostNode.statement) || isExpression(hostNode.statement)) {
return getDeclarationIdentifier(hostNode.statement);
}
return undefined;
break;
}
default:
Debug.assertNever(hostNode, "Found typedef tag attached to node which it should not be!");
}
}
@@ -4946,15 +5008,14 @@ namespace ts {
/**
* Gets the JSDoc parameter tags for the node if present.
*
* @remarks Returns any JSDoc param tag that matches the provided
* @remarks Returns any JSDoc param tag whose name matches the provided
* parameter, whether a param tag on a containing function
* expression, or a param tag on a variable declaration whose
* initializer is the containing function. The tags closest to the
* node are returned first, so in the previous example, the param
* tag on the containing function expression would be first.
*
* Does not return tags for binding patterns, because JSDoc matches
* parameters by name and binding patterns do not have a name.
* For binding patterns, parameter tags are matched by position.
*/
export function getJSDocParameterTags(param: ParameterDeclaration): ReadonlyArray<JSDocParameterTag> {
if (param.name) {
@@ -4975,6 +5036,22 @@ namespace ts {
return emptyArray;
}
/**
* Gets the JSDoc type parameter tags for the node if present.
*
* @remarks Returns any JSDoc template tag whose names match the provided
* parameter, whether a template tag on a containing function
* expression, or a template tag on a variable declaration whose
* initializer is the containing function. The tags closest to the
* node are returned first, so in the previous example, the template
* tag on the containing function expression would be first.
*/
export function getJSDocTypeParameterTags(param: TypeParameterDeclaration): ReadonlyArray<JSDocTemplateTag> {
const name = param.name.escapedText;
return getJSDocTags(param.parent).filter((tag): tag is JSDocTemplateTag =>
isJSDocTemplateTag(tag) && tag.typeParameters.some(tp => tp.name.escapedText === name));
}
/**
* Return true if the node has JSDoc parameter tags.
*
@@ -5103,7 +5180,27 @@ namespace ts {
Debug.assert(node.parent.kind === SyntaxKind.JSDocComment);
return flatMap(node.parent.tags, tag => isJSDocTemplateTag(tag) ? tag.typeParameters : undefined) as ReadonlyArray<TypeParameterDeclaration>;
}
return node.typeParameters || (isInJavaScriptFile(node) ? getJSDocTypeParameterDeclarations(node) : emptyArray);
if (node.typeParameters) {
return node.typeParameters;
}
if (isInJavaScriptFile(node)) {
const decls = getJSDocTypeParameterDeclarations(node);
if (decls.length) {
return decls;
}
const typeTag = getJSDocType(node);
if (typeTag && isFunctionTypeNode(typeTag) && typeTag.typeParameters) {
return typeTag.typeParameters;
}
}
return emptyArray;
}
export function getEffectiveConstraintOfTypeParameter(node: TypeParameterDeclaration): TypeNode | undefined {
return node.constraint ? node.constraint
: isJSDocTemplateTag(node.parent) && node === node.parent.typeParameters[0]
? node.parent.constraint
: undefined;
}
}
@@ -6967,6 +7064,15 @@ namespace ts {
return compilerOptions[flag] === undefined ? !!compilerOptions.strict : !!compilerOptions[flag];
}
export function compilerOptionsAffectSemanticDiagnostics(newOptions: CompilerOptions, oldOptions: CompilerOptions) {
if (oldOptions === newOptions) {
return false;
}
return optionDeclarations.some(option => (!!option.strictFlag && getStrictOptionValue(newOptions, option.name as StrictOptionName) !== getStrictOptionValue(oldOptions, option.name as StrictOptionName)) ||
(!!option.affectsSemanticDiagnostics && !newOptions[option.name] !== !oldOptions[option.name]));
}
export function hasZeroOrOneAsteriskCharacter(str: string): boolean {
let seenAsterisk = false;
for (let i = 0; i < str.length; i++) {
@@ -7260,8 +7366,6 @@ namespace ts {
if (pathComponents.length === 0) return "";
const root = pathComponents[0] && ensureTrailingDirectorySeparator(pathComponents[0]);
if (pathComponents.length === 1) return root;
return root + pathComponents.slice(1).join(directorySeparator);
}
@@ -7865,6 +7969,7 @@ namespace ts {
/** Must have ".d.ts" first because if ".ts" goes first, that will be detected as the extension instead of ".d.ts". */
export const supportedTypescriptExtensionsForExtractExtension: ReadonlyArray<Extension> = [Extension.Dts, Extension.Ts, Extension.Tsx];
export const supportedJavascriptExtensions: ReadonlyArray<Extension> = [Extension.Js, Extension.Jsx];
export const supportedJavaScriptAndJsonExtensions: ReadonlyArray<Extension> = [Extension.Js, Extension.Jsx, Extension.Json];
const allSupportedExtensions: ReadonlyArray<Extension> = [...supportedTypeScriptExtensions, ...supportedJavascriptExtensions];
export function getSupportedExtensions(options?: CompilerOptions, extraFileExtensions?: ReadonlyArray<FileExtensionInfo>): ReadonlyArray<string> {
@@ -7890,6 +7995,10 @@ namespace ts {
return some(supportedJavascriptExtensions, extension => fileExtensionIs(fileName, extension));
}
export function hasJavaScriptOrJsonFileExtension(fileName: string): boolean {
return supportedJavaScriptAndJsonExtensions.some(ext => fileExtensionIs(fileName, ext));
}
export function hasTypeScriptFileExtension(fileName: string): boolean {
return some(supportedTypeScriptExtensions, extension => fileExtensionIs(fileName, extension));
}
@@ -8213,4 +8322,11 @@ namespace ts {
// Include the `<>`
return { pos: typeParameters.pos - 1, end: typeParameters.end + 1 };
}
export function skipTypeChecking(sourceFile: SourceFile, options: CompilerOptions) {
// If skipLibCheck is enabled, skip reporting errors if file is a declaration file.
// If skipDefaultLibCheck is enabled, skip reporting errors if file contains a
// '/// <reference no-default-lib="true"/>' directive.
return options.skipLibCheck && sourceFile.isDeclarationFile || options.skipDefaultLibCheck && sourceFile.hasNoDefaultLib;
}
}
+35 -26
View File
@@ -27,12 +27,6 @@ namespace ts {
};
}
/** @internal */
export const nonClearingMessageCodes: number[] = [
Diagnostics.Found_1_error_Watching_for_file_changes.code,
Diagnostics.Found_0_errors_Watching_for_file_changes.code
];
/**
* @returns Whether the screen was cleared.
*/
@@ -41,7 +35,7 @@ namespace ts {
!options.preserveWatchOutput &&
!options.extendedDiagnostics &&
!options.diagnostics &&
!contains(nonClearingMessageCodes, diagnostic.code)) {
contains(screenStartingMessageCodes, diagnostic.code)) {
system.clearScreen();
return true;
}
@@ -174,6 +168,17 @@ namespace ts {
const noopFileWatcher: FileWatcher = { close: noop };
export function createWatchHost(system = sys, reportWatchStatus?: WatchStatusReporter): WatchHost {
const onWatchStatusChange = reportWatchStatus || createWatchStatusReporter(system);
return {
onWatchStatusChange,
watchFile: system.watchFile ? ((path, callback, pollingInterval) => system.watchFile!(path, callback, pollingInterval)) : () => noopFileWatcher,
watchDirectory: system.watchDirectory ? ((path, callback, recursive) => system.watchDirectory!(path, callback, recursive)) : () => noopFileWatcher,
setTimeout: system.setTimeout ? ((callback, ms, ...args: any[]) => system.setTimeout!.call(system, callback, ms, ...args)) : noop,
clearTimeout: system.clearTimeout ? (timeoutId => system.clearTimeout!(timeoutId)) : noop
};
}
/**
* Creates the watch compiler host that can be extended with config file or root file names and options host
*/
@@ -183,9 +188,10 @@ namespace ts {
}
let host: DirectoryStructureHost = system;
host; // tslint:disable-line no-unused-expression (TODO: `host` is unused!)
const useCaseSensitiveFileNames = () => system.useCaseSensitiveFileNames;
const writeFileName = (s: string) => system.write(s + system.newLine);
const onWatchStatusChange = reportWatchStatus || createWatchStatusReporter(system);
const { onWatchStatusChange, watchFile, watchDirectory, setTimeout, clearTimeout } = createWatchHost(system, reportWatchStatus);
return {
useCaseSensitiveFileNames,
getNewLine: () => system.newLine,
@@ -199,10 +205,10 @@ namespace ts {
readDirectory: (path, extensions, exclude, include, depth) => system.readDirectory(path, extensions, exclude, include, depth),
realpath: system.realpath && (path => system.realpath!(path)),
getEnvironmentVariable: system.getEnvironmentVariable && (name => system.getEnvironmentVariable(name)),
watchFile: system.watchFile ? ((path, callback, pollingInterval) => system.watchFile!(path, callback, pollingInterval)) : () => noopFileWatcher,
watchDirectory: system.watchDirectory ? ((path, callback, recursive) => system.watchDirectory!(path, callback, recursive)) : () => noopFileWatcher,
setTimeout: system.setTimeout ? ((callback, ms, ...args: any[]) => system.setTimeout!.call(system, callback, ms, ...args)) : noop,
clearTimeout: system.clearTimeout ? (timeoutId => system.clearTimeout!(timeoutId)) : noop,
watchFile,
watchDirectory,
setTimeout,
clearTimeout,
trace: s => system.write(s),
onWatchStatusChange,
createDirectory: path => system.createDirectory(path),
@@ -223,10 +229,10 @@ namespace ts {
const reportSummary = (errorCount: number) => {
if (errorCount === 1) {
onWatchStatusChange(createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes, errorCount), newLine, compilerOptions);
onWatchStatusChange!(createCompilerDiagnostic(Diagnostics.Found_1_error_Watching_for_file_changes, errorCount), newLine, compilerOptions);
}
else {
onWatchStatusChange(createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errorCount, errorCount), newLine, compilerOptions);
onWatchStatusChange!(createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errorCount, errorCount), newLine, compilerOptions);
}
};
@@ -269,7 +275,21 @@ namespace ts {
export type WatchStatusReporter = (diagnostic: Diagnostic, newLine: string, options: CompilerOptions) => void;
/** Create the program with rootNames and options, if they are undefined, oldProgram and new configFile diagnostics create new program */
export type CreateProgram<T extends BuilderProgram> = (rootNames: ReadonlyArray<string> | undefined, options: CompilerOptions | undefined, host?: CompilerHost, oldProgram?: T, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>) => T;
export interface WatchCompilerHost<T extends BuilderProgram> {
/** Host that has watch functionality used in --watch mode */
export interface WatchHost {
/** If provided, called with Diagnostic message that informs about change in watch status */
onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions): void;
/** Used to watch changes in source files, missing files needed to update the program or config file */
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
/** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
/** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
/** If provided, will be used to reset existing delayed compilation */
clearTimeout?(timeoutId: any): void;
}
export interface WatchCompilerHost<T extends BuilderProgram> extends WatchHost {
// TODO: GH#18217 Optional methods are frequently asserted
/**
@@ -278,8 +298,6 @@ namespace ts {
createProgram: CreateProgram<T>;
/** If provided, callback to invoke after every new program creation */
afterProgramCreate?(program: T): void;
/** If provided, called with Diagnostic message that informs about change in watch status */
onWatchStatusChange?(diagnostic: Diagnostic, newLine: string, options: CompilerOptions): void;
// Only for testing
/*@internal*/
@@ -322,15 +340,6 @@ namespace ts {
resolveModuleNames?(moduleNames: string[], containingFile: string, reusedNames?: string[]): ResolvedModule[];
/** If provided, used to resolve type reference directives, otherwise typescript's default resolution */
resolveTypeReferenceDirectives?(typeReferenceDirectiveNames: string[], containingFile: string): ResolvedTypeReferenceDirective[];
/** Used to watch changes in source files, missing files needed to update the program or config file */
watchFile(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
/** Used to watch resolved module's failed lookup locations, config file specs, type roots where auto type reference directives are added */
watchDirectory(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
/** If provided, will be used to set delayed compilation, so that multiple changes in short span are compiled together */
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
/** If provided, will be used to reset existing delayed compilation */
clearTimeout?(timeoutId: any): void;
}
/** Internal interface used to wire emit through same host */
+36
View File
@@ -374,5 +374,41 @@ namespace fakes {
return parsed;
}
}
export class SolutionBuilderHost extends CompilerHost implements ts.SolutionBuilderHost {
diagnostics: ts.Diagnostic[] = [];
reportDiagnostic(diagnostic: ts.Diagnostic) {
this.diagnostics.push(diagnostic);
}
reportSolutionBuilderStatus(diagnostic: ts.Diagnostic) {
this.diagnostics.push(diagnostic);
}
clearDiagnostics() {
this.diagnostics.length = 0;
}
assertDiagnosticMessages(...expected: ts.DiagnosticMessage[]) {
const actual = this.diagnostics.slice();
if (actual.length !== expected.length) {
assert.fail<any>(actual, expected, `Diagnostic arrays did not match - got\r\n${actual.map(a => " " + a.messageText).join("\r\n")}\r\nexpected\r\n${expected.map(e => " " + e.message).join("\r\n")}`);
}
for (let i = 0; i < actual.length; i++) {
if (actual[i].code !== expected[i].code) {
assert.fail(actual[i].messageText, expected[i].message, `Mismatched error code - expected diagnostic ${i} "${actual[i].messageText}" to match ${expected[i].message}`);
}
}
}
printDiagnostics(header = "== Diagnostics ==") {
const out = ts.createDiagnosticReporter(ts.sys);
ts.sys.write(header + "\r\n");
for (const d of this.diagnostics) {
out(d);
}
}
}
}
+161 -132
View File
@@ -50,10 +50,8 @@ namespace FourSlash {
data?: {};
}
export interface Range {
export interface Range extends ts.TextRange {
fileName: string;
pos: number;
end: number;
marker?: Marker;
}
@@ -870,8 +868,7 @@ namespace FourSlash {
const actualByName = ts.createMap<ts.CompletionEntry>();
for (const entry of actualCompletions.entries) {
if (actualByName.has(entry.name)) {
// TODO: GH#23587
if (entry.name !== "undefined" && entry.name !== "require") this.raiseError(`Duplicate (${actualCompletions.entries.filter(a => a.name === entry.name).length}) completions for ${entry.name}`);
this.raiseError(`Duplicate (${actualCompletions.entries.filter(a => a.name === entry.name).length}) completions for ${entry.name}`);
}
else {
actualByName.set(entry.name, entry);
@@ -911,8 +908,8 @@ namespace FourSlash {
}
private verifyCompletionEntry(actual: ts.CompletionEntry, expected: FourSlashInterface.ExpectedCompletionEntry) {
const { insertText, replacementSpan, hasAction, isRecommended, kind, text, documentation, sourceDisplay } = typeof expected === "string"
? { insertText: undefined, replacementSpan: undefined, hasAction: undefined, isRecommended: undefined, kind: undefined, text: undefined, documentation: undefined, sourceDisplay: undefined }
const { insertText, replacementSpan, hasAction, isRecommended, kind, text, documentation, source, sourceDisplay } = typeof expected === "string"
? { insertText: undefined, replacementSpan: undefined, hasAction: undefined, isRecommended: undefined, kind: undefined, text: undefined, documentation: undefined, source: undefined, sourceDisplay: undefined }
: expected;
if (actual.insertText !== insertText) {
@@ -930,6 +927,7 @@ namespace FourSlash {
assert.equal(actual.hasAction, hasAction);
assert.equal(actual.isRecommended, isRecommended);
assert.equal(actual.source, source);
if (text) {
const actualDetails = this.getCompletionEntryDetails(actual.name, actual.source)!;
@@ -1103,7 +1101,7 @@ namespace FourSlash {
return node;
}
private verifyRange(desc: string, expected: Range, actual: ts.Node) {
private verifyRange(desc: string, expected: ts.TextRange, actual: ts.Node) {
const actualStart = actual.getStart();
const actualEnd = actual.getEnd();
if (actualStart !== expected.pos || actualEnd !== expected.end) {
@@ -1639,11 +1637,7 @@ Actual: ${stringify(fullActual)}`);
baselineFile = baselineFile.replace(ts.Extension.Ts, ".baseline");
}
Harness.Baseline.runBaseline(
baselineFile,
() => {
return this.baselineCurrentFileLocations(pos => this.getBreakpointStatementLocation(pos)!);
});
Harness.Baseline.runBaseline(baselineFile, this.baselineCurrentFileLocations(pos => this.getBreakpointStatementLocation(pos)!));
}
private getEmitFiles(): ReadonlyArray<FourSlashFile> {
@@ -1674,66 +1668,58 @@ Actual: ${stringify(fullActual)}`);
for (const { name, text } of outputFiles) {
const fromTestFile = this.getFileContent(name);
if (fromTestFile !== text) {
this.raiseError("Emit output is not as expected: " + showTextDiff(fromTestFile, text));
this.raiseError(`Emit output for ${name} is not as expected: ${showTextDiff(fromTestFile, text)}`);
}
}
}
public baselineGetEmitOutput(): void {
Harness.Baseline.runBaseline(
ts.Debug.assertDefined(this.testData.globalOptions[MetadataOptionNames.baselineFile]),
() => {
let resultString = "";
// Loop through all the emittedFiles and emit them one by one
for (const emitFile of this.getEmitFiles()) {
const emitOutput = this.languageService.getEmitOutput(emitFile.fileName);
// Print emitOutputStatus in readable format
resultString += "EmitSkipped: " + emitOutput.emitSkipped + Harness.IO.newLine();
let resultString = "";
// Loop through all the emittedFiles and emit them one by one
for (const emitFile of this.getEmitFiles()) {
const emitOutput = this.languageService.getEmitOutput(emitFile.fileName);
// Print emitOutputStatus in readable format
resultString += "EmitSkipped: " + emitOutput.emitSkipped + Harness.IO.newLine();
if (emitOutput.emitSkipped) {
resultString += "Diagnostics:" + Harness.IO.newLine();
const diagnostics = ts.getPreEmitDiagnostics(this.languageService.getProgram()!); // TODO: GH#18217
for (const diagnostic of diagnostics) {
if (!ts.isString(diagnostic.messageText)) {
let chainedMessage: ts.DiagnosticMessageChain | undefined = diagnostic.messageText;
let indentation = " ";
while (chainedMessage) {
resultString += indentation + chainedMessage.messageText + Harness.IO.newLine();
chainedMessage = chainedMessage.next;
indentation = indentation + " ";
}
}
else {
resultString += " " + diagnostic.messageText + Harness.IO.newLine();
}
if (emitOutput.emitSkipped) {
resultString += "Diagnostics:" + Harness.IO.newLine();
const diagnostics = ts.getPreEmitDiagnostics(this.languageService.getProgram()!); // TODO: GH#18217
for (const diagnostic of diagnostics) {
if (!ts.isString(diagnostic.messageText)) {
let chainedMessage: ts.DiagnosticMessageChain | undefined = diagnostic.messageText;
let indentation = " ";
while (chainedMessage) {
resultString += indentation + chainedMessage.messageText + Harness.IO.newLine();
chainedMessage = chainedMessage.next;
indentation = indentation + " ";
}
}
for (const outputFile of emitOutput.outputFiles) {
const fileName = "FileName : " + outputFile.name + Harness.IO.newLine();
resultString = resultString + Harness.IO.newLine() + fileName + outputFile.text;
else {
resultString += " " + diagnostic.messageText + Harness.IO.newLine();
}
resultString += Harness.IO.newLine();
}
}
return resultString;
});
for (const outputFile of emitOutput.outputFiles) {
const fileName = "FileName : " + outputFile.name + Harness.IO.newLine();
resultString = resultString + Harness.IO.newLine() + fileName + outputFile.text;
}
resultString += Harness.IO.newLine();
}
Harness.Baseline.runBaseline(ts.Debug.assertDefined(this.testData.globalOptions[MetadataOptionNames.baselineFile]), resultString);
}
public baselineQuickInfo() {
let baselineFile = this.testData.globalOptions[MetadataOptionNames.baselineFile];
if (!baselineFile) {
baselineFile = ts.getBaseFileName(this.activeFile.fileName).replace(ts.Extension.Ts, ".baseline");
}
const baselineFile = this.testData.globalOptions[MetadataOptionNames.baselineFile] ||
ts.getBaseFileName(this.activeFile.fileName).replace(ts.Extension.Ts, ".baseline");
Harness.Baseline.runBaseline(
baselineFile,
() => stringify(
stringify(
this.testData.markers.map(marker => ({
marker,
quickInfo: this.languageService.getQuickInfoAtPosition(marker.fileName, marker.position)
}))
));
}))));
}
public printBreakpointLocation(pos: number) {
@@ -1967,18 +1953,11 @@ Actual: ${stringify(fullActual)}`);
* May be negative.
*/
private applyEdits(fileName: string, edits: ReadonlyArray<ts.TextChange>, isFormattingEdit: boolean): number {
// We get back a set of edits, but langSvc.editScript only accepts one at a time. Use this to keep track
// of the incremental offset from each edit to the next. We assume these edit ranges don't overlap
// Copy this so we don't ruin someone else's copy
edits = JSON.parse(JSON.stringify(edits));
// Get a snapshot of the content of the file so we can make sure any formatting edits didn't destroy non-whitespace characters
const oldContent = this.getFileContent(fileName);
let runningOffset = 0;
for (let i = 0; i < edits.length; i++) {
const edit = edits[i];
forEachTextChange(edits, edit => {
const offsetStart = edit.span.start;
const offsetEnd = offsetStart + edit.span.length;
this.editScriptAndUpdateMarkers(fileName, offsetStart, offsetEnd, edit.newText);
@@ -1994,14 +1973,7 @@ Actual: ${stringify(fullActual)}`);
}
}
runningOffset += editDelta;
// Update positions of any future edits affected by this change
for (let j = i + 1; j < edits.length; j++) {
if (edits[j].span.start >= edits[i].span.start) {
edits[j].span.start += editDelta;
}
}
}
});
if (isFormattingEdit) {
const newContent = this.getFileContent(fileName);
@@ -2043,30 +2015,14 @@ Actual: ${stringify(fullActual)}`);
this.languageServiceAdapterHost.editScript(fileName, editStart, editEnd, newText);
for (const marker of this.testData.markers) {
if (marker.fileName === fileName) {
marker.position = updatePosition(marker.position);
marker.position = updatePosition(marker.position, editStart, editEnd, newText);
}
}
for (const range of this.testData.ranges) {
if (range.fileName === fileName) {
range.pos = updatePosition(range.pos);
range.end = updatePosition(range.end);
}
}
function updatePosition(position: number) {
if (position > editStart) {
if (position < editEnd) {
// Inside the edit - mark it as invalidated (?)
return -1;
}
else {
// Move marker back/forward by the appropriate amount
return position + (editStart - editEnd) + newText.length;
}
}
else {
return position;
range.pos = updatePosition(range.pos, editStart, editEnd, newText);
range.end = updatePosition(range.end, editStart, editEnd, newText);
}
}
}
@@ -2347,10 +2303,7 @@ Actual: ${stringify(fullActual)}`);
public baselineCurrentFileNameOrDottedNameSpans() {
Harness.Baseline.runBaseline(
this.testData.globalOptions[MetadataOptionNames.baselineFile],
() => {
return this.baselineCurrentFileLocations(pos =>
this.getNameOrDottedNameSpan(pos)!);
});
this.baselineCurrentFileLocations(pos => this.getNameOrDottedNameSpan(pos)!));
}
public printNameOrDottedNameSpans(pos: number) {
@@ -2500,22 +2453,24 @@ Actual: ${stringify(fullActual)}`);
this.applyCodeActions(codeActions);
this.verifyNewContent(options, ts.flatMap(codeActions, a => a.changes.map(c => c.fileName)));
this.verifyNewContentAfterChange(options, ts.flatMap(codeActions, a => a.changes.map(c => c.fileName)));
}
public verifyRangeIs(expectedText: string, includeWhiteSpace?: boolean) {
this.verifyTextMatches(this.rangeText(this.getOnlyRange()), !!includeWhiteSpace, expectedText);
}
private getOnlyRange() {
const ranges = this.getRanges();
if (ranges.length !== 1) {
this.raiseError("Exactly one range should be specified in the testfile.");
}
return ts.first(ranges);
}
const actualText = this.rangeText(ranges[0]);
const result = includeWhiteSpace
? actualText === expectedText
: this.removeWhitespace(actualText) === this.removeWhitespace(expectedText);
if (!result) {
private verifyTextMatches(actualText: string, includeWhitespace: boolean, expectedText: string) {
const removeWhitespace = (s: string): string => includeWhitespace ? s : this.removeWhitespace(s);
if (removeWhitespace(actualText) !== removeWhitespace(expectedText)) {
this.raiseError(`Actual range text doesn't match expected text.\n${showTextDiff(expectedText, actualText)}`);
}
}
@@ -2582,33 +2537,68 @@ Actual: ${stringify(fullActual)}`);
const action = actions[index];
assert.equal(action.description, options.description);
assert.deepEqual(action.commands, options.commands);
for (const change of action.changes) {
this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false);
if (options.applyChanges) {
for (const change of action.changes) {
this.applyEdits(change.fileName, change.textChanges, /*isFormattingEdit*/ false);
}
this.verifyNewContentAfterChange(options, action.changes.map(c => c.fileName));
}
else {
this.verifyNewContent(options, action.changes);
}
this.verifyNewContent(options, action.changes.map(c => c.fileName));
}
private verifyNewContent(options: FourSlashInterface.NewContentOptions, changedFiles: ReadonlyArray<string>) {
const assertedChangedFiles = !options.newFileContent || typeof options.newFileContent === "string"
private verifyNewContent({ newFileContent, newRangeContent }: FourSlashInterface.NewContentOptions, changes: ReadonlyArray<ts.FileTextChanges>): void {
if (newRangeContent !== undefined) {
assert(newFileContent === undefined);
assert(changes.length === 1, "Affected 0 or more than 1 file, must use 'newFileContent' instead of 'newRangeContent'");
const change = ts.first(changes);
assert(change.fileName = this.activeFile.fileName);
const newText = ts.textChanges.applyChanges(this.getFileContent(this.activeFile.fileName), change.textChanges);
const newRange = updateTextRangeForTextChanges(this.getOnlyRange(), change.textChanges);
const actualText = newText.slice(newRange.pos, newRange.end);
this.verifyTextMatches(actualText, /*includeWhitespace*/ true, newRangeContent);
}
else {
if (newFileContent === undefined) throw ts.Debug.fail();
if (typeof newFileContent !== "object") newFileContent = { [this.activeFile.fileName]: newFileContent };
for (const change of changes) {
const expectedNewContent = newFileContent[change.fileName];
if (expectedNewContent === undefined) {
ts.Debug.fail(`Did not expect a change in ${change.fileName}`);
}
const oldText = this.tryGetFileContent(change.fileName);
ts.Debug.assert(!!change.isNewFile === (oldText === undefined));
const newContent = change.isNewFile ? ts.first(change.textChanges).newText : ts.textChanges.applyChanges(oldText!, change.textChanges);
assert.equal(newContent, expectedNewContent);
}
for (const newFileName in newFileContent) {
ts.Debug.assert(changes.some(c => c.fileName === newFileName), "No change in file", () => newFileName);
}
}
}
private verifyNewContentAfterChange({ newFileContent, newRangeContent }: FourSlashInterface.NewContentOptions, changedFiles: ReadonlyArray<string>) {
const assertedChangedFiles = !newFileContent || typeof newFileContent === "string"
? [this.activeFile.fileName]
: ts.getOwnKeys(options.newFileContent);
: ts.getOwnKeys(newFileContent);
assert.deepEqual(assertedChangedFiles, changedFiles);
if (options.newFileContent !== undefined) {
assert(!options.newRangeContent);
if (typeof options.newFileContent === "string") {
this.verifyCurrentFileContent(options.newFileContent);
if (newFileContent !== undefined) {
assert(!newRangeContent);
if (typeof newFileContent === "string") {
this.verifyCurrentFileContent(newFileContent);
}
else {
for (const fileName in options.newFileContent) {
this.verifyFileContent(fileName, options.newFileContent[fileName]);
for (const fileName in newFileContent) {
this.verifyFileContent(fileName, newFileContent[fileName]);
}
}
}
else {
this.verifyRangeIs(options.newRangeContent!, /*includeWhitespace*/ true);
this.verifyRangeIs(newRangeContent!, /*includeWhitespace*/ true);
}
}
@@ -2985,7 +2975,7 @@ Actual: ${stringify(fullActual)}`);
}
public verifyApplicableRefactorAvailableAtMarker(negative: boolean, markerName: string) {
const isAvailable = this.getApplicableRefactors(this.getMarkerByName(markerName).position).length > 0;
const isAvailable = this.getApplicableRefactors(this.getMarkerByName(markerName)).length > 0;
if (negative && isAvailable) {
this.raiseError(`verifyApplicableRefactorAvailableAtMarker failed - expected no refactor at marker ${markerName} but found some.`);
}
@@ -3002,7 +2992,7 @@ Actual: ${stringify(fullActual)}`);
}
public verifyRefactorAvailable(negative: boolean, name: string, actionName?: string) {
let refactors = this.getApplicableRefactors(this.getSelection());
let refactors = this.getApplicableRefactorsAtSelection();
refactors = refactors.filter(r => r.name === name && (actionName === undefined || r.actions.some(a => a.name === actionName)));
const isAvailable = refactors.length > 0;
@@ -3022,11 +3012,11 @@ Actual: ${stringify(fullActual)}`);
}
public verifyRefactorsAvailable(names: ReadonlyArray<string>): void {
assert.deepEqual(unique(this.getApplicableRefactors(this.getSelection()), r => r.name), names);
assert.deepEqual(unique(this.getApplicableRefactorsAtSelection(), r => r.name), names);
}
public verifyRefactor({ name, actionName, refactors }: FourSlashInterface.VerifyRefactorOptions) {
const actualRefactors = this.getApplicableRefactors(this.getSelection()).filter(r => r.name === name && r.actions.some(a => a.name === actionName));
const actualRefactors = this.getApplicableRefactorsAtSelection().filter(r => r.name === name && r.actions.some(a => a.name === actionName));
this.assertObjectsEqual(actualRefactors, refactors);
}
@@ -3047,7 +3037,7 @@ Actual: ${stringify(fullActual)}`);
public applyRefactor({ refactorName, actionName, actionDescription, newContent: newContentWithRenameMarker }: FourSlashInterface.ApplyRefactorOptions) {
const range = this.getSelection();
const refactors = this.getApplicableRefactors(range);
const refactors = this.getApplicableRefactorsAtSelection();
const refactorsWithName = refactors.filter(r => r.name === refactorName);
if (refactorsWithName.length === 0) {
this.raiseError(`The expected refactor: ${refactorName} is not available at the marker location.\nAvailable refactors: ${refactors.map(r => r.name)}`);
@@ -3125,8 +3115,8 @@ Actual: ${stringify(fullActual)}`);
const action = ts.first(refactor.actions);
assert(action.name === "Move to a new file" && action.description === "Move to a new file");
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, this.formatCodeSettings, range, refactor.name, action.name, options.preferences || ts.emptyOptions)!;
this.testNewFileContents(editInfo.edits, options.newFileContents, "move to new file");
const editInfo = this.languageService.getEditsForRefactor(range.fileName, this.formatCodeSettings, range, refactor.name, action.name, options.preferences || ts.emptyOptions)!;
this.verifyNewContent({ newFileContent: options.newFileContents }, editInfo.edits);
}
private testNewFileContents(edits: ReadonlyArray<ts.FileTextChanges>, newFileContents: { [fileName: string]: string }, description: string): void {
@@ -3165,21 +3155,21 @@ Actual: ${stringify(fullActual)}`);
formattingOptions?: ts.FormatCodeSettings) {
formattingOptions = formattingOptions || this.formatCodeSettings;
const markerPos = this.getMarkerByName(markerName).position;
const marker = this.getMarkerByName(markerName);
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, markerPos, ts.emptyOptions);
const applicableRefactors = this.languageService.getApplicableRefactors(this.activeFile.fileName, marker.position, ts.emptyOptions);
const applicableRefactorToApply = ts.find(applicableRefactors, refactor => refactor.name === refactorNameToApply);
if (!applicableRefactorToApply) {
this.raiseError(`The expected refactor: ${refactorNameToApply} is not available at the marker location.`);
}
const editInfo = this.languageService.getEditsForRefactor(this.activeFile.fileName, formattingOptions, markerPos, refactorNameToApply, actionName, ts.emptyOptions)!;
const editInfo = this.languageService.getEditsForRefactor(marker.fileName, formattingOptions, marker.position, refactorNameToApply, actionName, ts.emptyOptions)!;
for (const edit of editInfo.edits) {
this.applyEdits(edit.fileName, edit.textChanges, /*isFormattingEdit*/ false);
}
const actualContent = this.getFileContent(this.activeFile.fileName);
const actualContent = this.getFileContent(marker.fileName);
if (actualContent !== expectedContent) {
this.raiseError(`verifyFileAfterApplyingRefactors failed:\n${showTextDiff(expectedContent, actualContent)}`);
@@ -3381,9 +3371,45 @@ Actual: ${stringify(fullActual)}`);
test(renameKeys(newFileContents, key => pathUpdater(key) || key), "with file moved");
}
private getApplicableRefactors(positionOrRange: number | ts.TextRange, preferences = ts.emptyOptions): ReadonlyArray<ts.ApplicableRefactorInfo> {
return this.languageService.getApplicableRefactors(this.activeFile.fileName, positionOrRange, preferences) || ts.emptyArray;
private getApplicableRefactorsAtSelection() {
return this.getApplicableRefactorsWorker(this.getSelection(), this.activeFile.fileName);
}
private getApplicableRefactors(rangeOrMarker: Range | Marker, preferences = ts.emptyOptions): ReadonlyArray<ts.ApplicableRefactorInfo> {
return this.getApplicableRefactorsWorker("position" in rangeOrMarker ? rangeOrMarker.position : rangeOrMarker, rangeOrMarker.fileName, preferences);
}
private getApplicableRefactorsWorker(positionOrRange: number | ts.TextRange, fileName: string, preferences = ts.emptyOptions): ReadonlyArray<ts.ApplicableRefactorInfo> {
return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences) || ts.emptyArray;
}
}
function updateTextRangeForTextChanges({ pos, end }: ts.TextRange, textChanges: ReadonlyArray<ts.TextChange>): ts.TextRange {
forEachTextChange(textChanges, change => {
const update = (p: number): number => updatePosition(p, change.span.start, ts.textSpanEnd(change.span), change.newText);
pos = update(pos);
end = update(end);
});
return { pos, end };
}
/** Apply each textChange in order, updating future changes to account for the text offset of previous changes. */
function forEachTextChange(changes: ReadonlyArray<ts.TextChange>, cb: (change: ts.TextChange) => void): void {
// Copy this so we don't ruin someone else's copy
changes = JSON.parse(JSON.stringify(changes));
for (let i = 0; i < changes.length; i++) {
const change = changes[i];
cb(change);
const changeDelta = change.newText.length - change.span.length;
for (let j = i + 1; j < changes.length; j++) {
if (changes[j].span.start >= change.span.start) {
changes[j].span.start += changeDelta;
}
}
}
}
function updatePosition(position: number, editStart: number, editEnd: number, { length }: string): number {
// If inside the edit, return -1 to mark as invalid
return position <= editStart ? position : position < editEnd ? -1 : position + length - + (editEnd - editStart);
}
function renameKeys<T>(obj: { readonly [key: string]: T }, renameKey: (key: string) => string): { readonly [key: string]: T } {
@@ -4764,6 +4790,7 @@ namespace FourSlashInterface {
export type ExpectedCompletionEntry = string | {
readonly name: string,
readonly source?: string,
readonly insertText?: string,
readonly replacementSpan?: FourSlash.Range,
readonly hasAction?: boolean, // If not specified, will assert that this is false.
@@ -4848,10 +4875,12 @@ namespace FourSlashInterface {
}
export interface VerifyCodeFixOptions extends NewContentOptions {
description: string;
errorCode?: number;
index?: number;
preferences?: ts.UserPreferences;
readonly description: string;
readonly errorCode?: number;
readonly index?: number;
readonly preferences?: ts.UserPreferences;
readonly applyChanges?: boolean;
readonly commands?: ReadonlyArray<ts.CodeActionCommand>;
}
export interface VerifyCodeFixAvailableOptions {
+54 -77
View File
@@ -1483,17 +1483,12 @@ namespace Harness {
}
export function doErrorBaseline(baselinePath: string, inputFiles: ReadonlyArray<TestFile>, errors: ReadonlyArray<ts.Diagnostic>, pretty?: boolean) {
Baseline.runBaseline(baselinePath.replace(/\.tsx?$/, ".errors.txt"), (): string | null => {
if (!errors || (errors.length === 0)) {
/* tslint:disable:no-null-keyword */
return null;
/* tslint:enable:no-null-keyword */
}
return getErrorBaseline(inputFiles, errors, pretty);
});
Baseline.runBaseline(baselinePath.replace(/\.tsx?$/, ".errors.txt"),
// tslint:disable-next-line no-null-keyword
!errors || (errors.length === 0) ? null : getErrorBaseline(inputFiles, errors, pretty));
}
export function doTypeAndSymbolBaseline(baselinePath: string, program: ts.Program, allFiles: {unitName: string, content: string}[], opts?: Baseline.BaselineOptions, multifile?: boolean, skipTypeBaselines?: boolean, skipSymbolBaselines?: boolean) {
export function doTypeAndSymbolBaseline(baselinePath: string, program: ts.Program, allFiles: {unitName: string, content: string}[], opts?: Baseline.BaselineOptions, multifile?: boolean, skipTypeBaselines?: boolean, skipSymbolBaselines?: boolean, hasErrorBaseline?: boolean) {
// The full walker simulates the types that you would get from doing a full
// compile. The pull walker simulates the types you get when you just do
// a type query for a random node (like how the LS would do it). Most of the
@@ -1509,7 +1504,7 @@ namespace Harness {
// These types are equivalent, but depend on what order the compiler observed
// certain parts of the program.
const fullWalker = new TypeWriterWalker(program, /*fullTypeCheck*/ true);
const fullWalker = new TypeWriterWalker(program, /*fullTypeCheck*/ true, !!hasErrorBaseline);
// Produce baselines. The first gives the types for all expressions.
// The second gives symbols for all identifiers.
@@ -1552,7 +1547,7 @@ namespace Harness {
if (!multifile) {
const fullBaseLine = generateBaseLine(isSymbolBaseLine, isSymbolBaseLine ? skipSymbolBaselines : skipTypeBaselines);
Baseline.runBaseline(outputFileName + fullExtension, () => fullBaseLine, opts);
Baseline.runBaseline(outputFileName + fullExtension, fullBaseLine, opts);
}
else {
Baseline.runMultifileBaseline(outputFileName, fullExtension, () => {
@@ -1637,22 +1632,21 @@ namespace Harness {
throw new Error("Number of sourcemap files should be same as js files.");
}
Baseline.runBaseline(baselinePath.replace(/\.tsx?/, ".js.map"), () => {
if ((options.noEmitOnError && result.diagnostics.length !== 0) || result.maps.size === 0) {
// We need to return null here or the runBaseLine will actually create a empty file.
// Baselining isn't required here because there is no output.
/* tslint:disable:no-null-keyword */
return null;
/* tslint:enable:no-null-keyword */
}
let sourceMapCode = "";
let sourceMapCode: string | null;
if ((options.noEmitOnError && result.diagnostics.length !== 0) || result.maps.size === 0) {
// We need to return null here or the runBaseLine will actually create a empty file.
// Baselining isn't required here because there is no output.
/* tslint:disable:no-null-keyword */
sourceMapCode = null;
/* tslint:enable:no-null-keyword */
}
else {
sourceMapCode = "";
result.maps.forEach(sourceMap => {
sourceMapCode += fileOutput(sourceMap, harnessSettings);
});
return sourceMapCode;
});
}
Baseline.runBaseline(baselinePath.replace(/\.tsx?/, ".js.map"), sourceMapCode);
}
}
@@ -1662,49 +1656,40 @@ namespace Harness {
}
// check js output
Baseline.runBaseline(baselinePath.replace(/\.tsx?/, ts.Extension.Js), () => {
let tsCode = "";
const tsSources = otherFiles.concat(toBeCompiled);
if (tsSources.length > 1) {
tsCode += "//// [" + header + "] ////\r\n\r\n";
}
for (let i = 0; i < tsSources.length; i++) {
tsCode += "//// [" + ts.getBaseFileName(tsSources[i].unitName) + "]\r\n";
tsCode += tsSources[i].content + (i < (tsSources.length - 1) ? "\r\n" : "");
}
let tsCode = "";
const tsSources = otherFiles.concat(toBeCompiled);
if (tsSources.length > 1) {
tsCode += "//// [" + header + "] ////\r\n\r\n";
}
for (let i = 0; i < tsSources.length; i++) {
tsCode += "//// [" + ts.getBaseFileName(tsSources[i].unitName) + "]\r\n";
tsCode += tsSources[i].content + (i < (tsSources.length - 1) ? "\r\n" : "");
}
let jsCode = "";
result.js.forEach(file => {
jsCode += fileOutput(file, harnessSettings);
});
if (result.dts.size > 0) {
jsCode += "\r\n\r\n";
result.dts.forEach(declFile => {
jsCode += fileOutput(declFile, harnessSettings);
});
}
const declFileContext = prepareDeclarationCompilationContext(
toBeCompiled, otherFiles, result, harnessSettings, options, /*currentDirectory*/ undefined
);
const declFileCompilationResult = compileDeclarationFiles(declFileContext, result.symlinks);
if (declFileCompilationResult && declFileCompilationResult.declResult.diagnostics.length) {
jsCode += "\r\n\r\n//// [DtsFileErrors]\r\n";
jsCode += "\r\n\r\n";
jsCode += getErrorBaseline(tsConfigFiles.concat(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.diagnostics);
}
if (jsCode.length > 0) {
return tsCode + "\r\n\r\n" + jsCode;
}
else {
/* tslint:disable:no-null-keyword */
return null;
/* tslint:enable:no-null-keyword */
}
let jsCode = "";
result.js.forEach(file => {
jsCode += fileOutput(file, harnessSettings);
});
if (result.dts.size > 0) {
jsCode += "\r\n\r\n";
result.dts.forEach(declFile => {
jsCode += fileOutput(declFile, harnessSettings);
});
}
const declFileContext = prepareDeclarationCompilationContext(
toBeCompiled, otherFiles, result, harnessSettings, options, /*currentDirectory*/ undefined
);
const declFileCompilationResult = compileDeclarationFiles(declFileContext, result.symlinks);
if (declFileCompilationResult && declFileCompilationResult.declResult.diagnostics.length) {
jsCode += "\r\n\r\n//// [DtsFileErrors]\r\n";
jsCode += "\r\n\r\n";
jsCode += getErrorBaseline(tsConfigFiles.concat(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.diagnostics);
}
Baseline.runBaseline(baselinePath.replace(/\.tsx?/, ts.Extension.Js), jsCode.length > 0 ? tsCode + "\r\n\r\n" + jsCode : null); // tslint:disable-line no-null-keyword
}
function fileOutput(file: documents.TextDocument, harnessSettings: TestCaseParser.CompilerSettings): string {
@@ -2027,16 +2012,6 @@ namespace Harness {
}
const fileCache: { [idx: string]: boolean } = {};
function generateActual(generateContent: () => string | null): string | null {
const actual = generateContent();
if (actual === undefined) {
throw new Error("The generated content was \"undefined\". Return \"null\" if no baselining is required.\"");
}
return actual;
}
function compareToBaseline(actual: string | null, relativeFileName: string, opts: BaselineOptions | undefined) {
// actual is now either undefined (the generator had an error), null (no file requested),
@@ -2100,9 +2075,11 @@ namespace Harness {
}
}
export function runBaseline(relativeFileName: string, generateContent: () => string | null, opts?: BaselineOptions): void {
export function runBaseline(relativeFileName: string, actual: string | null, opts?: BaselineOptions): void {
const actualFileName = localPath(relativeFileName, opts && opts.Baselinefolder, opts && opts.Subfolder);
const actual = generateActual(generateContent);
if (actual === undefined) {
throw new Error("The generated content was \"undefined\". Return \"null\" if no baselining is required.\"");
}
const comparison = compareToBaseline(actual, relativeFileName, opts);
writeComparison(comparison.expected, comparison.actual, relativeFileName, actualFileName);
}
+46 -2
View File
@@ -25,7 +25,7 @@ class TypeWriterWalker {
private checker: ts.TypeChecker;
constructor(private program: ts.Program, fullTypeCheck: boolean) {
constructor(private program: ts.Program, fullTypeCheck: boolean, private hadErrorBaseline: boolean) {
// Consider getting both the diagnostics checker and the non-diagnostics checker to verify
// they are consistent.
this.checker = fullTypeCheck
@@ -69,6 +69,26 @@ class TypeWriterWalker {
}
}
private isImportStatementName(node: ts.Node) {
if (ts.isImportSpecifier(node.parent) && (node.parent.name === node || node.parent.propertyName === node)) return true;
if (ts.isImportClause(node.parent) && node.parent.name === node) return true;
if (ts.isImportEqualsDeclaration(node.parent) && node.parent.name === node) return true;
return false;
}
private isExportStatementName(node: ts.Node) {
if (ts.isExportAssignment(node.parent) && node.parent.expression === node) return true;
if (ts.isExportSpecifier(node.parent) && (node.parent.name === node || node.parent.propertyName === node)) return true;
return false;
}
private isIntrinsicJsxTag(node: ts.Node) {
const p = node.parent;
if (!(ts.isJsxOpeningElement(p) || ts.isJsxClosingElement(p) || ts.isJsxSelfClosingElement(p))) return false;
if (p.tagName !== node) return false;
return ts.isIntrinsicJsxName(node.getText());
}
private writeTypeOrSymbol(node: ts.Node, isSymbolWalk: boolean): TypeWriterResult | undefined {
const actualPos = ts.skipTrivia(this.currentSourceFile.text, node.pos);
const lineAndCharacter = this.currentSourceFile.getLineAndCharacterOfPosition(actualPos);
@@ -85,7 +105,31 @@ class TypeWriterWalker {
// let type = this.checker.getTypeAtLocation(node);
let type = ts.isExpressionWithTypeArgumentsInClassExtendsClause(node.parent) ? this.checker.getTypeAtLocation(node.parent) : undefined;
if (!type || type.flags & ts.TypeFlags.Any) type = this.checker.getTypeAtLocation(node);
const typeString = this.checker.typeToString(type, node.parent, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.AllowUniqueESSymbolType);
const typeString =
// Distinguish `errorType`s from `any`s; but only if the file has no errors.
// Additionally,
// * the LHS of a qualified name
// * a binding pattern name
// * labels
// * the "global" in "declare global"
// * the "target" in "new.target"
// * names in import statements
// * type-only names in export statements
// * and intrinsic jsx tag names
// return `error`s via `getTypeAtLocation`
// But this is generally expected, so we don't call those out, either
(!this.hadErrorBaseline &&
type.flags & ts.TypeFlags.Any &&
!ts.isBindingElement(node.parent) &&
!ts.isPropertyAccessOrQualifiedName(node.parent) &&
!ts.isLabelName(node) &&
!(ts.isModuleDeclaration(node.parent) && ts.isGlobalScopeAugmentation(node.parent)) &&
!ts.isMetaProperty(node.parent) &&
!this.isImportStatementName(node) &&
!this.isExportStatementName(node) &&
!this.isIntrinsicJsxTag(node)) ?
(type as ts.IntrinsicType).intrinsicName :
this.checker.typeToString(type, node.parent, ts.TypeFormatFlags.NoTruncation | ts.TypeFormatFlags.AllowUniqueESSymbolType);
return {
line: lineAndCharacter.line,
syntaxKind: node.kind,
+11 -3
View File
@@ -620,14 +620,14 @@ interface Array<T> {}`
}
}
removeFile(filePath: string) {
deleteFile(filePath: string) {
const path = this.toFullPath(filePath);
const currentEntry = this.fs.get(path) as FsFile;
Debug.assert(isFsFile(currentEntry));
this.removeFileOrFolder(currentEntry, returnFalse);
}
removeFolder(folderPath: string, recursive?: boolean) {
deleteFolder(folderPath: string, recursive?: boolean) {
const path = this.toFullPath(folderPath);
const currentEntry = this.fs.get(path) as FsFolder;
Debug.assert(isFsFolder(currentEntry));
@@ -635,7 +635,7 @@ interface Array<T> {}`
const subEntries = currentEntry.entries.slice();
subEntries.forEach(fsEntry => {
if (isFsFolder(fsEntry)) {
this.removeFolder(fsEntry.fullPath, recursive);
this.deleteFolder(fsEntry.fullPath, recursive);
}
else {
this.removeFileOrFolder(fsEntry, returnFalse);
@@ -766,6 +766,14 @@ interface Array<T> {}`
return (fsEntry && fsEntry.modifiedTime)!; // TODO: GH#18217
}
setModifiedTime(s: string, date: Date) {
const path = this.toFullPath(s);
const fsEntry = this.fs.get(path);
if (fsEntry) {
fsEntry.modifiedTime = date;
}
}
readFile(s: string): string | undefined {
const fsEntry = this.getRealFile(this.toFullPath(s));
return fsEntry ? fsEntry.content : undefined;
+10
View File
@@ -1378,6 +1378,16 @@ type Extract<T, U> = T extends U ? T : never;
*/
type NonNullable<T> = T extends null | undefined ? never : T;
/**
* Obtain the parameters of a function type in a tuple
*/
type Parameters<T extends (...args: any[]) => any> = T extends (...args: infer P) => any ? P : never;
/**
* Obtain the parameters of a constructor function type in a tuple
*/
type ConstructorParameters<T extends new (...args: any[]) => any> = T extends new (...args: infer P) => any ? P : never;
/**
* Obtain the return type of a function type
*/
+2 -2
View File
@@ -11,7 +11,7 @@ interface ReadonlyArray<T> {
* thisArg is omitted, undefined is used as the this value.
*/
flatMap<U, This = undefined> (
callback: (this: This, value: T, index: number, array: T[]) => U|U[],
callback: (this: This, value: T, index: number, array: T[]) => U|ReadonlyArray<U>,
thisArg?: This
): U[]
@@ -125,7 +125,7 @@ interface Array<T> {
* thisArg is omitted, undefined is used as the this value.
*/
flatMap<U, This = undefined> (
callback: (this: This, value: T, index: number, array: T[]) => U|U[],
callback: (this: This, value: T, index: number, array: T[]) => U|ReadonlyArray<U>,
thisArg?: This
): U[]
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+193 -107
View File
@@ -5,6 +5,8 @@ namespace ts.server {
// tslint:disable variable-name
export const ProjectsUpdatedInBackgroundEvent = "projectsUpdatedInBackground";
export const SurveyReady = "surveyReady";
export const LargeFileReferencedEvent = "largeFileReferenced";
export const ConfigFileDiagEvent = "configFileDiag";
export const ProjectLanguageServiceStateEvent = "projectLanguageServiceState";
export const ProjectInfoTelemetryEvent = "projectInfo";
@@ -16,6 +18,16 @@ namespace ts.server {
data: { openFiles: string[]; };
}
export interface SurveyReady {
eventName: typeof SurveyReady;
data: { surveyId: string; };
}
export interface LargeFileReferencedEvent {
eventName: typeof LargeFileReferencedEvent;
data: { file: string; fileSize: number; maxFileSize: number; };
}
export interface ConfigFileDiagEvent {
eventName: typeof ConfigFileDiagEvent;
data: { triggerFile: string, configFileName: string, diagnostics: ReadonlyArray<Diagnostic> };
@@ -92,7 +104,7 @@ namespace ts.server {
readonly checkJs: boolean;
}
export type ProjectServiceEvent = ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent;
export type ProjectServiceEvent = LargeFileReferencedEvent | SurveyReady | ProjectsUpdatedInBackgroundEvent | ConfigFileDiagEvent | ProjectLanguageServiceStateEvent | ProjectInfoTelemetryEvent | OpenFileInfoTelemetryEvent;
export type ProjectServiceEventHandler = (event: ProjectServiceEvent) => void;
@@ -212,9 +224,15 @@ namespace ts.server {
}
}
/*@internal*/
export function convertUserPreferences(preferences: protocol.UserPreferences): UserPreferences {
const { lazyConfiguredProjectsFromExternalProject, ...userPreferences } = preferences;
return userPreferences;
}
export interface HostConfiguration {
formatCodeOptions: FormatCodeSettings;
preferences: UserPreferences;
preferences: protocol.UserPreferences;
hostInfo: string;
extraFileExtensions?: FileExtensionInfo[];
}
@@ -344,6 +362,12 @@ namespace ts.server {
return project.dirty && project.updateGraph();
}
function setProjectOptionsUsed(project: ConfiguredProject | ExternalProject) {
if (project.projectKind === ProjectKind.Configured) {
(project as ConfiguredProject).projectOptions = true;
}
}
export class ProjectService {
/*@internal*/
@@ -444,6 +468,9 @@ namespace ts.server {
/** Tracks projects that we have already sent telemetry for. */
private readonly seenProjects = createMap<true>();
/** Tracks projects that we have already sent survey events for. */
private readonly seenSurveyProjects = createMap<true>();
/*@internal*/
readonly watchFactory: WatchFactory<WatchType, Project>;
@@ -645,6 +672,27 @@ namespace ts.server {
this.eventHandler(event);
}
/* @internal */
sendSurveyReadyEvent(surveyId: string) {
if (!this.eventHandler) {
return;
}
this.eventHandler({ eventName: SurveyReady, data: { surveyId } });
}
/* @internal */
sendLargeFileReferencedEvent(file: string, fileSize: number) {
if (!this.eventHandler) {
return;
}
const event: LargeFileReferencedEvent = {
eventName: LargeFileReferencedEvent,
data: { file, fileSize, maxFileSize }
};
this.eventHandler(event);
}
/* @internal */
delayUpdateProjectGraphAndEnsureProjectStructureForOpenFiles(project: Project) {
this.delayUpdateProjectGraph(project);
@@ -777,7 +825,7 @@ namespace ts.server {
return info && info.getFormatCodeSettings() || this.hostConfiguration.formatCodeOptions;
}
getPreferences(file: NormalizedPath): UserPreferences {
getPreferences(file: NormalizedPath): protocol.UserPreferences {
const info = this.getScriptInfoForNormalizedPath(file);
return info && info.getPreferences() || this.hostConfiguration.preferences;
}
@@ -786,7 +834,7 @@ namespace ts.server {
return this.hostConfiguration.formatCodeOptions;
}
getHostPreferences(): UserPreferences {
getHostPreferences(): protocol.UserPreferences {
return this.hostConfiguration.preferences;
}
@@ -1385,47 +1433,6 @@ namespace ts.server {
return findProjectByName(projectFileName, this.externalProjects);
}
private convertConfigFileContentToProjectOptions(configFilename: string, cachedDirectoryStructureHost: CachedDirectoryStructureHost) {
configFilename = normalizePath(configFilename);
const configFileContent = this.host.readFile(configFilename)!; // TODO: GH#18217
const result = parseJsonText(configFilename, configFileContent);
if (!result.endOfFileToken) {
result.endOfFileToken = <EndOfFileToken>{ kind: SyntaxKind.EndOfFileToken };
}
const errors = result.parseDiagnostics as Diagnostic[];
const parsedCommandLine = parseJsonSourceFileConfigFileContent(
result,
cachedDirectoryStructureHost,
getDirectoryPath(configFilename),
/*existingOptions*/ {},
configFilename,
/*resolutionStack*/[],
this.hostConfiguration.extraFileExtensions);
if (parsedCommandLine.errors.length) {
errors.push(...parsedCommandLine.errors);
}
Debug.assert(!!parsedCommandLine.fileNames);
const projectOptions: ProjectOptions = {
files: parsedCommandLine.fileNames,
compilerOptions: parsedCommandLine.options,
configHasExtendsProperty: parsedCommandLine.raw.extends !== undefined,
configHasFilesProperty: parsedCommandLine.raw.files !== undefined,
configHasIncludeProperty: parsedCommandLine.raw.include !== undefined,
configHasExcludeProperty: parsedCommandLine.raw.exclude !== undefined,
wildcardDirectories: createMapFromTemplate(parsedCommandLine.wildcardDirectories!), // TODO: GH#18217
typeAcquisition: parsedCommandLine.typeAcquisition,
compileOnSave: parsedCommandLine.compileOnSave,
projectReferences: parsedCommandLine.projectReferences
};
return { projectOptions, configFileErrors: errors, configFileSpecs: parsedCommandLine.configFileSpecs };
}
/** Get a filename if the language service exceeds the maximum allowed program size; otherwise returns undefined. */
private getFilenameForExceededTotalSizeLimitForNonTsFiles<T>(name: string, options: CompilerOptions | undefined, fileNames: T[], propertyReader: FilePropertyReader<T>): string | undefined {
if (options && options.disableSizeLimit || !this.host.getFileSize) {
@@ -1482,24 +1489,42 @@ namespace ts.server {
options.compileOnSave === undefined ? true : options.compileOnSave);
project.excludedFiles = excludedFiles;
this.addFilesToNonInferredProjectAndUpdateGraph(project, files, externalFilePropertyReader, typeAcquisition);
this.addFilesToNonInferredProject(project, files, externalFilePropertyReader, typeAcquisition);
this.externalProjects.push(project);
this.sendProjectTelemetry(projectFileName, project);
return project;
}
private sendProjectTelemetry(projectKey: string, project: ExternalProject | ConfiguredProject, projectOptions?: ProjectOptions): void {
if (this.seenProjects.has(projectKey)) {
/*@internal*/
sendSurveyReady(project: ExternalProject | ConfiguredProject): void {
if (this.seenSurveyProjects.has(project.projectName)) {
return;
}
this.seenProjects.set(projectKey, true);
if (project.getCompilerOptions().checkJs !== undefined) {
const name = "checkJs";
this.logger.info(`Survey ${name} is ready`);
this.sendSurveyReadyEvent(name);
this.seenSurveyProjects.set(project.projectName, true);
}
}
/*@internal*/
sendProjectTelemetry(project: ExternalProject | ConfiguredProject): void {
if (this.seenProjects.has(project.projectName)) {
setProjectOptionsUsed(project);
return;
}
this.seenProjects.set(project.projectName, true);
if (!this.eventHandler || !this.host.createSHA256Hash) {
setProjectOptionsUsed(project);
return;
}
const projectOptions = project.projectKind === ProjectKind.Configured ? (project as ConfiguredProject).projectOptions as ProjectOptions : undefined;
setProjectOptionsUsed(project);
const data: ProjectInfoTelemetryEventData = {
projectId: this.host.createSHA256Hash(projectKey),
projectId: this.host.createSHA256Hash(project.projectName),
fileStats: countEachFileTypes(project.getScriptInfos()),
compilerOptions: convertCompilerOptionsForTelemetry(project.getCompilationSettings()),
typeAcquisition: convertTypeAcquisition(project.getTypeAcquisition()),
@@ -1520,8 +1545,7 @@ namespace ts.server {
return "other";
}
const configFilePath = project instanceof ConfiguredProject ? project.getConfigFilePath() : undefined!; // TODO: GH#18217
return getBaseConfigFileName(configFilePath) || "other";
return getBaseConfigFileName(project.getConfigFilePath()) || "other";
}
function convertTypeAcquisition({ enable, include, exclude }: TypeAcquisition): ProjectInfoTypeAcquisitionData {
@@ -1533,30 +1557,19 @@ namespace ts.server {
}
}
private addFilesToNonInferredProjectAndUpdateGraph<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, typeAcquisition: TypeAcquisition): void {
private addFilesToNonInferredProject<T>(project: ConfiguredProject | ExternalProject, files: T[], propertyReader: FilePropertyReader<T>, typeAcquisition: TypeAcquisition): void {
this.updateNonInferredProjectFiles(project, files, propertyReader);
project.setTypeAcquisition(typeAcquisition);
// This doesnt need scheduling since its either creation or reload of the project
project.updateGraph();
}
private createConfiguredProject(configFileName: NormalizedPath) {
const cachedDirectoryStructureHost = createCachedDirectoryStructureHost(this.host, this.host.getCurrentDirectory(), this.host.useCaseSensitiveFileNames)!; // TODO: GH#18217
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, cachedDirectoryStructureHost);
this.logger.info(`Opened configuration file ${configFileName}`);
const lastFileExceededProgramSize = this.getFilenameForExceededTotalSizeLimitForNonTsFiles(configFileName, projectOptions.compilerOptions, projectOptions.files!, fileNamePropertyReader); // TODO: GH#18217
const project = new ConfiguredProject(
configFileName,
this,
this.documentRegistry,
projectOptions.configHasFilesProperty,
projectOptions.compilerOptions!, // TODO: GH#18217
lastFileExceededProgramSize,
projectOptions.compileOnSave === undefined ? false : projectOptions.compileOnSave,
cachedDirectoryStructureHost,
projectOptions.projectReferences);
project.configFileSpecs = configFileSpecs;
cachedDirectoryStructureHost);
// TODO: We probably should also watch the configFiles that are extended
project.configFileWatcher = this.watchFactory.watchFile(
this.host,
@@ -1566,19 +1579,89 @@ namespace ts.server {
WatchType.ConfigFilePath,
project
);
if (!lastFileExceededProgramSize) {
project.watchWildcards(projectOptions.wildcardDirectories!); // TODO: GH#18217
}
project.setProjectErrors(configFileErrors);
const filesToAdd = projectOptions.files!.concat(project.getExternalFiles());
this.addFilesToNonInferredProjectAndUpdateGraph(project, filesToAdd, fileNamePropertyReader, projectOptions.typeAcquisition!); // TODO: GH#18217
this.configuredProjects.set(project.canonicalConfigFilePath, project);
this.setConfigFileExistenceByNewConfiguredProject(project);
this.sendProjectTelemetry(configFileName, project, projectOptions);
return project;
}
/* @internal */
private createConfiguredProjectWithDelayLoad(configFileName: NormalizedPath) {
const project = this.createConfiguredProject(configFileName);
project.pendingReload = ConfigFileProgramReloadLevel.Full;
return project;
}
/* @internal */
private createAndLoadConfiguredProject(configFileName: NormalizedPath) {
const project = this.createConfiguredProject(configFileName);
this.loadConfiguredProject(project);
return project;
}
/* @internal */
private createLoadAndUpdateConfiguredProject(configFileName: NormalizedPath) {
const project = this.createAndLoadConfiguredProject(configFileName);
project.updateGraph();
return project;
}
/**
* Read the config file of the project, and update the project root file names.
*/
/* @internal */
private loadConfiguredProject(project: ConfiguredProject) {
// Read updated contents from disk
const configFilename = normalizePath(project.getConfigFilePath());
const configFileContent = this.host.readFile(configFilename)!; // TODO: GH#18217
const result = parseJsonText(configFilename, configFileContent);
if (!result.endOfFileToken) {
result.endOfFileToken = <EndOfFileToken>{ kind: SyntaxKind.EndOfFileToken };
}
const configFileErrors = result.parseDiagnostics as Diagnostic[];
const parsedCommandLine = parseJsonSourceFileConfigFileContent(
result,
project.getCachedDirectoryStructureHost(),
getDirectoryPath(configFilename),
/*existingOptions*/ {},
configFilename,
/*resolutionStack*/[],
this.hostConfiguration.extraFileExtensions);
if (parsedCommandLine.errors.length) {
configFileErrors.push(...parsedCommandLine.errors);
}
Debug.assert(!!parsedCommandLine.fileNames);
const compilerOptions = parsedCommandLine.options;
// Update the project
if (!project.projectOptions) {
project.projectOptions = {
configHasExtendsProperty: parsedCommandLine.raw.extends !== undefined,
configHasFilesProperty: parsedCommandLine.raw.files !== undefined,
configHasIncludeProperty: parsedCommandLine.raw.include !== undefined,
configHasExcludeProperty: parsedCommandLine.raw.exclude !== undefined
};
}
project.configFileSpecs = parsedCommandLine.configFileSpecs;
project.setProjectErrors(configFileErrors);
project.updateReferences(parsedCommandLine.projectReferences);
const lastFileExceededProgramSize = this.getFilenameForExceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, compilerOptions, parsedCommandLine.fileNames, fileNamePropertyReader);
if (lastFileExceededProgramSize) {
project.disableLanguageService(lastFileExceededProgramSize);
project.stopWatchingWildCards();
}
else {
project.enableLanguageService();
project.watchWildcards(createMapFromTemplate(parsedCommandLine.wildcardDirectories!)); // TODO: GH#18217
}
project.enablePluginsWithOptions(compilerOptions);
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles());
this.updateRootAndOptionsOfNonInferredProject(project, filesToAdd, fileNamePropertyReader, compilerOptions, parsedCommandLine.typeAcquisition!, parsedCommandLine.compileOnSave!); // TODO: GH#18217
}
private updateNonInferredProjectFiles<T>(project: ExternalProject | ConfiguredProject, files: T[], propertyReader: FilePropertyReader<T>) {
const projectRootFilesMap = project.getRootFilesMap();
const newRootScriptInfoMap = createMap<ProjectRoot>();
@@ -1637,31 +1720,31 @@ namespace ts.server {
project.markAsDirty();
}
private updateNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean | undefined) {
private updateRootAndOptionsOfNonInferredProject<T>(project: ExternalProject | ConfiguredProject, newUncheckedFiles: T[], propertyReader: FilePropertyReader<T>, newOptions: CompilerOptions, newTypeAcquisition: TypeAcquisition, compileOnSave: boolean | undefined) {
project.setCompilerOptions(newOptions);
// VS only set the CompileOnSaveEnabled option in the request if the option was changed recently
// therefore if it is undefined, it should not be updated.
if (compileOnSave !== undefined) {
project.compileOnSaveEnabled = compileOnSave;
}
this.addFilesToNonInferredProjectAndUpdateGraph(project, newUncheckedFiles, propertyReader, newTypeAcquisition);
this.addFilesToNonInferredProject(project, newUncheckedFiles, propertyReader, newTypeAcquisition);
}
/**
* Reload the file names from config file specs and update the project graph
*/
/*@internal*/
reloadFileNamesOfConfiguredProject(project: ConfiguredProject): boolean {
reloadFileNamesOfConfiguredProject(project: ConfiguredProject) {
const configFileSpecs = project.configFileSpecs!; // TODO: GH#18217
const configFileName = project.getConfigFilePath();
const fileNamesResult = getFileNamesFromConfigSpecs(configFileSpecs, getDirectoryPath(configFileName), project.getCompilationSettings(), project.getCachedDirectoryStructureHost(), this.hostConfiguration.extraFileExtensions);
project.updateErrorOnNoInputFiles(fileNamesResult.fileNames.length !== 0);
this.updateNonInferredProjectFiles(project, fileNamesResult.fileNames, fileNamePropertyReader);
this.updateNonInferredProjectFiles(project, fileNamesResult.fileNames.concat(project.getExternalFiles()), fileNamePropertyReader);
return project.updateGraph();
}
/**
* Read the config file of the project again and update the project
* Read the config file of the project again by clearing the cache and update the project graph
*/
/* @internal */
reloadConfiguredProject(project: ConfiguredProject) {
@@ -1673,23 +1756,10 @@ namespace ts.server {
const configFileName = project.getConfigFilePath();
this.logger.info(`Reloading configured project ${configFileName}`);
// Read updated contents from disk
const { projectOptions, configFileErrors, configFileSpecs } = this.convertConfigFileContentToProjectOptions(configFileName, host);
// Load project from the disk
this.loadConfiguredProject(project);
project.updateGraph();
// Update the project
project.configFileSpecs = configFileSpecs;
project.setProjectErrors(configFileErrors);
project.updateReferences(projectOptions.projectReferences);
const lastFileExceededProgramSize = this.getFilenameForExceededTotalSizeLimitForNonTsFiles(project.canonicalConfigFilePath, projectOptions.compilerOptions, projectOptions.files!, fileNamePropertyReader); // TODO: GH#18217
if (lastFileExceededProgramSize) {
project.disableLanguageService(lastFileExceededProgramSize);
project.stopWatchingWildCards();
}
else {
project.enableLanguageService();
project.watchWildcards(projectOptions.wildcardDirectories!); // TODO: GH#18217
}
this.updateNonInferredProject(project, projectOptions.files!, fileNamePropertyReader, projectOptions.compilerOptions!, projectOptions.typeAcquisition!, projectOptions.compileOnSave!); // TODO: GH#18217
this.sendConfigFileDiagEvent(project, configFileName);
}
@@ -1953,7 +2023,19 @@ namespace ts.server {
this.logger.info("Format host information updated");
}
if (args.preferences) {
const { lazyConfiguredProjectsFromExternalProject } = this.hostConfiguration.preferences;
this.hostConfiguration.preferences = { ...this.hostConfiguration.preferences, ...args.preferences };
if (lazyConfiguredProjectsFromExternalProject && !this.hostConfiguration.preferences.lazyConfiguredProjectsFromExternalProject) {
// Load configured projects for external projects that are pending reload
this.configuredProjects.forEach(project => {
if (project.hasExternalProjectRef() &&
project.pendingReload === ConfigFileProgramReloadLevel.Full &&
!this.pendingProjectUpdates.has(project.getProjectName())) {
this.loadConfiguredProject(project);
project.updateGraph();
}
});
}
}
if (args.extraFileExtensions) {
this.hostConfiguration.extraFileExtensions = args.extraFileExtensions;
@@ -2021,17 +2103,14 @@ namespace ts.server {
// otherwise we create a new one.
const configFileName = this.getConfigFileNameForFile(info);
if (configFileName) {
const project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
this.createConfiguredProject(configFileName);
updatedProjects.set(configFileName, true);
}
else if (!updatedProjects.has(configFileName)) {
const project = this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName);
if (!updatedProjects.has(configFileName)) {
if (delayReload) {
project.pendingReload = ConfigFileProgramReloadLevel.Full;
this.delayUpdateProjectGraph(project);
}
else {
// reload from the disk
this.reloadConfiguredProject(project);
}
updatedProjects.set(configFileName, true);
@@ -2119,7 +2198,7 @@ namespace ts.server {
const configFileName = this.getConfigFileNameForFile(originalFileInfo);
if (!configFileName) return undefined;
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || this.createConfiguredProject(configFileName);
const configuredProject = this.findConfiguredProjectByProjectName(configFileName) || this.createAndLoadConfiguredProject(configFileName);
updateProjectIfDirty(configuredProject);
// Keep this configured project as referenced from project
addOriginalConfiguredProject(configuredProject);
@@ -2169,7 +2248,7 @@ namespace ts.server {
if (configFileName) {
project = this.findConfiguredProjectByProjectName(configFileName);
if (!project) {
project = this.createConfiguredProject(configFileName);
project = this.createLoadAndUpdateConfiguredProject(configFileName);
// Send the event only if the project got created as part of this open request and info is part of the project
if (info.isOrphan()) {
// Since the file isnt part of configured project, do not send config file info
@@ -2559,7 +2638,9 @@ namespace ts.server {
externalProject.enableLanguageService();
}
// external project already exists and not config files were added - update the project and return;
this.updateNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, compilerOptions, proj.typeAcquisition, proj.options.compileOnSave);
// The graph update here isnt postponed since any file open operation needs all updated external projects
this.updateRootAndOptionsOfNonInferredProject(externalProject, proj.rootFiles, externalFilePropertyReader, compilerOptions, proj.typeAcquisition, proj.options.compileOnSave);
externalProject.updateGraph();
return;
}
// some config files were added to external project (that previously were not there)
@@ -2606,8 +2687,10 @@ namespace ts.server {
for (const tsconfigFile of tsConfigFiles) {
let project = this.findConfiguredProjectByProjectName(tsconfigFile);
if (!project) {
// errors are stored in the project
project = this.createConfiguredProject(tsconfigFile);
// errors are stored in the project, do not need to update the graph
project = this.getHostPreferences().lazyConfiguredProjectsFromExternalProject ?
this.createConfiguredProjectWithDelayLoad(tsconfigFile) :
this.createLoadAndUpdateConfiguredProject(tsconfigFile);
}
if (project && !contains(exisingConfigFiles, tsconfigFile)) {
// keep project alive even if no documents are opened - its lifetime is bound to the lifetime of containing external project
@@ -2617,8 +2700,11 @@ namespace ts.server {
}
else {
// no config files - remove the item from the collection
// Create external project and update its graph, do not delay update since
// any file open operation needs all updated external projects
this.externalProjectToConfiguredProjectMap.delete(proj.projectFileName);
this.createExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition, excludedFiles);
const project = this.createExternalProject(proj.projectFileName, rootFiles, proj.options, proj.typeAcquisition, excludedFiles);
project.updateGraph();
}
}
+48 -20
View File
@@ -149,6 +149,8 @@ namespace ts.server {
*/
private projectStateVersion = 0;
protected isInitialLoadPending: () => boolean = returnFalse;
/*@internal*/
dirty = false;
@@ -1033,7 +1035,10 @@ namespace ts.server {
/* @internal */
getChangesSinceVersion(lastKnownVersion?: number): ProjectFilesWithTSDiagnostics {
updateProjectIfDirty(this);
// Update the graph only if initial configured project load is not pending
if (!this.isInitialLoadPending()) {
updateProjectIfDirty(this);
}
const info: protocol.ProjectVersionInfo = {
projectName: this.getProjectName(),
@@ -1091,9 +1096,8 @@ namespace ts.server {
this.rootFilesMap.delete(info.path);
}
protected enableGlobalPlugins() {
protected enableGlobalPlugins(options: CompilerOptions) {
const host = this.projectService.host;
const options = this.getCompilationSettings();
if (!host.require) {
this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
@@ -1244,7 +1248,7 @@ namespace ts.server {
if (!projectRootPath && !projectService.useSingleInferredProject) {
this.canonicalCurrentDirectory = projectService.toCanonicalFileName(this.currentDirectory);
}
this.enableGlobalPlugins();
this.enableGlobalPlugins(this.getCompilerOptions());
}
addRoot(info: ScriptInfo) {
@@ -1316,28 +1320,29 @@ namespace ts.server {
private projectErrors: Diagnostic[] | undefined;
private projectReferences: ReadonlyArray<ProjectReference> | undefined;
/*@internal*/
projectOptions?: ProjectOptions | true;
protected isInitialLoadPending: () => boolean = returnTrue;
/*@internal*/
constructor(configFileName: NormalizedPath,
projectService: ProjectService,
documentRegistry: DocumentRegistry,
hasExplicitListOfFiles: boolean,
compilerOptions: CompilerOptions,
lastFileExceededProgramSize: string | undefined,
public compileOnSaveEnabled: boolean,
cachedDirectoryStructureHost: CachedDirectoryStructureHost,
private projectReferences: ReadonlyArray<ProjectReference> | undefined) {
cachedDirectoryStructureHost: CachedDirectoryStructureHost) {
super(configFileName,
ProjectKind.Configured,
projectService,
documentRegistry,
hasExplicitListOfFiles,
lastFileExceededProgramSize,
compilerOptions,
compileOnSaveEnabled,
/*hasExplicitListOfFiles*/ false,
/*lastFileExceededProgramSize*/ undefined,
/*compilerOptions*/ {},
/*compileOnSaveEnabled*/ false,
cachedDirectoryStructureHost,
getDirectoryPath(configFileName));
this.canonicalConfigFilePath = asNormalizedPath(projectService.toCanonicalFileName(configFileName));
this.enablePlugins();
}
/**
@@ -1345,17 +1350,24 @@ namespace ts.server {
* @returns: true if set of files in the project stays the same and false - otherwise.
*/
updateGraph(): boolean {
this.isInitialLoadPending = returnFalse;
const reloadLevel = this.pendingReload;
this.pendingReload = ConfigFileProgramReloadLevel.None;
let result: boolean;
switch (reloadLevel) {
case ConfigFileProgramReloadLevel.Partial:
return this.projectService.reloadFileNamesOfConfiguredProject(this);
result = this.projectService.reloadFileNamesOfConfiguredProject(this);
break;
case ConfigFileProgramReloadLevel.Full:
this.projectService.reloadConfiguredProject(this);
return true;
result = true;
break;
default:
return super.updateGraph();
result = super.updateGraph();
}
this.projectService.sendProjectTelemetry(this);
this.projectService.sendSurveyReady(this);
return result;
}
/*@internal*/
@@ -1382,8 +1394,12 @@ namespace ts.server {
}
enablePlugins() {
this.enablePluginsWithOptions(this.getCompilerOptions());
}
/*@internal*/
enablePluginsWithOptions(options: CompilerOptions) {
const host = this.projectService.host;
const options = this.getCompilationSettings();
if (!host.require) {
this.projectService.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
@@ -1407,7 +1423,7 @@ namespace ts.server {
}
}
this.enableGlobalPlugins();
this.enableGlobalPlugins(options);
}
/**
@@ -1505,6 +1521,11 @@ namespace ts.server {
) || false;
}
/*@internal*/
hasExternalProjectRef() {
return !!this.externalProjectRefCount;
}
getEffectiveTypeRoots() {
return getEffectiveTypeRoots(this.getCompilationSettings(), this.directoryStructureHost) || [];
}
@@ -1547,6 +1568,13 @@ namespace ts.server {
getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName)));
}
updateGraph() {
const result = super.updateGraph();
this.projectService.sendProjectTelemetry(this);
this.projectService.sendSurveyReady(this);
return result;
}
getExcludedFiles() {
return this.excludedFiles;
}
+35 -1
View File
@@ -1839,7 +1839,7 @@ namespace ts.server.protocol {
* begin with prefix.
*/
export interface CompletionsRequest extends FileLocationRequest {
command: CommandTypes.Completions;
command: CommandTypes.Completions | CommandTypes.CompletionInfo;
arguments: CompletionsRequestArgs;
}
@@ -2436,6 +2436,39 @@ namespace ts.server.protocol {
openFiles: string[];
}
export type SurveyReadyEventName = "surveyReady";
export interface SurveyReadyEvent extends Event {
event: SurveyReadyEventName;
body: SurveyReadyEventBody;
}
export interface SurveyReadyEventBody {
/** Name of the survey. This is an internal machine- and programmer-friendly name */
surveyId: string;
}
export type LargeFileReferencedEventName = "largeFileReferenced";
export interface LargeFileReferencedEvent extends Event {
event: LargeFileReferencedEventName;
body: LargeFileReferencedEventBody;
}
export interface LargeFileReferencedEventBody {
/**
* name of the large file being loaded
*/
file: string;
/**
* size of the file
*/
fileSize: number;
/**
* max file size allowed on the server
*/
maxFileSize: number;
}
/**
* Arguments for reload request.
*/
@@ -2802,6 +2835,7 @@ namespace ts.server.protocol {
readonly includeCompletionsWithInsertText?: boolean;
readonly importModuleSpecifierPreference?: "relative" | "non-relative";
readonly allowTextChangesInNewFiles?: boolean;
readonly lazyConfiguredProjectsFromExternalProject?: boolean;
}
export interface CompilerOptions {
+22 -8
View File
@@ -39,7 +39,7 @@ namespace ts.server {
*/
private pendingReloadFromDisk: boolean;
constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath, initialVersion?: ScriptInfoVersion) {
constructor(private readonly host: ServerHost, private readonly fileName: NormalizedPath, initialVersion: ScriptInfoVersion | undefined, private readonly info: ScriptInfo) {
this.version = initialVersion || { svc: 0, text: 0 };
}
@@ -164,9 +164,20 @@ namespace ts.server {
private getFileText(tempFileName?: string) {
let text: string;
const getText = () => text === undefined ? (text = this.host.readFile(tempFileName || this.fileName) || "") : text;
const size = this.host.getFileSize ? this.host.getFileSize(tempFileName || this.fileName) : getText().length;
return size > maxFileSize ? "" : getText();
const fileName = tempFileName || this.fileName;
const getText = () => text === undefined ? (text = this.host.readFile(fileName) || "") : text;
// Only non typescript files have size limitation
if (!hasTypeScriptFileExtension(this.fileName)) {
const fileSize = this.host.getFileSize ? this.host.getFileSize(fileName) : getText().length;
if (fileSize > maxFileSize) {
Debug.assert(!!this.info.containingProjects.length);
const service = this.info.containingProjects[0].projectService;
service.logger.info(`Skipped loading contents of large file ${fileName} for info ${this.info.fileName}: fileSize: ${fileSize}`);
this.info.containingProjects[0].projectService.sendLargeFileReferencedEvent(fileName, fileSize);
return "";
}
}
return getText();
}
private switchToScriptVersionCache(): ScriptVersionCache {
@@ -223,7 +234,7 @@ namespace ts.server {
*/
readonly containingProjects: Project[] = [];
private formatSettings: FormatCodeSettings | undefined;
private preferences: UserPreferences | undefined;
private preferences: protocol.UserPreferences | undefined;
/* @internal */
fileWatcher: FileWatcher | undefined;
@@ -248,7 +259,7 @@ namespace ts.server {
initialVersion?: ScriptInfoVersion) {
this.isDynamic = isDynamicFileName(fileName);
this.textStorage = new TextStorage(host, fileName, initialVersion);
this.textStorage = new TextStorage(host, fileName, initialVersion, this);
if (hasMixedContent || this.isDynamic) {
this.textStorage.reload("");
this.realpath = this.path;
@@ -322,7 +333,7 @@ namespace ts.server {
}
getFormatCodeSettings(): FormatCodeSettings | undefined { return this.formatSettings; }
getPreferences(): UserPreferences | undefined { return this.preferences; }
getPreferences(): protocol.UserPreferences | undefined { return this.preferences; }
attachToProject(project: Project): boolean {
const isNew = !this.isAttached(project);
@@ -421,7 +432,7 @@ namespace ts.server {
}
}
setOptions(formatSettings: FormatCodeSettings, preferences: UserPreferences | undefined): void {
setOptions(formatSettings: FormatCodeSettings, preferences: protocol.UserPreferences | undefined): void {
if (formatSettings) {
if (!this.formatSettings) {
this.formatSettings = getDefaultFormatCodeSettings(this.host);
@@ -459,12 +470,15 @@ namespace ts.server {
if (this.isDynamicOrHasMixedContent()) {
this.textStorage.reload("");
this.markContainingProjectsAsDirty();
return true;
}
else {
if (this.textStorage.reloadWithFileText(tempFileName)) {
this.markContainingProjectsAsDirty();
return true;
}
}
return false;
}
/*@internal*/
+12 -9
View File
@@ -266,8 +266,6 @@ namespace ts.server {
getValue: (path: Path) => T,
projects: Projects,
action: (project: Project, value: T) => ReadonlyArray<U> | U | undefined,
comparer?: (a: U, b: U) => number,
areEqual?: (a: U, b: U) => boolean,
): U[] {
const outputs = flatMap(isArray(projects) ? projects : projects.projects, project => action(project, defaultValue));
if (!isArray(projects) && projects.symLinkedProjects) {
@@ -276,10 +274,7 @@ namespace ts.server {
outputs.push(...flatMap(projects, project => action(project, value)));
});
}
return comparer
? sortAndDeduplicate(outputs, comparer, areEqual)
: deduplicate(outputs, areEqual);
return deduplicate(outputs, equateValues);
}
function combineProjectOutputFromEveryProject<T>(projectService: ProjectService, action: (project: Project) => ReadonlyArray<T>, areEqual: (a: T, b: T) => boolean) {
@@ -559,6 +554,10 @@ namespace ts.server {
const { openFiles } = event.data;
this.projectsUpdatedInBackgroundEvent(openFiles);
break;
case LargeFileReferencedEvent:
const { file, fileSize, maxFileSize } = event.data;
this.event<protocol.LargeFileReferencedEventBody>({ file, fileSize, maxFileSize }, "largeFileReferenced");
break;
case ConfigFileDiagEvent:
const { triggerFile, configFileName: configFile, diagnostics } = event.data;
const bakedDiags = map(diagnostics, diagnostic => formatConfigFileDiag(diagnostic, /*includeFileName*/ true));
@@ -568,6 +567,10 @@ namespace ts.server {
diagnostics: bakedDiags
}, "configFileDiag");
break;
case SurveyReady:
const { surveyId } = event.data;
this.event<protocol.SurveyReadyEventBody>({ surveyId }, "surveyReady");
break;
case ProjectLanguageServiceStateEvent: {
const eventName: protocol.ProjectLanguageServiceStateEventName = "projectLanguageServiceState";
this.event<protocol.ProjectLanguageServiceStateEventBody>({
@@ -1419,7 +1422,7 @@ namespace ts.server {
const position = this.getPosition(args, scriptInfo);
const completions = project.getLanguageService().getCompletionsAtPosition(file, position, {
...this.getPreferences(file),
...convertUserPreferences(this.getPreferences(file)),
triggerCharacter: args.triggerCharacter,
includeExternalModuleExports: args.includeExternalModuleExports,
includeInsertTextCompletions: args.includeInsertTextCompletions
@@ -2348,7 +2351,7 @@ namespace ts.server {
return this.projectService.getFormatCodeOptions(file);
}
private getPreferences(file: NormalizedPath): UserPreferences {
private getPreferences(file: NormalizedPath): protocol.UserPreferences {
return this.projectService.getPreferences(file);
}
@@ -2356,7 +2359,7 @@ namespace ts.server {
return this.projectService.getHostFormatCodeOptions();
}
private getHostPreferences(): UserPreferences {
private getHostPreferences(): protocol.UserPreferences {
return this.projectService.getHostPreferences();
}
}
+1 -10
View File
@@ -120,6 +120,7 @@ namespace ts.server {
};
}
/*@internal*/
export interface ProjectOptions {
configHasExtendsProperty: boolean;
/**
@@ -128,16 +129,6 @@ namespace ts.server {
configHasFilesProperty: boolean;
configHasIncludeProperty: boolean;
configHasExcludeProperty: boolean;
projectReferences: ReadonlyArray<ProjectReference> | undefined;
/**
* these fields can be present in the project file
*/
files?: string[];
wildcardDirectories?: Map<WatchDirectoryFlags>;
compilerOptions?: CompilerOptions;
typeAcquisition?: TypeAcquisition;
compileOnSave?: boolean;
}
export function isInferredProjectName(name: string) {
@@ -0,0 +1,549 @@
/* @internal */
namespace ts.codefix {
const fixId = "convertToAsyncFunction";
const errorCodes = [Diagnostics.This_may_be_converted_to_an_async_function.code];
registerCodeFix({
errorCodes,
getCodeActions(context: CodeFixContext) {
const changes = textChanges.ChangeTracker.with(context, (t) => convertToAsyncFunction(t, context.sourceFile, context.span.start, context.program.getTypeChecker(), context));
return [createCodeFixAction(fixId, changes, Diagnostics.Convert_to_async_function, fixId, Diagnostics.Convert_all_to_async_functions)];
},
fixIds: [fixId],
getAllCodeActions: context => codeFixAll(context, errorCodes, (changes, err) => convertToAsyncFunction(changes, err.file, err.start, context.program.getTypeChecker(), context)),
});
/*
custom type to encapsulate information for variable declarations synthesized in the refactor
numberOfUsesOriginal - number of times the variable should be assigned in the refactor
numberOfUsesSynthesized - count of how many times the variable has been assigned so far
At the end of the refactor, numberOfUsesOriginal should === numberOfUsesSynthesized
*/
interface SynthIdentifier {
identifier: Identifier;
types: Type[];
numberOfAssignmentsOriginal: number;
}
interface SymbolAndIdentifier {
identifier: Identifier;
symbol: Symbol;
}
interface Transformer {
checker: TypeChecker;
synthNamesMap: Map<SynthIdentifier>; // keys are the symbol id of the identifier
allVarNames: SymbolAndIdentifier[];
setOfExpressionsToReturn: Map<true>; // keys are the node ids of the expressions
constIdentifiers: Identifier[];
originalTypeMap: Map<Type>; // keys are the node id of the identifier
isInJSFile: boolean;
}
function convertToAsyncFunction(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker, context: CodeFixContextBase): void {
// get the function declaration - returns a promise
const tokenAtPosition = getTokenAtPosition(sourceFile, position);
let functionToConvert: FunctionLikeDeclaration | undefined;
// if the parent of a FunctionLikeDeclaration is a variable declaration, the convertToAsync diagnostic will be reported on the variable name
if (isIdentifier(tokenAtPosition) && isVariableDeclaration(tokenAtPosition.parent) &&
tokenAtPosition.parent.initializer && isFunctionLikeDeclaration(tokenAtPosition.parent.initializer)) {
functionToConvert = tokenAtPosition.parent.initializer;
}
else {
functionToConvert = tryCast(getContainingFunction(getTokenAtPosition(sourceFile, position)), isFunctionLikeDeclaration);
}
if (!functionToConvert) {
return;
}
const synthNamesMap: Map<SynthIdentifier> = createMap();
const originalTypeMap: Map<Type> = createMap();
const allVarNames: SymbolAndIdentifier[] = [];
const isInJSFile = isInJavaScriptFile(functionToConvert);
const setOfExpressionsToReturn = getAllPromiseExpressionsToReturn(functionToConvert, checker);
const functionToConvertRenamed: FunctionLikeDeclaration = renameCollidingVarNames(functionToConvert, checker, synthNamesMap, context, setOfExpressionsToReturn, originalTypeMap, allVarNames);
const constIdentifiers = getConstIdentifiers(synthNamesMap);
const returnStatements = getReturnStatementsWithPromiseHandlers(functionToConvertRenamed);
const transformer = { checker, synthNamesMap, allVarNames, setOfExpressionsToReturn, constIdentifiers, originalTypeMap, isInJSFile };
if (!returnStatements.length) {
return;
}
// add the async keyword
changes.insertModifierBefore(sourceFile, SyntaxKind.AsyncKeyword, functionToConvert);
function startTransformation(node: CallExpression, nodeToReplace: Node) {
const newNodes = transformExpression(node, transformer, node);
changes.replaceNodeWithNodes(sourceFile, nodeToReplace, newNodes);
}
for (const statement of returnStatements) {
if (isCallExpression(statement)) {
startTransformation(statement, statement);
}
else {
forEachChild(statement, function visit(node: Node) {
if (isCallExpression(node)) {
startTransformation(node, statement);
}
else if (!isFunctionLike(node)) {
forEachChild(node, visit);
}
});
}
}
}
// Returns the identifiers that are never reassigned in the refactor
function getConstIdentifiers(synthNamesMap: Map<SynthIdentifier>): Identifier[] {
const constIdentifiers: Identifier[] = [];
synthNamesMap.forEach((val) => {
if (val.numberOfAssignmentsOriginal === 0) {
constIdentifiers.push(val.identifier);
}
});
return constIdentifiers;
}
/*
Finds all of the expressions of promise type that should not be saved in a variable during the refactor
*/
function getAllPromiseExpressionsToReturn(func: FunctionLikeDeclaration, checker: TypeChecker): Map<true> {
if (!func.body) {
return createMap<true>();
}
const setOfExpressionsToReturn: Map<true> = createMap<true>();
forEachChild(func.body, function visit(node: Node) {
if (isPromiseReturningExpression(node, checker, "then")) {
setOfExpressionsToReturn.set(getNodeId(node).toString(), true);
forEach((<CallExpression>node).arguments, visit);
}
else if (isPromiseReturningExpression(node, checker, "catch")) {
setOfExpressionsToReturn.set(getNodeId(node).toString(), true);
// if .catch() is the last call in the chain, move leftward in the chain until we hit something else that should be returned
forEachChild(node, visit);
}
else if (isPromiseReturningExpression(node, checker)) {
setOfExpressionsToReturn.set(getNodeId(node).toString(), true);
// don't recurse here, since we won't refactor any children or arguments of the expression
}
else {
forEachChild(node, visit);
}
});
return setOfExpressionsToReturn;
}
/*
Returns true if node is a promise returning expression
If name is not undefined, node is a promise returning call of name
*/
function isPromiseReturningExpression(node: Node, checker: TypeChecker, name?: string): boolean {
const isNodeExpression = name ? isCallExpression(node) : isExpression(node);
const isExpressionOfName = isNodeExpression && (!name || hasPropertyAccessExpressionWithName(node as CallExpression, name));
const nodeType = isExpressionOfName && checker.getTypeAtLocation(node);
return !!(nodeType && checker.getPromisedTypeOfPromise(nodeType));
}
function declaredInFile(symbol: Symbol, sourceFile: SourceFile): boolean {
return symbol.valueDeclaration && symbol.valueDeclaration.getSourceFile() === sourceFile;
}
/*
Renaming of identifiers may be neccesary as the refactor changes scopes -
This function collects all existing identifier names and names of identifiers that will be created in the refactor.
It then checks for any collisions and renames them through getSynthesizedDeepClone
*/
function renameCollidingVarNames(nodeToRename: FunctionLikeDeclaration, checker: TypeChecker, synthNamesMap: Map<SynthIdentifier>, context: CodeFixContextBase, setOfAllExpressionsToReturn: Map<true>, originalType: Map<Type>, allVarNames: SymbolAndIdentifier[]): FunctionLikeDeclaration {
const identsToRenameMap: Map<Identifier> = createMap(); // key is the symbol id
forEachChild(nodeToRename, function visit(node: Node) {
if (!isIdentifier(node)) {
forEachChild(node, visit);
return;
}
const symbol = checker.getSymbolAtLocation(node);
const isDefinedInFile = symbol && declaredInFile(symbol, context.sourceFile);
if (symbol && isDefinedInFile) {
const type = checker.getTypeAtLocation(node);
const lastCallSignature = getLastCallSignature(type, checker);
const symbolIdString = getSymbolId(symbol).toString();
// if the identifier refers to a function we want to add the new synthesized variable for the declaration (ex. blob in let blob = res(arg))
// Note - the choice of the last call signature is arbitrary
if (lastCallSignature && lastCallSignature.parameters.length && !synthNamesMap.has(symbolIdString)) {
const synthName = getNewNameIfConflict(createIdentifier(lastCallSignature.parameters[0].name), allVarNames);
synthNamesMap.set(symbolIdString, synthName);
allVarNames.push({ identifier: synthName.identifier, symbol });
}
// we only care about identifiers that are parameters and declarations (don't care about other uses)
else if (node.parent && (isParameter(node.parent) || isVariableDeclaration(node.parent))) {
// if the identifier name conflicts with a different identifier that we've already seen
if (allVarNames.some(ident => ident.identifier.text === node.text && ident.symbol !== symbol)) {
const newName = getNewNameIfConflict(node, allVarNames);
identsToRenameMap.set(symbolIdString, newName.identifier);
synthNamesMap.set(symbolIdString, newName);
allVarNames.push({ identifier: newName.identifier, symbol });
}
else {
const identifier = getSynthesizedDeepClone(node);
identsToRenameMap.set(symbolIdString, identifier);
synthNamesMap.set(symbolIdString, { identifier, types: [], numberOfAssignmentsOriginal: allVarNames.filter(elem => elem.identifier.text === node.text).length/*, numberOfAssignmentsSynthesized: 0*/ });
if ((isParameter(node.parent) && isExpressionOrCallOnTypePromise(node.parent.parent)) || isVariableDeclaration(node.parent)) {
allVarNames.push({ identifier, symbol });
}
}
}
}
});
return getSynthesizedDeepCloneWithRenames(nodeToRename, /*includeTrivia*/ true, identsToRenameMap, checker, deepCloneCallback);
function isExpressionOrCallOnTypePromise(child: Node): boolean {
const node = child.parent;
if (isCallExpression(node) || isIdentifier(node) && !setOfAllExpressionsToReturn.get(getNodeId(node).toString())) {
const nodeType = checker.getTypeAtLocation(node);
const isPromise = nodeType && checker.getPromisedTypeOfPromise(nodeType);
return !!isPromise;
}
return false;
}
function deepCloneCallback(node: Node, clone: Node) {
if (isIdentifier(node)) {
const symbol = checker.getSymbolAtLocation(node);
const symboldIdString = symbol && getSymbolId(symbol).toString();
const renameInfo = symbol && synthNamesMap.get(symboldIdString!);
if (renameInfo) {
const type = checker.getTypeAtLocation(node);
if (type) {
originalType.set(getNodeId(clone).toString(), type);
}
}
}
const val = setOfAllExpressionsToReturn.get(getNodeId(node).toString());
if (val !== undefined) {
setOfAllExpressionsToReturn.delete(getNodeId(node).toString());
setOfAllExpressionsToReturn.set(getNodeId(clone).toString(), val);
}
}
}
function getNewNameIfConflict(name: Identifier, allVarNames: SymbolAndIdentifier[]): SynthIdentifier {
const numVarsSameName = allVarNames.filter(elem => elem.identifier.text === name.text).length;
const numberOfAssignmentsOriginal = 0;
const identifier = numVarsSameName === 0 ? name : createIdentifier(name.text + "_" + numVarsSameName);
return { identifier, types: [], numberOfAssignmentsOriginal };
}
// dispatch function to recursively build the refactoring
function transformExpression(node: Expression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] {
if (!node) {
return [];
}
const originalType = isIdentifier(node) && transformer.originalTypeMap.get(getNodeId(node).toString());
const nodeType = originalType || transformer.checker.getTypeAtLocation(node);
if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "then") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType)) {
return transformThen(node, transformer, outermostParent, prevArgName);
}
else if (isCallExpression(node) && hasPropertyAccessExpressionWithName(node, "catch") && nodeType && !!transformer.checker.getPromisedTypeOfPromise(nodeType)) {
return transformCatch(node, transformer, prevArgName);
}
else if (isPropertyAccessExpression(node)) {
return transformExpression(node.expression, transformer, outermostParent, prevArgName);
}
else if (nodeType && transformer.checker.getPromisedTypeOfPromise(nodeType)) {
return transformPromiseCall(node, transformer, prevArgName);
}
return [];
}
function transformCatch(node: CallExpression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] {
const func = node.arguments[0];
const argName = getArgName(func, transformer);
const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(node).toString());
/*
If there is another call in the chain after the .catch() we are transforming, we will need to save the result of both paths (try block and catch block)
To do this, we will need to synthesize a variable that we were not aware of while we were adding identifiers to the synthNamesMap
We will use the prevArgName and then update the synthNamesMap with a new variable name for the next transformation step
*/
if (prevArgName && !shouldReturn) {
prevArgName.numberOfAssignmentsOriginal = 2; // Try block and catch block
transformer.synthNamesMap.forEach((val, key) => {
if (val.identifier.text === prevArgName.identifier.text) {
transformer.synthNamesMap.set(key, getNewNameIfConflict(prevArgName.identifier, transformer.allVarNames));
}
});
// update the constIdentifiers list
if (transformer.constIdentifiers.some(elem => elem.text === prevArgName.identifier.text)) {
transformer.constIdentifiers.push(getNewNameIfConflict(prevArgName.identifier, transformer.allVarNames).identifier);
}
}
const tryBlock = createBlock(transformExpression(node.expression, transformer, node, prevArgName));
const transformationBody = getTransformationBody(func, prevArgName, argName, node, transformer);
const catchArg = argName.identifier.text.length > 0 ? argName.identifier.text : "e";
const catchClause = createCatchClause(catchArg, createBlock(transformationBody));
/*
In order to avoid an implicit any, we will synthesize a type for the declaration using the unions of the types of both paths (try block and catch block)
*/
let varDeclList;
if (prevArgName && !shouldReturn) {
const typeArray: Type[] = prevArgName.types;
const unionType = transformer.checker.getUnionType(typeArray, UnionReduction.Subtype);
const unionTypeNode = transformer.isInJSFile ? undefined : transformer.checker.typeToTypeNode(unionType);
const varDecl = [createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), unionTypeNode)];
varDeclList = createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList(varDecl, NodeFlags.Let));
}
const tryStatement = createTry(tryBlock, catchClause, /*finallyBlock*/ undefined);
return varDeclList ? [varDeclList, tryStatement] : [tryStatement];
}
function transformThen(node: CallExpression, transformer: Transformer, outermostParent: CallExpression, prevArgName?: SynthIdentifier): Statement[] {
const [res, rej] = node.arguments;
if (!res) {
return transformExpression(node.expression, transformer, outermostParent);
}
const argNameRes = getArgName(res, transformer);
const transformationBody = getTransformationBody(res, prevArgName, argNameRes, node, transformer);
if (rej) {
const argNameRej = getArgName(rej, transformer);
const tryBlock = createBlock(transformExpression(node.expression, transformer, node, argNameRes).concat(transformationBody));
const transformationBody2 = getTransformationBody(rej, prevArgName, argNameRej, node, transformer);
const catchArg = argNameRej.identifier.text.length > 0 ? argNameRej.identifier.text : "e";
const catchClause = createCatchClause(catchArg, createBlock(transformationBody2));
return [createTry(tryBlock, catchClause, /* finallyBlock */ undefined) as Statement];
}
else {
return transformExpression(node.expression, transformer, node, argNameRes).concat(transformationBody);
}
return [];
}
function getFlagOfIdentifier(node: Identifier, constIdentifiers: Identifier[]): NodeFlags {
const inArr: boolean = constIdentifiers.some(elem => elem.text === node.text);
return inArr ? NodeFlags.Const : NodeFlags.Let;
}
function transformPromiseCall(node: Expression, transformer: Transformer, prevArgName?: SynthIdentifier): Statement[] {
const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(node).toString());
// the identifier is empty when the handler (.then()) ignores the argument - In this situation we do not need to save the result of the promise returning call
const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0;
const originalNodeParent = node.original ? node.original.parent : node.parent;
if (hasPrevArgName && !shouldReturn && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) {
return createVariableDeclarationOrAssignment(prevArgName!, createAwait(node), transformer).concat(); // hack to make the types match
}
else if (!hasPrevArgName && !shouldReturn && (!originalNodeParent || isPropertyAccessExpression(originalNodeParent))) {
return [createStatement(createAwait(node))];
}
return [createReturn(getSynthesizedDeepClone(node))];
}
function createVariableDeclarationOrAssignment(prevArgName: SynthIdentifier, rightHandSide: Expression, transformer: Transformer): NodeArray<Statement> {
if (prevArgName.types.length < prevArgName.numberOfAssignmentsOriginal) {
return createNodeArray([createStatement(createAssignment(getSynthesizedDeepClone(prevArgName.identifier), rightHandSide))]);
}
return createNodeArray([createVariableStatement(/*modifiers*/ undefined,
(createVariableDeclarationList([createVariableDeclaration(getSynthesizedDeepClone(prevArgName.identifier), /*type*/ undefined, rightHandSide)], getFlagOfIdentifier(prevArgName.identifier, transformer.constIdentifiers))))]);
}
function getTransformationBody(func: Node, prevArgName: SynthIdentifier | undefined, argName: SynthIdentifier, parent: CallExpression, transformer: Transformer): NodeArray<Statement> {
const hasPrevArgName = prevArgName && prevArgName.identifier.text.length > 0;
const hasArgName = argName && argName.identifier.text.length > 0;
const shouldReturn = transformer.setOfExpressionsToReturn.get(getNodeId(parent).toString());
switch (func.kind) {
case SyntaxKind.Identifier:
if (!hasArgName) break;
const synthCall = createCall(getSynthesizedDeepClone(func) as Identifier, /*typeArguments*/ undefined, [argName.identifier]);
if (shouldReturn) {
return createNodeArray([createReturn(synthCall)]);
}
if (!hasPrevArgName) break;
const type = transformer.originalTypeMap.get(getNodeId(func).toString());
const callSignatures = type && transformer.checker.getSignaturesOfType(type, SignatureKind.Call);
const returnType = callSignatures && callSignatures[0].getReturnType();
const varDeclOrAssignment = createVariableDeclarationOrAssignment(prevArgName!, createAwait(synthCall), transformer);
prevArgName!.types.push(returnType!);
return varDeclOrAssignment;
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
// Arrow functions with block bodies { } will enter this control flow
if (isFunctionLikeDeclaration(func) && func.body && isBlock(func.body) && func.body.statements) {
let refactoredStmts: Statement[] = [];
for (const statement of func.body.statements) {
if (getReturnStatementsWithPromiseHandlers(statement).length) {
refactoredStmts = refactoredStmts.concat(getInnerTransformationBody(transformer, [statement], prevArgName));
}
else {
refactoredStmts.push(statement);
}
}
return shouldReturn ? getSynthesizedDeepClones(createNodeArray(refactoredStmts)) :
removeReturns(createNodeArray(refactoredStmts), prevArgName!.identifier, transformer.constIdentifiers);
}
else {
const funcBody = (<ArrowFunction>func).body;
const innerRetStmts = getReturnStatementsWithPromiseHandlers(createReturn(funcBody as Expression));
const innerCbBody = getInnerTransformationBody(transformer, innerRetStmts, prevArgName);
if (innerCbBody.length > 0) {
return createNodeArray(innerCbBody);
}
if (hasPrevArgName && !shouldReturn) {
const type = transformer.checker.getTypeAtLocation(func);
const returnType = getLastCallSignature(type, transformer.checker).getReturnType();
const varDeclOrAssignment = createVariableDeclarationOrAssignment(prevArgName!, getSynthesizedDeepClone(funcBody) as Expression, transformer);
prevArgName!.types.push(returnType);
return varDeclOrAssignment;
}
else {
return createNodeArray([createReturn(getSynthesizedDeepClone(funcBody) as Expression)]);
}
}
break;
}
return createNodeArray([]);
}
function getLastCallSignature(type: Type, checker: TypeChecker): Signature {
const callSignatures = type && checker.getSignaturesOfType(type, SignatureKind.Call);
return callSignatures && callSignatures[callSignatures.length - 1];
}
function removeReturns(stmts: NodeArray<Statement>, prevArgName: Identifier, constIdentifiers: Identifier[]): NodeArray<Statement> {
const ret: Statement[] = [];
for (const stmt of stmts) {
if (isReturnStatement(stmt)) {
if (stmt.expression) {
ret.push(createVariableStatement(/*modifiers*/ undefined,
(createVariableDeclarationList([createVariableDeclaration(prevArgName, /*type*/ undefined, stmt.expression)], getFlagOfIdentifier(prevArgName, constIdentifiers)))));
}
}
else {
ret.push(getSynthesizedDeepClone(stmt));
}
}
return createNodeArray(ret);
}
function getInnerTransformationBody(transformer: Transformer, innerRetStmts: Node[], prevArgName?: SynthIdentifier) {
let innerCbBody: Statement[] = [];
for (const stmt of innerRetStmts) {
forEachChild(stmt, function visit(node: Node) {
if (isCallExpression(node)) {
const temp = transformExpression(node, transformer, node, prevArgName);
innerCbBody = innerCbBody.concat(temp);
if (innerCbBody.length > 0) {
return;
}
}
else if (!isFunctionLike(node)) {
forEachChild(node, visit);
}
});
}
return innerCbBody;
}
function hasPropertyAccessExpressionWithName(node: CallExpression, funcName: string): boolean {
if (!isPropertyAccessExpression(node.expression)) {
return false;
}
return node.expression.name.text === funcName;
}
function getArgName(funcNode: Node, transformer: Transformer): SynthIdentifier {
const numberOfAssignmentsOriginal = 0;
const types: Type[] = [];
let name: SynthIdentifier | undefined;
if (isFunctionLikeDeclaration(funcNode)) {
if (funcNode.parameters.length > 0) {
const param = funcNode.parameters[0].name as Identifier;
name = getMapEntryIfExists(param);
}
}
else if (isCallExpression(funcNode) && funcNode.arguments.length > 0 && isIdentifier(funcNode.arguments[0])) {
name = { identifier: funcNode.arguments[0] as Identifier, types, numberOfAssignmentsOriginal };
}
else if (isIdentifier(funcNode)) {
name = getMapEntryIfExists(funcNode);
}
if (!name || name.identifier === undefined || name.identifier.text === "_" || name.identifier.text === "undefined") {
return { identifier: createIdentifier(""), types, numberOfAssignmentsOriginal };
}
return name;
function getMapEntryIfExists(identifier: Identifier): SynthIdentifier {
const originalNode = getOriginalNode(identifier);
const symbol = getSymbol(originalNode);
if (!symbol) {
return { identifier, types, numberOfAssignmentsOriginal };
}
const mapEntry = transformer.synthNamesMap.get(getSymbolId(symbol).toString());
return mapEntry || { identifier, types, numberOfAssignmentsOriginal };
}
function getSymbol(node: Node): Symbol | undefined {
return node.symbol ? node.symbol : transformer.checker.getSymbolAtLocation(node);
}
function getOriginalNode(node: Node): Node {
return node.original ? node.original : node;
}
}
}
+1 -1
View File
@@ -208,7 +208,7 @@ namespace ts.codefix {
return replacement[1];
}
else {
changes.replaceRangeWithText(sourceFile, createTextRange(left.getStart(sourceFile), right.pos), "export default");
changes.replaceRangeWithText(sourceFile, createRange(left.getStart(sourceFile), right.pos), "export default");
return true;
}
}
+66 -67
View File
@@ -12,16 +12,16 @@ namespace ts.codefix {
const info = getInfo(context.sourceFile, context.span.start, context.program.getTypeChecker());
if (!info) return undefined;
if (info.kind === InfoKind.enum) {
if (info.kind === InfoKind.Enum) {
const { token, parentDeclaration } = info;
const changes = textChanges.ChangeTracker.with(context, t => addEnumMemberDeclaration(t, context.program.getTypeChecker(), token, parentDeclaration));
return [createCodeFixAction(fixName, changes, [Diagnostics.Add_missing_enum_member_0, token.text], fixId, Diagnostics.Add_all_missing_members)];
}
const { parentDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, parentDeclaration, token, call, makeStatic, inJs, context.preferences);
const addMember = inJs ?
singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, classDeclarationSourceFile, parentDeclaration, token.text, makeStatic)) :
getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, parentDeclaration, token, makeStatic);
const { parentDeclaration, declSourceFile, inJs, makeStatic, token, call } = info;
const methodCodeAction = call && getActionForMethodDeclaration(context, declSourceFile, parentDeclaration, token, call, makeStatic, inJs, context.preferences);
const addMember = inJs && !isInterfaceDeclaration(parentDeclaration) ?
singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, declSourceFile, parentDeclaration, token.text, makeStatic)) :
getActionsForAddMissingMemberInTypeScriptFile(context, declSourceFile, parentDeclaration, token, makeStatic);
return concatenate(singleElementArray(methodCodeAction), addMember);
},
fixIds: [fixId],
@@ -30,7 +30,7 @@ namespace ts.codefix {
const checker = program.getTypeChecker();
const seen = createMap<true>();
const classToMembers = new NodeMap<ClassLikeDeclaration, ClassInfo[]>();
const typeDeclToMembers = new NodeMap<ClassOrInterface, ClassOrInterfaceInfo[]>();
return createCombinedCodeActions(textChanges.ChangeTracker.with(context, changes => {
eachDiagnostic(context, errorCodes, diag => {
@@ -39,39 +39,39 @@ namespace ts.codefix {
return;
}
if (info.kind === InfoKind.enum) {
if (info.kind === InfoKind.Enum) {
const { token, parentDeclaration } = info;
addEnumMemberDeclaration(changes, checker, token, parentDeclaration);
}
else {
const { parentDeclaration, token } = info;
const infos = classToMembers.getOrUpdate(parentDeclaration, () => []);
const infos = typeDeclToMembers.getOrUpdate(parentDeclaration, () => []);
if (!infos.some(i => i.token.text === token.text)) infos.push(info);
}
});
classToMembers.forEach((infos, classDeclaration) => {
const superClasses = getAllSuperClasses(classDeclaration, checker);
typeDeclToMembers.forEach((infos, classDeclaration) => {
const supers = getAllSupers(classDeclaration, checker);
for (const info of infos) {
// If some superclass added this property, don't add it again.
if (superClasses.some(superClass => {
const superInfos = classToMembers.get(superClass);
if (supers.some(superClassOrInterface => {
const superInfos = typeDeclToMembers.get(superClassOrInterface);
return !!superInfos && superInfos.some(({ token }) => token.text === info.token.text);
})) continue;
const { parentDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
const { parentDeclaration, declSourceFile, inJs, makeStatic, token, call } = info;
// Always prefer to add a method declaration if possible.
if (call) {
addMethodDeclaration(context, changes, classDeclarationSourceFile, parentDeclaration, token, call, makeStatic, inJs, preferences);
addMethodDeclaration(context, changes, declSourceFile, parentDeclaration, token, call, makeStatic, inJs, preferences);
}
else {
if (inJs) {
addMissingMemberInJs(changes, classDeclarationSourceFile, parentDeclaration, token.text, makeStatic);
if (inJs && !isInterfaceDeclaration(parentDeclaration)) {
addMissingMemberInJs(changes, declSourceFile, parentDeclaration, token.text, makeStatic);
}
else {
const typeNode = getTypeNode(program.getTypeChecker(), parentDeclaration, token);
addPropertyDeclaration(changes, classDeclarationSourceFile, parentDeclaration, token.text, typeNode, makeStatic);
addPropertyDeclaration(changes, declSourceFile, parentDeclaration, token.text, typeNode, makeStatic);
}
}
}
@@ -80,37 +80,35 @@ namespace ts.codefix {
},
});
function getAllSuperClasses(cls: ClassLikeDeclaration | undefined, checker: TypeChecker): ReadonlyArray<ClassLikeDeclaration> {
function getAllSupers(decl: ClassOrInterface | undefined, checker: TypeChecker): ReadonlyArray<ClassOrInterface> {
const res: ClassLikeDeclaration[] = [];
while (cls) {
const superElement = getClassExtendsHeritageElement(cls);
while (decl) {
const superElement = getClassExtendsHeritageElement(decl);
const superSymbol = superElement && checker.getSymbolAtLocation(superElement.expression);
const superDecl = superSymbol && find(superSymbol.declarations, isClassLike);
if (superDecl) { res.push(superDecl); }
cls = superDecl;
decl = superDecl;
}
return res;
}
interface InfoBase {
readonly kind: InfoKind;
type ClassOrInterface = ClassLikeDeclaration | InterfaceDeclaration;
const enum InfoKind { Enum, ClassOrInterface }
interface EnumInfo {
readonly kind: InfoKind.Enum;
readonly token: Identifier;
readonly parentDeclaration: EnumDeclaration | ClassLikeDeclaration;
}
enum InfoKind { enum, class }
interface EnumInfo extends InfoBase {
readonly kind: InfoKind.enum;
readonly parentDeclaration: EnumDeclaration;
}
interface ClassInfo extends InfoBase {
readonly kind: InfoKind.class;
readonly parentDeclaration: ClassLikeDeclaration;
interface ClassOrInterfaceInfo {
readonly kind: InfoKind.ClassOrInterface;
readonly token: Identifier;
readonly parentDeclaration: ClassOrInterface;
readonly makeStatic: boolean;
readonly classDeclarationSourceFile: SourceFile;
readonly declSourceFile: SourceFile;
readonly inJs: boolean;
readonly call: CallExpression | undefined;
}
type Info = EnumInfo | ClassInfo;
type Info = EnumInfo | ClassOrInterfaceInfo;
function getInfo(tokenSourceFile: SourceFile, tokenPos: number, checker: TypeChecker): Info | undefined {
// The identifier of the missing property. eg:
@@ -128,35 +126,36 @@ namespace ts.codefix {
const { symbol } = leftExpressionType;
if (!symbol || !symbol.declarations) return undefined;
const classDeclaration = find(symbol.declarations, isClassLike);
if (classDeclaration) {
const makeStatic = (leftExpressionType as TypeReference).target !== checker.getDeclaredTypeOfSymbol(symbol);
const classDeclarationSourceFile = classDeclaration.getSourceFile();
const inJs = isSourceFileJavaScript(classDeclarationSourceFile);
// Prefer to change the class instead of the interface if they are merged
const classOrInterface = find(symbol.declarations, isClassLike) || find(symbol.declarations, isInterfaceDeclaration);
if (classOrInterface) {
const makeStatic = ((leftExpressionType as TypeReference).target || leftExpressionType) !== checker.getDeclaredTypeOfSymbol(symbol);
const declSourceFile = classOrInterface.getSourceFile();
const inJs = isSourceFileJavaScript(declSourceFile);
const call = tryCast(parent.parent, isCallExpression);
return { kind: InfoKind.class, token, parentDeclaration: classDeclaration, makeStatic, classDeclarationSourceFile, inJs, call };
return { kind: InfoKind.ClassOrInterface, token, parentDeclaration: classOrInterface, makeStatic, declSourceFile, inJs, call };
}
const enumDeclaration = find(symbol.declarations, isEnumDeclaration);
if (enumDeclaration) {
return { kind: InfoKind.enum, token, parentDeclaration: enumDeclaration };
return { kind: InfoKind.Enum, token, parentDeclaration: enumDeclaration };
}
return undefined;
}
function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined {
const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, classDeclarationSourceFile, classDeclaration, tokenName, makeStatic));
function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined {
const changes = textChanges.ChangeTracker.with(context, t => addMissingMemberInJs(t, declSourceFile, classDeclaration, tokenName, makeStatic));
return changes.length === 0 ? undefined
: createCodeFixAction(fixName, changes, [makeStatic ? Diagnostics.Initialize_static_property_0 : Diagnostics.Initialize_property_0_in_the_constructor, tokenName], fixId, Diagnostics.Add_all_missing_members);
}
function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): void {
function addMissingMemberInJs(changeTracker: textChanges.ChangeTracker, declSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): void {
if (makeStatic) {
if (classDeclaration.kind === SyntaxKind.ClassExpression) {
return;
}
const className = classDeclaration.name!.getText();
const staticInitialization = initializePropertyToUndefined(createIdentifier(className), tokenName);
changeTracker.insertNodeAfter(classDeclarationSourceFile, classDeclaration, staticInitialization);
changeTracker.insertNodeAfter(declSourceFile, classDeclaration, staticInitialization);
}
else {
const classConstructor = getFirstConstructorWithBody(classDeclaration);
@@ -164,7 +163,7 @@ namespace ts.codefix {
return;
}
const propertyInitialization = initializePropertyToUndefined(createThis(), tokenName);
changeTracker.insertNodeAtConstructorEnd(classDeclarationSourceFile, classConstructor, propertyInitialization);
changeTracker.insertNodeAtConstructorEnd(declSourceFile, classConstructor, propertyInitialization);
}
}
@@ -172,13 +171,13 @@ namespace ts.codefix {
return createStatement(createAssignment(createPropertyAccess(obj, propertyName), createIdentifier("undefined")));
}
function getActionsForAddMissingMemberInTypeScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, token: Identifier, makeStatic: boolean): CodeFixAction[] | undefined {
function getActionsForAddMissingMemberInTypeScriptFile(context: CodeFixContext, declSourceFile: SourceFile, classDeclaration: ClassOrInterface, token: Identifier, makeStatic: boolean): CodeFixAction[] | undefined {
const typeNode = getTypeNode(context.program.getTypeChecker(), classDeclaration, token);
const addProp = createAddPropertyDeclarationAction(context, classDeclarationSourceFile, classDeclaration, makeStatic, token.text, typeNode);
return makeStatic ? [addProp] : [addProp, createAddIndexSignatureAction(context, classDeclarationSourceFile, classDeclaration, token.text, typeNode)];
const addProp = createAddPropertyDeclarationAction(context, declSourceFile, classDeclaration, makeStatic, token.text, typeNode);
return makeStatic ? [addProp] : [addProp, createAddIndexSignatureAction(context, declSourceFile, classDeclaration, token.text, typeNode)];
}
function getTypeNode(checker: TypeChecker, classDeclaration: ClassLikeDeclaration, token: Node) {
function getTypeNode(checker: TypeChecker, classDeclaration: ClassOrInterface, token: Node) {
let typeNode: TypeNode | undefined;
if (token.parent.parent.kind === SyntaxKind.BinaryExpression) {
const binaryExpression = token.parent.parent as BinaryExpression;
@@ -189,12 +188,12 @@ namespace ts.codefix {
return typeNode || createKeywordTypeNode(SyntaxKind.AnyKeyword);
}
function createAddPropertyDeclarationAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFixAction {
const changes = textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, classDeclarationSourceFile, classDeclaration, tokenName, typeNode, makeStatic));
function createAddPropertyDeclarationAction(context: CodeFixContext, declSourceFile: SourceFile, classDeclaration: ClassOrInterface, makeStatic: boolean, tokenName: string, typeNode: TypeNode): CodeFixAction {
const changes = textChanges.ChangeTracker.with(context, t => addPropertyDeclaration(t, declSourceFile, classDeclaration, tokenName, typeNode, makeStatic));
return createCodeFixAction(fixName, changes, [makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0, tokenName], fixId, Diagnostics.Add_all_missing_members);
}
function addPropertyDeclaration(changeTracker: textChanges.ChangeTracker, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode, makeStatic: boolean): void {
function addPropertyDeclaration(changeTracker: textChanges.ChangeTracker, declSourceFile: SourceFile, classDeclaration: ClassOrInterface, tokenName: string, typeNode: TypeNode, makeStatic: boolean): void {
const property = createProperty(
/*decorators*/ undefined,
/*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
@@ -205,15 +204,15 @@ namespace ts.codefix {
const lastProp = getNodeToInsertPropertyAfter(classDeclaration);
if (lastProp) {
changeTracker.insertNodeAfter(classDeclarationSourceFile, lastProp, property);
changeTracker.insertNodeAfter(declSourceFile, lastProp, property);
}
else {
changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, property);
changeTracker.insertNodeAtClassStart(declSourceFile, classDeclaration, property);
}
}
// Gets the last of the first run of PropertyDeclarations, or undefined if the class does not start with a PropertyDeclaration.
function getNodeToInsertPropertyAfter(cls: ClassLikeDeclaration): PropertyDeclaration | undefined {
function getNodeToInsertPropertyAfter(cls: ClassOrInterface): PropertyDeclaration | undefined {
let res: PropertyDeclaration | undefined;
for (const member of cls.members) {
if (!isPropertyDeclaration(member)) break;
@@ -222,7 +221,7 @@ namespace ts.codefix {
return res;
}
function createAddIndexSignatureAction(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, typeNode: TypeNode): CodeFixAction {
function createAddIndexSignatureAction(context: CodeFixContext, declSourceFile: SourceFile, classDeclaration: ClassOrInterface, tokenName: string, typeNode: TypeNode): CodeFixAction {
// Index signatures cannot have the static modifier.
const stringTypeNode = createKeywordTypeNode(SyntaxKind.StringKeyword);
const indexingParameter = createParameter(
@@ -239,44 +238,44 @@ namespace ts.codefix {
[indexingParameter],
typeNode);
const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature));
const changes = textChanges.ChangeTracker.with(context, t => t.insertNodeAtClassStart(declSourceFile, classDeclaration, indexSignature));
// No fixId here because code-fix-all currently only works on adding individual named properties.
return createCodeFixActionNoFixId(fixName, changes, [Diagnostics.Add_index_signature_for_property_0, tokenName]);
}
function getActionForMethodDeclaration(
context: CodeFixContext,
classDeclarationSourceFile: SourceFile,
classDeclaration: ClassLikeDeclaration,
declSourceFile: SourceFile,
classDeclaration: ClassOrInterface,
token: Identifier,
callExpression: CallExpression,
makeStatic: boolean,
inJs: boolean,
preferences: UserPreferences,
): CodeFixAction | undefined {
const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(context, t, classDeclarationSourceFile, classDeclaration, token, callExpression, makeStatic, inJs, preferences));
const changes = textChanges.ChangeTracker.with(context, t => addMethodDeclaration(context, t, declSourceFile, classDeclaration, token, callExpression, makeStatic, inJs, preferences));
return createCodeFixAction(fixName, changes, [makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0, token.text], fixId, Diagnostics.Add_all_missing_members);
}
function addMethodDeclaration(
context: CodeFixContextBase,
changeTracker: textChanges.ChangeTracker,
classDeclarationSourceFile: SourceFile,
classDeclaration: ClassLikeDeclaration,
declSourceFile: SourceFile,
typeDecl: ClassOrInterface,
token: Identifier,
callExpression: CallExpression,
makeStatic: boolean,
inJs: boolean,
preferences: UserPreferences,
): void {
const methodDeclaration = createMethodFromCallExpression(context, callExpression, token.text, inJs, makeStatic, preferences);
const methodDeclaration = createMethodFromCallExpression(context, callExpression, token.text, inJs, makeStatic, preferences, !isInterfaceDeclaration(typeDecl));
const containingMethodDeclaration = getAncestor(callExpression, SyntaxKind.MethodDeclaration);
if (containingMethodDeclaration && containingMethodDeclaration.parent === classDeclaration) {
changeTracker.insertNodeAfter(classDeclarationSourceFile, containingMethodDeclaration, methodDeclaration);
if (containingMethodDeclaration && containingMethodDeclaration.parent === typeDecl) {
changeTracker.insertNodeAfter(declSourceFile, containingMethodDeclaration, methodDeclaration);
}
else {
changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration);
changeTracker.insertNodeAtClassStart(declSourceFile, typeDecl, methodDeclaration);
}
}
+11 -14
View File
@@ -112,26 +112,23 @@ namespace ts.codefix {
export function createMethodFromCallExpression(
context: CodeFixContextBase,
{ typeArguments, arguments: args, parent: parent }: CallExpression,
call: CallExpression,
methodName: string,
inJs: boolean,
makeStatic: boolean,
preferences: UserPreferences,
body: boolean,
): MethodDeclaration {
const { typeArguments, arguments: args, parent } = call;
const checker = context.program.getTypeChecker();
const types = map(args,
arg => {
let type = checker.getTypeAtLocation(arg);
if (type === undefined) {
return undefined;
}
// Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {"
type = checker.getBaseTypeOfLiteralType(type);
return checker.typeToTypeNode(type);
});
const types = map(args, arg =>
// Widen the type so we don't emit nonsense annotations like "function fn(x: 3) {"
checker.typeToTypeNode(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(arg))));
const names = map(args, arg =>
isIdentifier(arg) ? arg.text :
isPropertyAccessExpression(arg) ? arg.name.text : undefined);
isPropertyAccessExpression(arg) ? arg.name.text : undefined);
const contextualType = checker.getContextualType(call);
const returnType = inJs ? undefined : contextualType && checker.typeToTypeNode(contextualType, call) || createKeywordTypeNode(SyntaxKind.AnyKeyword);
return createMethod(
/*decorators*/ undefined,
/*modifiers*/ makeStatic ? [createToken(SyntaxKind.StaticKeyword)] : undefined,
@@ -141,8 +138,8 @@ namespace ts.codefix {
/*typeParameters*/ inJs ? undefined : map(typeArguments, (_, i) =>
createTypeParameterDeclaration(CharacterCodes.T + typeArguments!.length - 1 <= CharacterCodes.Z ? String.fromCharCode(CharacterCodes.T + i) : `T${i}`)),
/*parameters*/ createDummyParameters(args.length, names, types, /*minArgumentCount*/ undefined, inJs),
/*type*/ inJs ? undefined : createKeywordTypeNode(SyntaxKind.AnyKeyword),
createStubbedMethodBody(preferences));
/*type*/ returnType,
body ? createStubbedMethodBody(preferences) : undefined);
}
function createDummyParameters(argCount: number, names: (string | undefined)[] | undefined, types: (TypeNode | undefined)[] | undefined, minArgumentCount: number | undefined, inJs: boolean): ParameterDeclaration[] {
+43 -25
View File
@@ -163,14 +163,19 @@ namespace ts.codefix {
position: number,
preferences: UserPreferences,
): { readonly moduleSpecifier: string, readonly codeAction: CodeAction } {
const exportInfos = getAllReExportingModules(exportedSymbol, moduleSymbol, symbolName, sourceFile, program.getTypeChecker(), program.getSourceFiles());
const exportInfos = getAllReExportingModules(exportedSymbol, moduleSymbol, symbolName, sourceFile, program.getCompilerOptions(), program.getTypeChecker(), program.getSourceFiles());
Debug.assert(exportInfos.some(info => info.moduleSymbol === moduleSymbol));
// We sort the best codefixes first, so taking `first` is best for completions.
const moduleSpecifier = first(getNewImportInfos(program, sourceFile, position, exportInfos, host, preferences)).moduleSpecifier;
const fix = first(getFixForImport(exportInfos, symbolName, position, program, sourceFile, host, preferences));
return { moduleSpecifier, codeAction: codeActionForFix({ host, formatContext }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences)) };
return { moduleSpecifier, codeAction: codeFixActionToCodeAction(codeActionForFix({ host, formatContext }, sourceFile, symbolName, fix, getQuotePreference(sourceFile, preferences))) };
}
function getAllReExportingModules(exportedSymbol: Symbol, exportingModuleSymbol: Symbol, symbolName: string, sourceFile: SourceFile, checker: TypeChecker, allSourceFiles: ReadonlyArray<SourceFile>): ReadonlyArray<SymbolExportInfo> {
function codeFixActionToCodeAction({ description, changes, commands }: CodeFixAction): CodeAction {
return { description, changes, commands };
}
function getAllReExportingModules(exportedSymbol: Symbol, exportingModuleSymbol: Symbol, symbolName: string, sourceFile: SourceFile, compilerOptions: CompilerOptions, checker: TypeChecker, allSourceFiles: ReadonlyArray<SourceFile>): ReadonlyArray<SymbolExportInfo> {
const result: SymbolExportInfo[] = [];
forEachExternalModule(checker, allSourceFiles, (moduleSymbol, moduleFile) => {
// Don't import from a re-export when looking "up" like to `./index` or `../index`.
@@ -178,10 +183,14 @@ namespace ts.codefix {
return;
}
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, compilerOptions);
if (defaultInfo && defaultInfo.name === symbolName && skipAlias(defaultInfo.symbol, checker) === exportedSymbol) {
result.push({ moduleSymbol, importKind: defaultInfo.kind, exportedSymbolIsTypeOnly: isTypeOnlySymbol(defaultInfo.symbol) });
}
for (const exported of checker.getExportsOfModule(moduleSymbol)) {
if ((exported.escapedName === InternalSymbolName.Default || exported.name === symbolName) && skipAlias(exported, checker) === exportedSymbol) {
const isDefaultExport = checker.tryGetMemberInModuleExports(InternalSymbolName.Default, moduleSymbol) === exported;
result.push({ moduleSymbol, importKind: isDefaultExport ? ImportKind.Default : ImportKind.Named, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exported) });
if (exported.name === symbolName && skipAlias(exported, checker) === exportedSymbol) {
result.push({ moduleSymbol, importKind: ImportKind.Named, exportedSymbolIsTypeOnly: isTypeOnlySymbol(exported) });
}
}
});
@@ -274,14 +283,13 @@ namespace ts.codefix {
preferences: UserPreferences,
): ReadonlyArray<FixAddNewImport | FixUseImportType> {
const isJs = isSourceFileJavaScript(sourceFile);
const choicesForEachExportingModule = flatMap<SymbolExportInfo, ReadonlyArray<FixAddNewImport | FixUseImportType>>(moduleSymbols, ({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }) => {
const modulePathsGroups = moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences, program.redirectTargetsMap);
return modulePathsGroups.map(group => group.map((moduleSpecifier): FixAddNewImport | FixUseImportType =>
const choicesForEachExportingModule = flatMap(moduleSymbols, ({ moduleSymbol, importKind, exportedSymbolIsTypeOnly }) =>
moduleSpecifiers.getModuleSpecifiers(moduleSymbol, program.getCompilerOptions(), sourceFile, host, program.getSourceFiles(), preferences, program.redirectTargetsMap)
.map((moduleSpecifier): FixAddNewImport | FixUseImportType =>
// `position` should only be undefined at a missing jsx namespace, in which case we shouldn't be looking for pure types.
exportedSymbolIsTypeOnly && isJs ? { kind: ImportFixKind.ImportType, moduleSpecifier, position: Debug.assertDefined(position) } : { kind: ImportFixKind.AddNew, moduleSpecifier, importKind }));
});
// Sort to keep the shortest paths first, but keep [relativePath, importRelativeToBaseUrl] groups together
return flatten<FixAddNewImport | FixUseImportType>(choicesForEachExportingModule.sort((a, b) => first(a).moduleSpecifier.length - first(b).moduleSpecifier.length));
// Sort to keep the shortest paths first
return choicesForEachExportingModule.sort((a, b) => a.moduleSpecifier.length - b.moduleSpecifier.length);
}
function getFixesForAddImport(
@@ -395,13 +403,9 @@ namespace ts.codefix {
forEachExternalModuleToImportFrom(checker, sourceFile, program.getSourceFiles(), moduleSymbol => {
cancellationToken.throwIfCancellationRequested();
// check the default export
const defaultExport = checker.tryGetMemberInModuleExports(InternalSymbolName.Default, moduleSymbol);
if (defaultExport) {
const info = getDefaultExportInfo(defaultExport, moduleSymbol, program);
if (info && info.name === symbolName && symbolHasMeaning(info.symbolForMeaning, currentTokenMeaning)) {
addSymbol(moduleSymbol, defaultExport, ImportKind.Default);
}
const defaultInfo = getDefaultLikeExportInfo(moduleSymbol, checker, program.getCompilerOptions());
if (defaultInfo && defaultInfo.name === symbolName && symbolHasMeaning(defaultInfo.symbolForMeaning, currentTokenMeaning)) {
addSymbol(moduleSymbol, defaultInfo.symbol, defaultInfo.kind);
}
// check exports with the same name
@@ -413,9 +417,24 @@ namespace ts.codefix {
return originalSymbolToExportInfos;
}
function getDefaultExportInfo(defaultExport: Symbol, moduleSymbol: Symbol, program: Program): { readonly symbolForMeaning: Symbol, readonly name: string } | undefined {
const checker = program.getTypeChecker();
function getDefaultLikeExportInfo(
moduleSymbol: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions,
): { readonly symbol: Symbol, readonly symbolForMeaning: Symbol, readonly name: string, readonly kind: ImportKind.Default | ImportKind.Equals } | undefined {
const exported = getDefaultLikeExportWorker(moduleSymbol, checker);
if (!exported) return undefined;
const { symbol, kind } = exported;
const info = getDefaultExportInfoWorker(symbol, moduleSymbol, checker, compilerOptions);
return info && { symbol, symbolForMeaning: info.symbolForMeaning, name: info.name, kind };
}
function getDefaultLikeExportWorker(moduleSymbol: Symbol, checker: TypeChecker): { readonly symbol: Symbol, readonly kind: ImportKind.Default | ImportKind.Equals } | undefined {
const defaultExport = checker.tryGetMemberInModuleExports(InternalSymbolName.Default, moduleSymbol);
if (defaultExport) return { symbol: defaultExport, kind: ImportKind.Default };
const exportEquals = checker.resolveExternalModuleSymbol(moduleSymbol);
return exportEquals === moduleSymbol ? undefined : { symbol: exportEquals, kind: ImportKind.Equals };
}
function getDefaultExportInfoWorker(defaultExport: Symbol, moduleSymbol: Symbol, checker: TypeChecker, compilerOptions: CompilerOptions): { readonly symbolForMeaning: Symbol, readonly name: string } | undefined {
const localSymbol = getLocalSymbolForExportDefault(defaultExport);
if (localSymbol) return { symbolForMeaning: localSymbol, name: localSymbol.name };
@@ -423,12 +442,11 @@ namespace ts.codefix {
if (name !== undefined) return { symbolForMeaning: defaultExport, name };
if (defaultExport.flags & SymbolFlags.Alias) {
const aliased = checker.getAliasedSymbol(defaultExport);
return getDefaultExportInfo(aliased, Debug.assertDefined(aliased.parent), program);
const aliased = checker.getImmediateAliasedSymbol(defaultExport);
return aliased && getDefaultExportInfoWorker(aliased, Debug.assertDefined(aliased.parent), checker, compilerOptions);
}
else {
const moduleName = moduleSymbolToValidIdentifier(moduleSymbol, program.getCompilerOptions().target!);
return moduleName === undefined ? undefined : { symbolForMeaning: defaultExport, name: moduleName };
return { symbolForMeaning: defaultExport, name: moduleSymbolToValidIdentifier(moduleSymbol, compilerOptions.target!) };
}
}
+11 -9
View File
@@ -534,17 +534,19 @@ namespace ts.codefix {
else if (usageContext.properties && hasCallContext(usageContext.properties.get("push" as __String))) {
return checker.createArrayType(getParameterTypeFromCallContexts(0, usageContext.properties.get("push" as __String)!.callContexts!, /*isRestParameter*/ false, checker)!);
}
else if (usageContext.properties || usageContext.callContexts || usageContext.constructContexts || usageContext.numberIndexContext || usageContext.stringIndexContext) {
else if (usageContext.numberIndexContext) {
return checker.createArrayType(recur(usageContext.numberIndexContext));
}
else if (usageContext.properties || usageContext.callContexts || usageContext.constructContexts || usageContext.stringIndexContext) {
const members = createUnderscoreEscapedMap<Symbol>();
const callSignatures: Signature[] = [];
const constructSignatures: Signature[] = [];
let stringIndexInfo: IndexInfo | undefined;
let numberIndexInfo: IndexInfo | undefined;
if (usageContext.properties) {
usageContext.properties.forEach((context, name) => {
const symbol = checker.createSymbol(SymbolFlags.Property, name);
symbol.type = getTypeFromUsageContext(context, checker) || checker.getAnyType();
symbol.type = recur(context);
members.set(name, symbol);
});
}
@@ -561,19 +563,19 @@ namespace ts.codefix {
}
}
if (usageContext.numberIndexContext) {
numberIndexInfo = checker.createIndexInfo(getTypeFromUsageContext(usageContext.numberIndexContext, checker) || checker.getAnyType(), /*isReadonly*/ false);
}
if (usageContext.stringIndexContext) {
stringIndexInfo = checker.createIndexInfo(getTypeFromUsageContext(usageContext.stringIndexContext, checker) || checker.getAnyType(), /*isReadonly*/ false);
stringIndexInfo = checker.createIndexInfo(recur(usageContext.stringIndexContext), /*isReadonly*/ false);
}
return checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, numberIndexInfo); // TODO: GH#18217
return checker.createAnonymousType(/*symbol*/ undefined!, members, callSignatures, constructSignatures, stringIndexInfo, /*numberIndexInfo*/ undefined); // TODO: GH#18217
}
else {
return undefined;
}
function recur(innerContext: UsageContext): Type {
return getTypeFromUsageContext(innerContext, checker) || checker.getAnyType();
}
}
function getParameterTypeFromCallContexts(parameterIndex: number, callContexts: CallContext[], isRestParameter: boolean, checker: TypeChecker) {
+36 -24
View File
@@ -23,7 +23,8 @@ namespace ts.Completions {
type SymbolOriginInfoMap = (SymbolOriginInfo | undefined)[];
const enum KeywordCompletionFilters {
None,
None, // No keywords
All, // Every possible keyword (TODO: This is never appropriate)
ClassElementKeywords, // Keywords inside class body
InterfaceElementKeywords, // Keywords inside interface body
ConstructorParameterKeywords, // Keywords at constructor parameter
@@ -143,21 +144,13 @@ namespace ts.Completions {
getCompletionEntriesFromSymbols(symbols, entries, location, sourceFile, typeChecker, compilerOptions.target!, log, completionKind, preferences, propertyAccessToConvert, isJsxInitializer, recommendedCompletion, symbolToOriginInfoMap);
}
// TODO add filter for keyword based on type/value/namespace and also location
// Add all keywords if
// - this is not a member completion list (all the keywords)
// - other filters are enabled in required scenario so add those keywords
const isMemberCompletion = isMemberCompletionKind(completionKind);
if (keywordFilters !== KeywordCompletionFilters.None || !isMemberCompletion) {
addRange(entries, getKeywordCompletions(keywordFilters));
}
addRange(entries, getKeywordCompletions(keywordFilters));
for (const literal of literals) {
entries.push(createCompletionEntryForLiteral(literal));
}
return { isGlobalCompletion: isInSnippetScope, isMemberCompletion, isNewIdentifierLocation, entries };
return { isGlobalCompletion: isInSnippetScope, isMemberCompletion: isMemberCompletionKind(completionKind), isNewIdentifierLocation, entries };
}
function isUncheckedFile(sourceFile: SourceFile, compilerOptions: CompilerOptions): boolean {
@@ -271,6 +264,9 @@ namespace ts.Completions {
}
function quote(text: string, preferences: UserPreferences): string {
if (/^\d+$/.test(text)) {
return text;
}
const quoted = JSON.stringify(text);
switch (preferences.quotePreference) {
case undefined:
@@ -402,6 +398,8 @@ namespace ts.Completions {
return stringLiteralCompletionsFromProperties(typeChecker.getTypeFromTypeNode((node.parent.parent as IndexedAccessTypeNode).objectType));
case SyntaxKind.ImportType:
return { kind: StringLiteralCompletionKind.Paths, paths: PathCompletions.getStringLiteralCompletionsFromModuleNames(sourceFile, node, compilerOptions, host, typeChecker) };
case SyntaxKind.UnionType:
return isTypeReferenceNode(node.parent.parent.parent) ? { kind: StringLiteralCompletionKind.Types, types: getStringLiteralTypes(typeChecker.getTypeArgumentConstraint(node.parent.parent as UnionTypeNode)), isNewIdentifier: false } : undefined;
default:
return undefined;
}
@@ -1012,6 +1010,7 @@ namespace ts.Completions {
tryGetGlobalSymbols();
symbols = tagSymbols.concat(symbols);
completionKind = CompletionKind.MemberLike;
keywordFilters = KeywordCompletionFilters.None;
}
else if (isStartingCloseTag) {
const tagName = (<JsxElement>contextToken.parent.parent).openingElement.tagName;
@@ -1020,6 +1019,7 @@ namespace ts.Completions {
symbols = [tagSymbol];
}
completionKind = CompletionKind.MemberLike;
keywordFilters = KeywordCompletionFilters.None;
}
else {
// For JavaScript or TypeScript, if we're not after a dot, then just try to get the
@@ -1189,9 +1189,7 @@ namespace ts.Completions {
}
function getGlobalCompletions(): void {
if (tryGetFunctionLikeBodyCompletionContainer(contextToken)) {
keywordFilters = KeywordCompletionFilters.FunctionLikeBodyKeywords;
}
keywordFilters = tryGetFunctionLikeBodyCompletionContainer(contextToken) ? KeywordCompletionFilters.FunctionLikeBodyKeywords : KeywordCompletionFilters.All;
// Get all entities in the current scope.
completionKind = CompletionKind.Global;
@@ -1376,6 +1374,14 @@ namespace ts.Completions {
return;
}
if (resolvedModuleSymbol !== moduleSymbol &&
// Don't add another completion for `export =` of a symbol that's already global.
// So in `declare namespace foo {} declare module "foo" { export = foo; }`, there will just be the global completion for `foo`.
resolvedModuleSymbol.declarations.some(d => !!d.getSourceFile().externalModuleIndicator)) {
symbols.push(resolvedModuleSymbol);
symbolToOriginInfoMap[getSymbolId(resolvedModuleSymbol)] = { kind: SymbolOriginInfoKind.Export, moduleSymbol, isDefaultExport: false };
}
for (let symbol of typeChecker.getExportsOfModule(moduleSymbol)) {
// Don't add a completion for a re-export, only for the original.
// The actual import fix might end up coming from a re-export -- we don't compute that until getting completion details.
@@ -1638,7 +1644,8 @@ namespace ts.Completions {
completionKind = CompletionKind.MemberLike;
// Declaring new property/method/accessor
isNewIdentifierLocation = true;
keywordFilters = isClassLike(decl) ? KeywordCompletionFilters.ClassElementKeywords : KeywordCompletionFilters.InterfaceElementKeywords;
keywordFilters = contextToken.kind === SyntaxKind.AsteriskToken ? KeywordCompletionFilters.None :
isClassLike(decl) ? KeywordCompletionFilters.ClassElementKeywords : KeywordCompletionFilters.InterfaceElementKeywords;
// If you're in an interface you don't want to repeat things from super-interface. So just stop here.
if (!isClassLike(decl)) return GlobalsSearch.Success;
@@ -1676,14 +1683,16 @@ namespace ts.Completions {
*/
function tryGetObjectLikeCompletionContainer(contextToken: Node): ObjectLiteralExpression | ObjectBindingPattern | undefined {
if (contextToken) {
const { parent } = contextToken;
switch (contextToken.kind) {
case SyntaxKind.OpenBraceToken: // const x = { |
case SyntaxKind.CommaToken: // const x = { a: 0, |
const parent = contextToken.parent;
if (isObjectLiteralExpression(parent) || isObjectBindingPattern(parent)) {
return parent;
}
break;
case SyntaxKind.AsteriskToken:
return isMethodDeclaration(parent) ? tryCast(parent.parent, isObjectLiteralExpression) : undefined;
}
}
@@ -1860,10 +1869,8 @@ namespace ts.Completions {
case SyntaxKind.GetKeyword:
case SyntaxKind.SetKeyword:
if (isFromObjectTypeDeclaration(contextToken)) {
return false;
}
// falls through
return !isFromObjectTypeDeclaration(contextToken);
case SyntaxKind.ClassKeyword:
case SyntaxKind.EnumKeyword:
case SyntaxKind.InterfaceKeyword:
@@ -1875,6 +1882,9 @@ namespace ts.Completions {
case SyntaxKind.YieldKeyword:
case SyntaxKind.TypeKeyword: // type htm|
return true;
case SyntaxKind.AsteriskToken:
return isFunctionLike(contextToken.parent) && !isMethodDeclaration(contextToken.parent);
}
// If the previous token is keyword correspoding to class member completion keyword
@@ -2114,8 +2124,10 @@ namespace ts.Completions {
const kind = stringToToken(entry.name)!;
switch (keywordFilter) {
case KeywordCompletionFilters.None:
// "undefined" is a global variable, so don't need a keyword completion for it.
return kind !== SyntaxKind.UndefinedKeyword;
return false;
case KeywordCompletionFilters.All:
return kind === SyntaxKind.AsyncKeyword || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind) || kind === SyntaxKind.DeclareKeyword || kind === SyntaxKind.ModuleKeyword
|| isTypeKeyword(kind) && kind !== SyntaxKind.UndefinedKeyword;
case KeywordCompletionFilters.ClassElementKeywords:
return isClassMemberCompletionKeyword(kind);
case KeywordCompletionFilters.InterfaceElementKeywords:
@@ -2150,7 +2162,7 @@ namespace ts.Completions {
}
function isFunctionLikeBodyKeyword(kind: SyntaxKind) {
return kind === SyntaxKind.AsyncKeyword || !isClassMemberCompletionKeyword(kind);
return kind === SyntaxKind.AsyncKeyword || !isContextualKeyword(kind) && !isClassMemberCompletionKeyword(kind);
}
function keywordForNode(node: Node): SyntaxKind {
@@ -2226,7 +2238,7 @@ namespace ts.Completions {
default:
if (!isFromObjectTypeDeclaration(contextToken)) return undefined;
const isValidKeyword = isClassLike(contextToken.parent.parent) ? isClassMemberCompletionKeyword : isInterfaceOrTypeLiteralCompletionKeyword;
return (isValidKeyword(contextToken.kind) || isIdentifier(contextToken) && isValidKeyword(stringToToken(contextToken.text)!)) // TODO: GH#18217
return (isValidKeyword(contextToken.kind) || contextToken.kind === SyntaxKind.AsteriskToken || isIdentifier(contextToken) && isValidKeyword(stringToToken(contextToken.text)!)) // TODO: GH#18217
? contextToken.parent.parent as ObjectTypeDeclaration : undefined;
}
}
+2 -2
View File
@@ -184,7 +184,7 @@ namespace ts {
const bucket = getBucketForCompilationSettings(key, /*createIfMissing*/ true);
let entry = bucket.get(path);
const scriptTarget = scriptKind === ScriptKind.JSON ? ScriptTarget.JSON : compilationSettings.target;
const scriptTarget = scriptKind === ScriptKind.JSON ? ScriptTarget.JSON : compilationSettings.target || ScriptTarget.ES5;
if (!entry && externalCache) {
const sourceFile = externalCache.getDocument(key, path);
if (sourceFile) {
@@ -199,7 +199,7 @@ namespace ts {
if (!entry) {
// Have never seen this file with these settings. Create a new source file for it.
const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, scriptTarget!, version, /*setNodeParents*/ false, scriptKind); // TODO: GH#18217
const sourceFile = createLanguageServiceSourceFile(fileName, scriptSnapshot, scriptTarget, version, /*setNodeParents*/ false, scriptKind);
if (externalCache) {
externalCache.setDocument(key, path, sourceFile);
}
+5 -6
View File
@@ -6,7 +6,7 @@ namespace ts {
newFileOrDirPath: string,
host: LanguageServiceHost,
formatContext: formatting.FormatContext,
preferences: UserPreferences,
_preferences: UserPreferences,
sourceMapper: SourceMapper,
): ReadonlyArray<FileTextChanges> {
const useCaseSensitiveFileNames = hostUsesCaseSensitiveFileNames(host);
@@ -15,7 +15,7 @@ namespace ts {
const newToOld = getPathUpdater(newFileOrDirPath, oldFileOrDirPath, getCanonicalFileName, sourceMapper);
return textChanges.ChangeTracker.with({ host, formatContext }, changeTracker => {
updateTsconfigFiles(program, changeTracker, oldToNew, newFileOrDirPath, host.getCurrentDirectory(), useCaseSensitiveFileNames);
updateImports(program, changeTracker, oldToNew, newToOld, host, getCanonicalFileName, preferences);
updateImports(program, changeTracker, oldToNew, newToOld, host, getCanonicalFileName);
});
}
@@ -122,7 +122,6 @@ namespace ts {
newToOld: PathUpdater,
host: LanguageServiceHost,
getCanonicalFileName: GetCanonicalFileName,
preferences: UserPreferences,
): void {
const allFiles = program.getSourceFiles();
for (const sourceFile of allFiles) {
@@ -156,7 +155,7 @@ namespace ts {
// Need an update if the imported file moved, or the importing file moved and was using a relative path.
return toImport !== undefined && (toImport.updated || (importingSourceFileMoved && pathIsRelative(importLiteral.text)))
? moduleSpecifiers.getModuleSpecifier(program.getCompilerOptions(), sourceFile, newImportFromPath, toImport.newFileName, host, allFiles, preferences, program.redirectTargetsMap)
? moduleSpecifiers.updateModuleSpecifier(program.getCompilerOptions(), newImportFromPath, toImport.newFileName, host, allFiles, program.redirectTargetsMap, importLiteral.text)
: undefined;
});
}
@@ -210,7 +209,7 @@ namespace ts {
}
function updateImportsWorker(sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker, updateRef: (refText: string) => string | undefined, updateImport: (importLiteral: StringLiteralLike) => string | undefined) {
for (const ref of sourceFile.referencedFiles) {
for (const ref of sourceFile.referencedFiles || emptyArray) { // TODO: GH#26162
const updated = updateRef(ref.fileName);
if (updated !== undefined && updated !== sourceFile.text.slice(ref.pos, ref.end)) changeTracker.replaceRangeWithText(sourceFile, ref, updated);
}
@@ -222,7 +221,7 @@ namespace ts {
}
function createStringRange(node: StringLiteralLike, sourceFile: SourceFileLike): TextRange {
return createTextRange(node.getStart(sourceFile) + 1, node.end - 1);
return createRange(node.getStart(sourceFile) + 1, node.end - 1);
}
function forEachProperty(objectLiteral: Expression, cb: (property: PropertyAssignment, propertyName: string) => void) {
+36 -4
View File
@@ -29,7 +29,7 @@ namespace ts.GoToDefinition {
const calledDeclaration = tryGetSignatureDeclaration(typeChecker, node);
// Don't go to the component constructor definition for a JSX element, just go to the component definition.
if (calledDeclaration && !(isJsxOpeningLikeElement(node.parent) && isConstructorDeclaration(calledDeclaration))) {
if (calledDeclaration && !(isJsxOpeningLikeElement(node.parent) && isConstructorLike(calledDeclaration))) {
const sigInfo = createDefinitionFromSignatureDeclaration(typeChecker, calledDeclaration);
// For a function, if this is the original function definition, return just sigInfo.
// If this is the original constructor definition, parent is the class.
@@ -136,9 +136,30 @@ namespace ts.GoToDefinition {
}
const symbol = typeChecker.getSymbolAtLocation(node);
const type = symbol && typeChecker.getTypeOfSymbolAtLocation(symbol, node);
return type && flatMap(type.isUnion() && !(type.flags & TypeFlags.Enum) ? type.types : [type], t =>
t.symbol && getDefinitionFromSymbol(typeChecker, t.symbol, node));
if (!symbol) return undefined;
const typeAtLocation = typeChecker.getTypeOfSymbolAtLocation(symbol, node);
const returnType = tryGetReturnTypeOfFunction(symbol, typeAtLocation, typeChecker);
const fromReturnType = returnType && definitionFromType(returnType, typeChecker, node);
// If a function returns 'void' or some other type with no definition, just return the function definition.
return fromReturnType && fromReturnType.length !== 0 ? fromReturnType : definitionFromType(typeAtLocation, typeChecker, node);
}
function definitionFromType(type: Type, checker: TypeChecker, node: Node): DefinitionInfo[] {
return flatMap(type.isUnion() && !(type.flags & TypeFlags.Enum) ? type.types : [type], t =>
t.symbol && getDefinitionFromSymbol(checker, t.symbol, node));
}
function tryGetReturnTypeOfFunction(symbol: Symbol, type: Type, checker: TypeChecker): Type | undefined {
// If the type is just a function's inferred type,
// go-to-type should go to the return type instead, since go-to-definition takes you to the function anyway.
if (type.symbol === symbol ||
// At `const f = () => {}`, the symbol is `f` and the type symbol is at `() => {}`
symbol.valueDeclaration && type.symbol && isVariableDeclaration(symbol.valueDeclaration) && symbol.valueDeclaration.initializer === type.symbol.valueDeclaration as Node) {
const sigs = type.getCallSignatures();
if (sigs.length === 1) return checker.getReturnTypeOfSignature(first(sigs));
}
return undefined;
}
export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number): DefinitionInfoAndBoundSpan | undefined {
@@ -298,4 +319,15 @@ namespace ts.GoToDefinition {
// Don't go to a function type, go to the value having that type.
return tryCast(signature && signature.declaration, (d): d is SignatureDeclaration => isFunctionLike(d) && !isFunctionTypeNode(d));
}
function isConstructorLike(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.Constructor:
case SyntaxKind.ConstructorType:
case SyntaxKind.ConstructSignature:
return true;
default:
return false;
}
}
}
+1 -1
View File
@@ -367,7 +367,7 @@ namespace ts.JsDoc {
const varStatement = <VariableStatement>commentOwner;
const varDeclarations = varStatement.declarationList.declarations;
const parameters = varDeclarations.length === 1 && varDeclarations[0].initializer
? getParametersFromRightHandSideOfAssignment(varDeclarations[0].initializer!)
? getParametersFromRightHandSideOfAssignment(varDeclarations[0].initializer)
: undefined;
return { commentOwner, parameters };
}
+1 -1
View File
@@ -416,7 +416,7 @@ namespace ts.NavigationBar {
}
const declName = getNameOfDeclaration(<Declaration>node);
if (declName) {
if (declName && isPropertyName(declName)) {
return unescapeLeadingUnderscores(getPropertyNameForPropertyNameNode(declName)!); // TODO: GH#18217
}
switch (node.kind) {
+1 -1
View File
@@ -340,7 +340,7 @@ namespace ts.refactor {
const { name, namedBindings } = importDecl.importClause;
const defaultUnused = !name || isUnused(name);
const namedBindingsUnused = !namedBindings ||
(namedBindings.kind === SyntaxKind.NamespaceImport ? isUnused(namedBindings.name) : namedBindings.elements.every(e => isUnused(e.name)));
(namedBindings.kind === SyntaxKind.NamespaceImport ? isUnused(namedBindings.name) : namedBindings.elements.length !== 0 && namedBindings.elements.every(e => isUnused(e.name)));
if (defaultUnused && namedBindingsUnused) {
changes.delete(sourceFile, importDecl);
}
+1 -1
View File
@@ -29,7 +29,7 @@ namespace ts.Rename {
if (isStringLiteralLike(node) && tryGetImportFromModuleSpecifier(node)) return undefined;
const kind = SymbolDisplay.getSymbolKind(typeChecker, symbol, node);
const specifierName = (isImportOrExportSpecifierName(node) || isStringOrNumericLiteral(node) && node.parent.kind === SyntaxKind.ComputedPropertyName)
const specifierName = (isImportOrExportSpecifierName(node) || isStringOrNumericLiteralLike(node) && node.parent.kind === SyntaxKind.ComputedPropertyName)
? stripQuotes(getTextOfIdentifierOrLiteral(node))
: undefined;
const displayName = specifierName || typeChecker.symbolToString(symbol);
+2 -2
View File
@@ -2142,7 +2142,7 @@ namespace ts {
function initializeNameTable(sourceFile: SourceFile): void {
const nameTable = sourceFile.nameTable = createUnderscoreEscapedMap<number>();
sourceFile.forEachChild(function walk(node) {
if (isIdentifier(node) && node.escapedText || isStringOrNumericLiteral(node) && literalIsName(node)) {
if (isIdentifier(node) && node.escapedText || isStringOrNumericLiteralLike(node) && literalIsName(node)) {
const text = getEscapedTextOfIdentifierOrLiteral(node);
nameTable.set(text, nameTable.get(text) === undefined ? node.pos : -1);
}
@@ -2162,7 +2162,7 @@ namespace ts {
* then we want 'something' to be in the name table. Similarly, if we have
* "a['propname']" then we want to store "propname" in the name table.
*/
function literalIsName(node: StringLiteral | NumericLiteral): boolean {
function literalIsName(node: StringLiteralLike | NumericLiteral): boolean {
return isDeclarationName(node) ||
node.parent.kind === SyntaxKind.ExternalModuleReference ||
isArgumentOfElementAccessExpression(node) ||
+1 -2
View File
@@ -328,7 +328,6 @@ namespace ts {
}
export class LanguageServiceShimHostAdapter implements LanguageServiceHost {
private files: string[];
private loggingEnabled = false;
private tracingEnabled = false;
@@ -408,7 +407,7 @@ namespace ts {
public getScriptFileNames(): string[] {
const encoded = this.shimHost.getScriptFileNames();
return this.files = JSON.parse(encoded);
return JSON.parse(encoded);
}
public getScriptSnapshot(fileName: string): IScriptSnapshot | undefined {
+199 -85
View File
@@ -1,22 +1,23 @@
/* @internal */
namespace ts.SignatureHelp {
const enum ArgumentListKind {
TypeArguments,
CallArguments,
TaggedTemplateArguments,
JSXAttributesArguments
const enum InvocationKind { Call, TypeArgs, Contextual }
interface CallInvocation { readonly kind: InvocationKind.Call; readonly node: CallLikeExpression; }
interface TypeArgsInvocation { readonly kind: InvocationKind.TypeArgs; readonly called: Identifier; }
interface ContextualInvocation {
readonly kind: InvocationKind.Contextual;
readonly signature: Signature;
readonly node: Node; // Just for enclosingDeclaration for printing types
readonly symbol: Symbol;
}
const enum InvocationKind { Call, TypeArgs }
type Invocation = { kind: InvocationKind.Call, node: CallLikeExpression } | { kind: InvocationKind.TypeArgs, called: Expression };
type Invocation = CallInvocation | TypeArgsInvocation | ContextualInvocation;
interface ArgumentListInfo {
kind: ArgumentListKind;
invocation: Invocation;
argumentsSpan: TextSpan;
argumentIndex: number;
readonly isTypeParameterList: boolean;
readonly invocation: Invocation;
readonly argumentsSpan: TextSpan;
readonly argumentIndex: number;
/** argumentCount is the *apparent* number of arguments. */
argumentCount: number;
readonly argumentCount: number;
}
export function getSignatureHelpItems(program: Program, sourceFile: SourceFile, position: number, triggerReason: SignatureHelpTriggerReason | undefined, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
@@ -37,47 +38,63 @@ namespace ts.SignatureHelp {
return undefined;
}
const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile);
const argumentInfo = getContainingArgumentInfo(startingToken, position, sourceFile, typeChecker);
if (!argumentInfo) return undefined;
cancellationToken.throwIfCancellationRequested();
// Extra syntactic and semantic filtering of signature help
const candidateInfo = getCandidateInfo(argumentInfo, typeChecker, sourceFile, startingToken, onlyUseSyntacticOwners);
const candidateInfo = getCandidateOrTypeInfo(argumentInfo, typeChecker, sourceFile, startingToken, onlyUseSyntacticOwners);
cancellationToken.throwIfCancellationRequested();
if (!candidateInfo) {
// We didn't have any sig help items produced by the TS compiler. If this is a JS
// file, then see if we can figure out anything better.
if (isSourceFileJavaScript(sourceFile)) {
return createJavaScriptSignatureHelpItems(argumentInfo, program, cancellationToken);
}
return undefined;
return isSourceFileJavaScript(sourceFile) ? createJavaScriptSignatureHelpItems(argumentInfo, program, cancellationToken) : undefined;
}
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker => createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker));
return typeChecker.runWithCancellationToken(cancellationToken, typeChecker =>
candidateInfo.kind === CandidateOrTypeKind.Candidate
? createSignatureHelpItems(candidateInfo.candidates, candidateInfo.resolvedSignature, argumentInfo, sourceFile, typeChecker)
: createTypeHelpItems(candidateInfo.symbol, argumentInfo, sourceFile, typeChecker));
}
interface CandidateInfo { readonly candidates: ReadonlyArray<Signature>; readonly resolvedSignature: Signature; }
function getCandidateInfo(argumentInfo: ArgumentListInfo, checker: TypeChecker, sourceFile: SourceFile, startingToken: Node, onlyUseSyntacticOwners: boolean): CandidateInfo | undefined {
const { invocation } = argumentInfo;
if (invocation.kind === InvocationKind.Call) {
if (onlyUseSyntacticOwners && !isSyntacticOwner(startingToken, invocation.node, sourceFile)) {
return undefined;
const enum CandidateOrTypeKind { Candidate, Type }
interface CandidateInfo {
readonly kind: CandidateOrTypeKind.Candidate;
readonly candidates: ReadonlyArray<Signature>;
readonly resolvedSignature: Signature;
}
interface TypeInfo {
readonly kind: CandidateOrTypeKind.Type;
readonly symbol: Symbol;
}
function getCandidateOrTypeInfo({ invocation, argumentCount }: ArgumentListInfo, checker: TypeChecker, sourceFile: SourceFile, startingToken: Node, onlyUseSyntacticOwners: boolean): CandidateInfo | TypeInfo | undefined {
switch (invocation.kind) {
case InvocationKind.Call: {
if (onlyUseSyntacticOwners && !isSyntacticOwner(startingToken, invocation.node, sourceFile)) {
return undefined;
}
const candidates: Signature[] = [];
const resolvedSignature = checker.getResolvedSignatureForSignatureHelp(invocation.node, candidates, argumentCount)!; // TODO: GH#18217
return candidates.length === 0 ? undefined : { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature };
}
const candidates: Signature[] = [];
const resolvedSignature = checker.getResolvedSignatureForSignatureHelp(invocation.node, candidates, argumentInfo.argumentCount)!; // TODO: GH#18217
return candidates.length === 0 ? undefined : { candidates, resolvedSignature };
}
else if (invocation.kind === InvocationKind.TypeArgs) {
if (onlyUseSyntacticOwners && !lessThanFollowsCalledExpression(startingToken, sourceFile, invocation.called)) {
return undefined;
case InvocationKind.TypeArgs: {
const { called } = invocation;
if (onlyUseSyntacticOwners && !containsPrecedingToken(startingToken, sourceFile, isIdentifier(called) ? called.parent : called)) {
return undefined;
}
const candidates = getPossibleGenericSignatures(called, argumentCount, checker);
if (candidates.length !== 0) return { kind: CandidateOrTypeKind.Candidate, candidates, resolvedSignature: first(candidates) };
const symbol = checker.getSymbolAtLocation(called);
return symbol && { kind: CandidateOrTypeKind.Type, symbol };
}
const candidates = getPossibleGenericSignatures(invocation.called, argumentInfo.argumentCount, checker);
return candidates.length === 0 ? undefined : { candidates, resolvedSignature: first(candidates) };
}
else {
Debug.assertNever(invocation);
case InvocationKind.Contextual:
return { kind: CandidateOrTypeKind.Candidate, candidates: [invocation.signature], resolvedSignature: invocation.signature };
default:
return Debug.assertNever(invocation);
}
}
@@ -92,13 +109,14 @@ namespace ts.SignatureHelp {
return !!containingList && contains(invocationChildren, containingList);
}
case SyntaxKind.LessThanToken:
return lessThanFollowsCalledExpression(startingToken, sourceFile, node.expression);
return containsPrecedingToken(startingToken, sourceFile, node.expression);
default:
return false;
}
}
function createJavaScriptSignatureHelpItems(argumentInfo: ArgumentListInfo, program: Program, cancellationToken: CancellationToken): SignatureHelpItems | undefined {
if (argumentInfo.invocation.kind === InvocationKind.Contextual) return undefined;
// See if we can find some symbol with the call expression name that has call signatures.
const expression = getExpressionFromInvocation(argumentInfo.invocation);
const name = isIdentifier(expression) ? expression.text : isPropertyAccessExpression(expression) ? expression.name.text : undefined;
@@ -113,12 +131,12 @@ namespace ts.SignatureHelp {
}));
}
function lessThanFollowsCalledExpression(startingToken: Node, sourceFile: SourceFile, calledExpression: Expression) {
function containsPrecedingToken(startingToken: Node, sourceFile: SourceFile, container: Node) {
const precedingToken = Debug.assertDefined(
findPrecedingToken(startingToken.getFullStart(), sourceFile, startingToken.parent, /*excludeJsdoc*/ true)
);
return rangeContainsRange(calledExpression, precedingToken);
return rangeContainsRange(container, precedingToken);
}
export interface ArgumentInfoForCompletions {
@@ -128,10 +146,40 @@ namespace ts.SignatureHelp {
}
export function getArgumentInfoForCompletions(node: Node, position: number, sourceFile: SourceFile): ArgumentInfoForCompletions | undefined {
const info = getImmediatelyContainingArgumentInfo(node, position, sourceFile);
return !info || info.kind === ArgumentListKind.TypeArguments || info.invocation.kind === InvocationKind.TypeArgs ? undefined
return !info || info.isTypeParameterList || info.invocation.kind !== InvocationKind.Call ? undefined
: { invocation: info.invocation.node, argumentCount: info.argumentCount, argumentIndex: info.argumentIndex };
}
function getArgumentOrParameterListInfo(node: Node, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number, readonly argumentCount: number, readonly argumentsSpan: TextSpan } | undefined {
const info = getArgumentOrParameterListAndIndex(node, sourceFile);
if (!info) return undefined;
const { list, argumentIndex } = info;
const argumentCount = getArgumentCount(list);
if (argumentIndex !== 0) {
Debug.assertLessThan(argumentIndex, argumentCount);
}
const argumentsSpan = getApplicableSpanForArguments(list, sourceFile);
return { list, argumentIndex, argumentCount, argumentsSpan };
}
function getArgumentOrParameterListAndIndex(node: Node, sourceFile: SourceFile): { readonly list: Node, readonly argumentIndex: number } | undefined {
if (node.kind === SyntaxKind.LessThanToken || node.kind === SyntaxKind.OpenParenToken) {
// Find the list that starts right *after* the < or ( token.
// If the user has just opened a list, consider this item 0.
return { list: getChildListThatStartsWithOpenerToken(node.parent, node, sourceFile), argumentIndex: 0 };
}
else {
// findListItemInfo can return undefined if we are not in parent's argument list
// or type argument list. This includes cases where the cursor is:
// - To the right of the closing parenthesis, non-substitution template, or template tail.
// - Between the type arguments and the arguments (greater than token)
// - On the target of the call (parent.func)
// - On the 'new' keyword in a 'new' expression
const list = findContainingList(node);
return list && { list, argumentIndex: getArgumentIndex(list, node) };
}
}
/**
* Returns relevant information for the argument list and the current argument if we are
* in the argument of an invocation; returns undefined otherwise.
@@ -140,8 +188,6 @@ namespace ts.SignatureHelp {
const { parent } = node;
if (isCallOrNewExpression(parent)) {
const invocation = parent;
let list: Node | undefined;
let argumentIndex: number;
// There are 3 cases to handle:
// 1. The token introduces a list, and should begin a signature help session
@@ -157,32 +203,11 @@ namespace ts.SignatureHelp {
// Case 3:
// foo<T#, U#>(a#, #b#) -> The token is buried inside a list, and should give signature help
// Find out if 'node' is an argument, a type argument, or neither
if (node.kind === SyntaxKind.LessThanToken || node.kind === SyntaxKind.OpenParenToken) {
// Find the list that starts right *after* the < or ( token.
// If the user has just opened a list, consider this item 0.
list = getChildListThatStartsWithOpenerToken(parent, node, sourceFile);
Debug.assert(list !== undefined);
argumentIndex = 0;
}
else {
// findListItemInfo can return undefined if we are not in parent's argument list
// or type argument list. This includes cases where the cursor is:
// - To the right of the closing parenthesis, non-substitution template, or template tail.
// - Between the type arguments and the arguments (greater than token)
// - On the target of the call (parent.func)
// - On the 'new' keyword in a 'new' expression
list = findContainingList(node);
if (!list) return undefined;
argumentIndex = getArgumentIndex(list, node);
}
const kind = parent.typeArguments && parent.typeArguments.pos === list.pos ? ArgumentListKind.TypeArguments : ArgumentListKind.CallArguments;
const argumentCount = getArgumentCount(list);
if (argumentIndex !== 0) {
Debug.assertLessThan(argumentIndex, argumentCount);
}
const argumentsSpan = getApplicableSpanForArguments(list, sourceFile);
return { kind, invocation: { kind: InvocationKind.Call, node: invocation }, argumentsSpan, argumentIndex, argumentCount };
const info = getArgumentOrParameterListInfo(node, sourceFile);
if (!info) return undefined;
const { list, argumentIndex, argumentCount, argumentsSpan } = info;
const isTypeParameterList = !!parent.typeArguments && parent.typeArguments.pos === list.pos;
return { isTypeParameterList, invocation: { kind: InvocationKind.Call, node: invocation }, argumentsSpan, argumentIndex, argumentCount };
}
else if (isNoSubstitutionTemplateLiteral(node) && isTaggedTemplateExpression(parent)) {
// Check if we're actually inside the template;
@@ -190,6 +215,7 @@ namespace ts.SignatureHelp {
if (isInsideTemplateLiteral(node, position, sourceFile)) {
return getArgumentListInfoForTemplate(parent, /*argumentIndex*/ 0, sourceFile);
}
return undefined;
}
else if (isTemplateHead(node) && parent.parent.kind === SyntaxKind.TaggedTemplateExpression) {
const templateExpression = <TemplateExpression>parent;
@@ -223,7 +249,7 @@ namespace ts.SignatureHelp {
const attributeSpanStart = parent.attributes.pos;
const attributeSpanEnd = skipTrivia(sourceFile.text, parent.attributes.end, /*stopAfterLineBreak*/ false);
return {
kind: ArgumentListKind.JSXAttributesArguments,
isTypeParameterList: false,
invocation: { kind: InvocationKind.Call, node: parent },
argumentsSpan: createTextSpan(attributeSpanStart, attributeSpanEnd - attributeSpanStart),
argumentIndex: 0,
@@ -236,11 +262,67 @@ namespace ts.SignatureHelp {
const { called, nTypeArguments } = typeArgInfo;
const invocation: Invocation = { kind: InvocationKind.TypeArgs, called };
const argumentsSpan = createTextSpanFromBounds(called.getStart(sourceFile), node.end);
return { kind: ArgumentListKind.TypeArguments, invocation, argumentsSpan, argumentIndex: nTypeArguments, argumentCount: nTypeArguments + 1 };
return { isTypeParameterList: true, invocation, argumentsSpan, argumentIndex: nTypeArguments, argumentCount: nTypeArguments + 1 };
}
return undefined;
}
}
return undefined;
function getImmediatelyContainingArgumentOrContextualParameterInfo(node: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): ArgumentListInfo | undefined {
return tryGetParameterInfo(node, position, sourceFile, checker) || getImmediatelyContainingArgumentInfo(node, position, sourceFile);
}
function getHighestBinary(b: BinaryExpression): BinaryExpression {
return isBinaryExpression(b.parent) ? getHighestBinary(b.parent) : b;
}
function countBinaryExpressionParameters(b: BinaryExpression): number {
return isBinaryExpression(b.left) ? countBinaryExpressionParameters(b.left) + 1 : 2;
}
function tryGetParameterInfo(startingToken: Node, _position: number, sourceFile: SourceFile, checker: TypeChecker): ArgumentListInfo | undefined {
const info = getContextualSignatureLocationInfo(startingToken, sourceFile, checker);
if (!info) return undefined;
const { contextualType, argumentIndex, argumentCount, argumentsSpan } = info;
const signatures = contextualType.getCallSignatures();
if (signatures.length !== 1) return undefined;
const invocation: ContextualInvocation = { kind: InvocationKind.Contextual, signature: first(signatures), node: startingToken, symbol: chooseBetterSymbol(contextualType.symbol) };
return { isTypeParameterList: false, invocation, argumentsSpan, argumentIndex, argumentCount };
}
interface ContextualSignatureLocationInfo {readonly contextualType: Type; readonly argumentIndex: number; readonly argumentCount: number; readonly argumentsSpan: TextSpan; }
function getContextualSignatureLocationInfo(startingToken: Node, sourceFile: SourceFile, checker: TypeChecker): ContextualSignatureLocationInfo | undefined {
if (startingToken.kind !== SyntaxKind.OpenParenToken && startingToken.kind !== SyntaxKind.CommaToken) return undefined;
const { parent } = startingToken;
switch (parent.kind) {
case SyntaxKind.ParenthesizedExpression:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
const info = getArgumentOrParameterListInfo(startingToken, sourceFile);
if (!info) return undefined;
const { argumentIndex, argumentCount, argumentsSpan } = info;
const contextualType = isMethodDeclaration(parent) ? checker.getContextualTypeForObjectLiteralElement(parent) : checker.getContextualType(parent as ParenthesizedExpression | FunctionExpression | ArrowFunction);
return contextualType && { contextualType, argumentIndex, argumentCount, argumentsSpan };
case SyntaxKind.BinaryExpression: {
const highestBinary = getHighestBinary(parent as BinaryExpression);
const contextualType = checker.getContextualType(highestBinary);
const argumentIndex = startingToken.kind === SyntaxKind.OpenParenToken ? 0 : countBinaryExpressionParameters(parent as BinaryExpression) - 1;
const argumentCount = countBinaryExpressionParameters(highestBinary);
return contextualType && { contextualType, argumentIndex, argumentCount, argumentsSpan: createTextSpanFromNode(parent) };
}
default:
return undefined;
}
}
// The type of a function type node has a symbol at that node, but it's better to use the symbol for a parameter or type alias.
function chooseBetterSymbol(s: Symbol): Symbol {
return s.name === InternalSymbolName.Type
? firstDefined(s.declarations, d => isFunctionTypeNode(d) ? d.parent.symbol : undefined) || s
: s;
}
function getArgumentIndex(argumentsList: Node, node: Node) {
@@ -323,7 +405,7 @@ namespace ts.SignatureHelp {
Debug.assertLessThan(argumentIndex, argumentCount);
}
return {
kind: ArgumentListKind.TaggedTemplateArguments,
isTypeParameterList: false,
invocation: { kind: InvocationKind.Call, node: tagExpression },
argumentsSpan: getApplicableSpanForTaggedTemplate(tagExpression, sourceFile),
argumentIndex,
@@ -368,12 +450,12 @@ namespace ts.SignatureHelp {
return createTextSpan(applicableSpanStart, applicableSpanEnd - applicableSpanStart);
}
function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile): ArgumentListInfo | undefined {
function getContainingArgumentInfo(node: Node, position: number, sourceFile: SourceFile, checker: TypeChecker): ArgumentListInfo | undefined {
for (let n = node; !isBlock(n) && !isSourceFile(n); n = n.parent) {
// If the node is not a subspan of its parent, this is a big problem.
// There have been crashes that might be caused by this violation.
Debug.assert(rangeContainsRange(n.parent, n), "Not a subspan", () => `Child: ${Debug.showSyntaxKind(n)}, parent: ${Debug.showSyntaxKind(n.parent)}`);
const argumentInfo = getImmediatelyContainingArgumentInfo(n, position, sourceFile);
const argumentInfo = getImmediatelyContainingArgumentOrContextualParameterInfo(n, position, sourceFile, checker);
if (argumentInfo) {
return argumentInfo;
}
@@ -388,17 +470,24 @@ namespace ts.SignatureHelp {
return children[indexOfOpenerToken + 1];
}
function getExpressionFromInvocation(invocation: Invocation): Expression {
function getExpressionFromInvocation(invocation: CallInvocation | TypeArgsInvocation): Expression {
return invocation.kind === InvocationKind.Call ? getInvokedExpression(invocation.node) : invocation.called;
}
const signatureHelpNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope;
function createSignatureHelpItems(candidates: ReadonlyArray<Signature>, resolvedSignature: Signature, argumentListInfo: ArgumentListInfo, sourceFile: SourceFile, typeChecker: TypeChecker): SignatureHelpItems {
const { argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex } = argumentListInfo;
const isTypeParameterList = argumentListInfo.kind === ArgumentListKind.TypeArguments;
function getEnclosingDeclarationFromInvocation(invocation: Invocation): Node {
return invocation.kind === InvocationKind.Call ? invocation.node : invocation.kind === InvocationKind.TypeArgs ? invocation.called : invocation.node;
}
const enclosingDeclaration = invocation.kind === InvocationKind.Call ? invocation.node : invocation.called;
const callTargetSymbol = typeChecker.getSymbolAtLocation(getExpressionFromInvocation(invocation));
const signatureHelpNodeBuilderFlags = NodeBuilderFlags.OmitParameterModifiers | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.UseAliasDefinedOutsideCurrentScope;
function createSignatureHelpItems(
candidates: ReadonlyArray<Signature>,
resolvedSignature: Signature,
{ isTypeParameterList, argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo,
sourceFile: SourceFile,
typeChecker: TypeChecker,
): SignatureHelpItems {
const enclosingDeclaration = getEnclosingDeclarationFromInvocation(invocation);
const callTargetSymbol = invocation.kind === InvocationKind.Contextual ? invocation.symbol : typeChecker.getSymbolAtLocation(getExpressionFromInvocation(invocation));
const callTargetDisplayParts = callTargetSymbol ? symbolToDisplayParts(typeChecker, callTargetSymbol, /*enclosingDeclaration*/ undefined, /*meaning*/ undefined) : emptyArray;
const items = candidates.map(candidateSignature => getSignatureHelpItem(candidateSignature, callTargetDisplayParts, isTypeParameterList, typeChecker, enclosingDeclaration, sourceFile));
@@ -412,11 +501,36 @@ namespace ts.SignatureHelp {
return { items, applicableSpan, selectedItemIndex, argumentIndex, argumentCount };
}
function createTypeHelpItems(
symbol: Symbol,
{ argumentCount, argumentsSpan: applicableSpan, invocation, argumentIndex }: ArgumentListInfo,
sourceFile: SourceFile,
checker: TypeChecker
): SignatureHelpItems | undefined {
const typeParameters = checker.getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
if (!typeParameters) return undefined;
const items = [getTypeHelpItem(symbol, typeParameters, checker, getEnclosingDeclarationFromInvocation(invocation), sourceFile)];
return { items, applicableSpan, selectedItemIndex: 0, argumentIndex, argumentCount };
}
function getTypeHelpItem(symbol: Symbol, typeParameters: ReadonlyArray<TypeParameter>, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem {
const typeSymbolDisplay = symbolToDisplayParts(checker, symbol);
const printer = createPrinter({ removeComments: true });
const parameters = typeParameters.map(t => createSignatureHelpParameterForTypeParameter(t, checker, enclosingDeclaration, sourceFile, printer));
const documentation = symbol.getDocumentationComment(checker);
const tags = symbol.getJsDocTags();
const prefixDisplayParts = [...typeSymbolDisplay, punctuationPart(SyntaxKind.LessThanToken)];
return { isVariadic: false, prefixDisplayParts, suffixDisplayParts: [punctuationPart(SyntaxKind.GreaterThanToken)], separatorDisplayParts, parameters, documentation, tags };
}
const separatorDisplayParts: SymbolDisplayPart[] = [punctuationPart(SyntaxKind.CommaToken), spacePart()];
function getSignatureHelpItem(candidateSignature: Signature, callTargetDisplayParts: ReadonlyArray<SymbolDisplayPart>, isTypeParameterList: boolean, checker: TypeChecker, enclosingDeclaration: Node, sourceFile: SourceFile): SignatureHelpItem {
const { isVariadic, parameters, prefix, suffix } = (isTypeParameterList ? itemInfoForTypeParameters : itemInfoForParameters)(candidateSignature, checker, enclosingDeclaration, sourceFile);
const prefixDisplayParts = [...callTargetDisplayParts, ...prefix];
const suffixDisplayParts = [...suffix, ...returnTypeToDisplayParts(candidateSignature, enclosingDeclaration, checker)];
const separatorDisplayParts = [punctuationPart(SyntaxKind.CommaToken), spacePart()];
const documentation = candidateSignature.getDocumentationComment(checker);
const tags = candidateSignature.getJsDocTags();
return { isVariadic, prefixDisplayParts, suffixDisplayParts, separatorDisplayParts, parameters, documentation, tags };
@@ -477,6 +591,6 @@ namespace ts.SignatureHelp {
const param = checker.typeParameterToDeclaration(typeParameter, enclosingDeclaration)!;
printer.writeNode(EmitHint.Unspecified, param, sourceFile, writer);
});
return { name: typeParameter.symbol.name, documentation: emptyArray, displayParts, isOptional: false };
return { name: typeParameter.symbol.name, documentation: typeParameter.symbol.getDocumentationComment(checker), displayParts, isOptional: false };
}
}
+62
View File
@@ -3,6 +3,7 @@ namespace ts {
export function computeSuggestionDiagnostics(sourceFile: SourceFile, program: Program, cancellationToken: CancellationToken): DiagnosticWithLocation[] {
program.getSemanticDiagnostics(sourceFile, cancellationToken);
const diags: DiagnosticWithLocation[] = [];
const checker = program.getTypeChecker();
if (sourceFile.commonJsModuleIndicator &&
(programContainsEs6Modules(program) || compilerOptionsIndicateEs6Modules(program.getCompilerOptions())) &&
@@ -68,6 +69,9 @@ namespace ts {
}
}
if (isFunctionLikeDeclaration(node)) {
addConvertToAsyncFunctionDiagnostics(node, checker, diags);
}
node.forEachChild(check);
}
}
@@ -109,7 +113,65 @@ namespace ts {
}
}
function addConvertToAsyncFunctionDiagnostics(node: FunctionLikeDeclaration, checker: TypeChecker, diags: DiagnosticWithLocation[]): void {
if (isAsyncFunction(node) || !node.body) {
return;
}
const functionType = checker.getTypeAtLocation(node);
const callSignatures = checker.getSignaturesOfType(functionType, SignatureKind.Call);
const returnType = callSignatures.length ? checker.getReturnTypeOfSignature(callSignatures[0]) : undefined;
if (!returnType || !checker.getPromisedTypeOfPromise(returnType)) {
return;
}
// collect all the return statements
// check that a property access expression exists in there and that it is a handler
const returnStatements = getReturnStatementsWithPromiseHandlers(node);
if (returnStatements.length > 0) {
diags.push(createDiagnosticForNode(isVariableDeclaration(node.parent) ? node.parent.name : node, Diagnostics.This_may_be_converted_to_an_async_function));
}
}
function getErrorNodeFromCommonJsIndicator(commonJsModuleIndicator: Node): Node {
return isBinaryExpression(commonJsModuleIndicator) ? commonJsModuleIndicator.left : commonJsModuleIndicator;
}
/** @internal */
export function getReturnStatementsWithPromiseHandlers(node: Node): Node[] {
const returnStatements: Node[] = [];
if (isFunctionLike(node)) {
forEachChild(node, visit);
}
else {
visit(node);
}
function visit(child: Node) {
if (isFunctionLike(child)) {
return;
}
if (isReturnStatement(child)) {
forEachChild(child, addHandlers);
}
function addHandlers(returnChild: Node) {
if (isPromiseHandler(returnChild)) {
returnStatements.push(child as ReturnStatement);
}
}
forEachChild(child, visit);
}
return returnStatements;
}
function isPromiseHandler(node: Node): boolean {
return (isCallExpression(node) && isPropertyAccessExpression(node.expression) &&
(node.expression.name.text === "then" || node.expression.name.text === "catch"));
}
}
+2 -1
View File
@@ -611,7 +611,8 @@ namespace ts.SymbolDisplay {
displayParts.push(textPart(allSignatures.length === 2 ? "overload" : "overloads"));
displayParts.push(punctuationPart(SyntaxKind.CloseParenToken));
}
documentation = signature.getDocumentationComment(typeChecker);
const docComment = signature.getDocumentationComment(typeChecker);
documentation = docComment.length === 0 ? undefined : docComment;
tags = signature.getJsDocTags();
}
+11 -11
View File
@@ -291,7 +291,7 @@ namespace ts.textChanges {
}
public insertNodeAt(sourceFile: SourceFile, pos: number, newNode: Node, options: InsertNodeOptions = {}) {
this.replaceRange(sourceFile, createTextRange(pos), newNode, options);
this.replaceRange(sourceFile, createRange(pos), newNode, options);
}
private insertNodesAt(sourceFile: SourceFile, pos: number, newNodes: ReadonlyArray<Node>, options: ReplaceWithMultipleNodesOptions = {}): void {
@@ -334,7 +334,7 @@ namespace ts.textChanges {
}
public insertText(sourceFile: SourceFile, pos: number, text: string): void {
this.replaceRangeWithText(sourceFile, createTextRange(pos), text);
this.replaceRangeWithText(sourceFile, createRange(pos), text);
}
/** Prefer this over replacing a node with another that has a type annotation, as it avoids reformatting the other parts of the node. */
@@ -409,14 +409,14 @@ namespace ts.textChanges {
});
}
public insertNodeAtClassStart(sourceFile: SourceFile, cls: ClassLikeDeclaration, newElement: ClassElement): void {
public insertNodeAtClassStart(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration, newElement: ClassElement): void {
const clsStart = cls.getStart(sourceFile);
const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(getLineStartPositionForPosition(clsStart, sourceFile), clsStart, sourceFile, this.formatContext.options)
+ this.formatContext.options.indentSize!;
this.insertNodeAt(sourceFile, cls.members.pos, newElement, { indentation, ...this.getInsertNodeAtClassStartPrefixSuffix(sourceFile, cls) });
}
private getInsertNodeAtClassStartPrefixSuffix(sourceFile: SourceFile, cls: ClassLikeDeclaration): { prefix: string, suffix: string } {
private getInsertNodeAtClassStartPrefixSuffix(sourceFile: SourceFile, cls: ClassLikeDeclaration | InterfaceDeclaration): { prefix: string, suffix: string } {
if (cls.members.length === 0) {
if (addToSeen(this.classesWithNodesInsertedAtStart, getNodeId(cls), cls)) {
// For `class C {\n}`, don't add the trailing "\n"
@@ -456,7 +456,7 @@ namespace ts.textChanges {
// check if previous statement ends with semicolon
// if not - insert semicolon to preserve the code from changing the meaning due to ASI
if (sourceFile.text.charCodeAt(after.end - 1) !== CharacterCodes.semicolon) {
this.replaceRange(sourceFile, createTextRange(after.end), createToken(SyntaxKind.SemicolonToken));
this.replaceRange(sourceFile, createRange(after.end), createToken(SyntaxKind.SemicolonToken));
}
}
const endPosition = getAdjustedEndPosition(sourceFile, after, {});
@@ -595,7 +595,7 @@ namespace ts.textChanges {
// write separator and leading trivia of the next element as suffix
const suffix = `${tokenToString(nextToken.kind)}${sourceFile.text.substring(nextToken.end, containingList[index + 1].getStart(sourceFile))}`;
this.replaceRange(sourceFile, createTextRange(startPos, containingList[index + 1].getStart(sourceFile)), newNode, { prefix, suffix });
this.replaceRange(sourceFile, createRange(startPos, containingList[index + 1].getStart(sourceFile)), newNode, { prefix, suffix });
}
}
else {
@@ -629,7 +629,7 @@ namespace ts.textChanges {
}
if (multilineList) {
// insert separator immediately following the 'after' node to preserve comments in trailing trivia
this.replaceRange(sourceFile, createTextRange(end), createToken(separator));
this.replaceRange(sourceFile, createRange(end), createToken(separator));
// use the same indentation as 'after' item
const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(afterStartLinePosition, afterStart, sourceFile, this.formatContext.options);
// insert element before the line break on the line that contains 'after' element
@@ -637,10 +637,10 @@ namespace ts.textChanges {
if (insertPos !== end && isLineBreak(sourceFile.text.charCodeAt(insertPos - 1))) {
insertPos--;
}
this.replaceRange(sourceFile, createTextRange(insertPos), newNode, { indentation, prefix: this.newLineCharacter });
this.replaceRange(sourceFile, createRange(insertPos), newNode, { indentation, prefix: this.newLineCharacter });
}
else {
this.replaceRange(sourceFile, createTextRange(end), newNode, { prefix: `${tokenToString(separator)} ` });
this.replaceRange(sourceFile, createRange(end), newNode, { prefix: `${tokenToString(separator)} ` });
}
}
return this;
@@ -652,7 +652,7 @@ namespace ts.textChanges {
const [openBraceEnd, closeBraceEnd] = getClassBraceEnds(cls, sourceFile);
// For `class C { }` remove the whitespace inside the braces.
if (positionsAreOnSameLine(openBraceEnd, closeBraceEnd, sourceFile) && openBraceEnd !== closeBraceEnd - 1) {
this.deleteRange(sourceFile, createTextRange(openBraceEnd, closeBraceEnd - 1));
this.deleteRange(sourceFile, createRange(openBraceEnd, closeBraceEnd - 1));
}
});
}
@@ -708,7 +708,7 @@ namespace ts.textChanges {
return skipTrivia(sourceFile.text, getAdjustedStartPosition(sourceFile, node, {}, Position.FullStart), /*stopAfterLineBreak*/ false, /*stopAtComments*/ true);
}
function getClassBraceEnds(cls: ClassLikeDeclaration, sourceFile: SourceFile): [number, number] {
function getClassBraceEnds(cls: ClassLikeDeclaration | InterfaceDeclaration, sourceFile: SourceFile): [number, number] {
return [findChildOfKind(cls, SyntaxKind.OpenBraceToken, sourceFile)!.end, findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile)!.end];
}
+1
View File
@@ -46,6 +46,7 @@
"codefixes/addMissingInvocationForDecorator.ts",
"codefixes/annotateWithTypeFromJSDoc.ts",
"codefixes/convertFunctionToEs6Class.ts",
"codefixes/convertToAsyncFunction.ts",
"codefixes/convertToEs6Module.ts",
"codefixes/correctQualifiedNameToIndexedAccessType.ts",
"codefixes/fixClassIncorrectlyImplementsInterface.ts",
-8
View File
@@ -233,14 +233,6 @@ namespace ts {
installPackage?(options: InstallPackageOptions): Promise<ApplyCodeActionCommandResult>;
}
export interface UserPreferences {
readonly disableSuggestions?: boolean;
readonly quotePreference?: "double" | "single";
readonly includeCompletionsForModuleExports?: boolean;
readonly includeCompletionsWithInsertText?: boolean;
readonly importModuleSpecifierPreference?: "relative" | "non-relative";
readonly allowTextChangesInNewFiles?: boolean;
}
/* @internal */
export const emptyOptions = {};
+45 -15
View File
@@ -226,7 +226,7 @@ namespace ts {
export function isJumpStatementTarget(node: Node): node is Identifier & { parent: BreakOrContinueStatement } {
return node.kind === SyntaxKind.Identifier && isBreakOrContinueStatement(node.parent) && node.parent.label === node;
}
}
export function isLabelOfLabeledStatement(node: Node): node is Identifier {
return node.kind === SyntaxKind.Identifier && isLabeledStatement(node.parent) && node.parent.label === node;
@@ -396,7 +396,7 @@ namespace ts {
export function isThis(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.ThisKeyword:
// case SyntaxKind.ThisType: TODO: GH#9267
// case SyntaxKind.ThisType: TODO: GH#9267
return true;
case SyntaxKind.Identifier:
// 'this' as a parameter
@@ -755,7 +755,7 @@ namespace ts {
return result;
function find(n: Node): Node | undefined {
if (isNonWhitespaceToken(n)) {
if (isNonWhitespaceToken(n) && n.kind !== SyntaxKind.EndOfFileToken) {
return n;
}
@@ -786,16 +786,14 @@ namespace ts {
}
}
Debug.assert(startNode !== undefined || n.kind === SyntaxKind.SourceFile || isJSDocCommentContainingNode(n));
Debug.assert(startNode !== undefined || n.kind === SyntaxKind.SourceFile || n.kind === SyntaxKind.EndOfFileToken || isJSDocCommentContainingNode(n));
// Here we know that none of child token nodes embrace the position,
// the only known case is when position is at the end of the file.
// Try to find the rightmost token in the file without filtering.
// Namely we are skipping the check: 'position < node.end'
if (children.length) {
const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length, sourceFile);
return candidate && findRightmostToken(candidate, sourceFile);
}
const candidate = findRightmostChildNodeWithTokens(children, /*exclusiveStartPosition*/ children.length, sourceFile);
return candidate && findRightmostToken(candidate, sourceFile);
}
}
@@ -1048,7 +1046,7 @@ namespace ts {
function nodeHasTokens(n: Node, sourceFile: SourceFileLike): boolean {
// If we have a token or node that has a non-zero width, it must have tokens.
// Note: getWidth() does not take trivia into account.
return n.getWidth(sourceFile) !== 0;
return n.kind === SyntaxKind.EndOfFileToken ? !!(n as EndOfFileToken).jsDoc : n.getWidth(sourceFile) !== 0;
}
export function getNodeModifiers(node: Node): string {
@@ -1165,7 +1163,7 @@ namespace ts {
}
export function createTextRangeFromNode(node: Node, sourceFile: SourceFile): TextRange {
return createTextRange(node.getStart(sourceFile), node.end);
return createRange(node.getStart(sourceFile), node.end);
}
export function createTextSpanFromRange(range: TextRange): TextSpan {
@@ -1173,7 +1171,7 @@ namespace ts {
}
export function createTextRangeFromSpan(span: TextSpan): TextRange {
return createTextRange(span.start, span.start + span.length);
return createRange(span.start, span.start + span.length);
}
export function createTextChangeFromStartLength(start: number, length: number, newText: string): TextChange {
@@ -1187,6 +1185,7 @@ namespace ts {
export const typeKeywords: ReadonlyArray<SyntaxKind> = [
SyntaxKind.AnyKeyword,
SyntaxKind.BooleanKeyword,
SyntaxKind.FalseKeyword,
SyntaxKind.KeyOfKeyword,
SyntaxKind.NeverKeyword,
SyntaxKind.NullKeyword,
@@ -1194,6 +1193,7 @@ namespace ts {
SyntaxKind.ObjectKeyword,
SyntaxKind.StringKeyword,
SyntaxKind.SymbolKeyword,
SyntaxKind.TrueKeyword,
SyntaxKind.VoidKeyword,
SyntaxKind.UndefinedKeyword,
SyntaxKind.UniqueKeyword,
@@ -1232,13 +1232,13 @@ namespace ts {
}
export function skipConstraint(type: Type): Type {
return type.isTypeParameter() ? type.getConstraint()! : type; // TODO: GH#18217
return type.isTypeParameter() ? type.getConstraint() || type : type;
}
export function getNameFromPropertyName(name: PropertyName): string | undefined {
return name.kind === SyntaxKind.ComputedPropertyName
// treat computed property names where expression is string/numeric literal as just string/numeric literal
? isStringOrNumericLiteral(name.expression) ? name.expression.text : undefined
? isStringOrNumericLiteralLike(name.expression) ? name.expression.text : undefined
: getTextOfIdentifierOrLiteral(name);
}
@@ -1654,8 +1654,34 @@ namespace ts {
return clone;
}
function getSynthesizedDeepCloneWorker<T extends Node>(node: T): T {
const visited = visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext);
export function getSynthesizedDeepCloneWithRenames<T extends Node | undefined>(node: T, includeTrivia = true, renameMap?: Map<Identifier>, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T {
let clone;
if (node && isIdentifier(node!) && renameMap && checker) {
const symbol = checker.getSymbolAtLocation(node!);
const renameInfo = symbol && renameMap.get(String(getSymbolId(symbol)));
if (renameInfo) {
clone = createIdentifier(renameInfo.text);
}
}
if (!clone) {
clone = node && getSynthesizedDeepCloneWorker(node as NonNullable<T>, renameMap, checker, callback);
}
if (clone && !includeTrivia) suppressLeadingAndTrailingTrivia(clone);
if (callback && node) callback(node!, clone);
return clone as T;
}
function getSynthesizedDeepCloneWorker<T extends Node>(node: T, renameMap?: Map<Identifier>, checker?: TypeChecker, callback?: (originalNode: Node, clone: Node) => any): T {
const visited = (renameMap || checker || callback) ?
visitEachChild(node, wrapper, nullTransformationContext) :
visitEachChild(node, getSynthesizedDeepClone, nullTransformationContext);
if (visited === node) {
// This only happens for leaf nodes - internal nodes always see their children change.
const clone = getSynthesizedClone(node);
@@ -1673,6 +1699,10 @@ namespace ts {
// would have made.
visited.parent = undefined!;
return visited;
function wrapper(node: T) {
return getSynthesizedDeepCloneWithRenames(node, /*includeTrivia*/ true, renameMap, checker, callback);
}
}
export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T>, includeTrivia?: boolean): NodeArray<T>;
+15 -14
View File
@@ -207,24 +207,19 @@ class CompilerTest {
public verifyModuleResolution() {
if (this.options.traceResolution) {
Harness.Baseline.runBaseline(this.justName.replace(/\.tsx?$/, ".trace.json"), () => {
return utils.removeTestPathPrefixes(JSON.stringify(this.result.traces, undefined, 4));
});
Harness.Baseline.runBaseline(this.justName.replace(/\.tsx?$/, ".trace.json"),
utils.removeTestPathPrefixes(JSON.stringify(this.result.traces, undefined, 4)));
}
}
public verifySourceMapRecord() {
if (this.options.sourceMap || this.options.inlineSourceMap || this.options.declarationMap) {
Harness.Baseline.runBaseline(this.justName.replace(/\.tsx?$/, ".sourcemap.txt"), () => {
const record = utils.removeTestPathPrefixes(this.result.getSourceMapRecord()!);
if ((this.options.noEmitOnError && this.result.diagnostics.length !== 0) || record === undefined) {
// Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required.
/* tslint:disable:no-null-keyword */
return null;
/* tslint:enable:no-null-keyword */
}
return record;
});
const record = utils.removeTestPathPrefixes(this.result.getSourceMapRecord()!);
const baseline = (this.options.noEmitOnError && this.result.diagnostics.length !== 0) || record === undefined
// Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required.
? null // tslint:disable-line no-null-keyword
: record;
Harness.Baseline.runBaseline(this.justName.replace(/\.tsx?$/, ".sourcemap.txt"), baseline);
}
}
@@ -258,7 +253,13 @@ class CompilerTest {
Harness.Compiler.doTypeAndSymbolBaseline(
this.justName,
this.result.program!,
this.toBeCompiled.concat(this.otherFiles).filter(file => !!this.result.program!.getSourceFile(file.unitName)));
this.toBeCompiled.concat(this.otherFiles).filter(file => !!this.result.program!.getSourceFile(file.unitName)),
/*opts*/ undefined,
/*multifile*/ undefined,
/*skipTypeBaselines*/ undefined,
/*skipSymbolBaselines*/ undefined,
!!ts.length(this.result.diagnostics)
);
}
private makeUnitName(name: string, root: string) {
+3 -4
View File
@@ -80,9 +80,7 @@ abstract class ExternalCompileRunnerBase extends RunnerBase {
if (install.status !== 0) throw new Error(`NPM Install types for ${directoryName} failed: ${install.stderr.toString()}`);
}
args.push("--noEmit");
Harness.Baseline.runBaseline(`${cls.kind()}/${directoryName}.log`, () => {
return cls.report(cp.spawnSync(`node`, args, { cwd, timeout, shell: true }), cwd);
});
Harness.Baseline.runBaseline(`${cls.kind()}/${directoryName}.log`, cls.report(cp.spawnSync(`node`, args, { cwd, timeout, shell: true }), cwd));
});
});
}
@@ -138,7 +136,8 @@ function compareErrorStrings(a: string[], b: string[]) {
return ts.comparePathsCaseSensitive(errorFileA, errorFileB) ||
ts.compareValues(parseInt(lineNumberStringA), parseInt(lineNumberStringB)) ||
ts.compareValues(parseInt(columnNumberStringA), parseInt(columnNumberStringB)) ||
ts.compareStringsCaseSensitive(remainderA, remainderB);
ts.compareStringsCaseSensitive(remainderA, remainderB) ||
ts.compareStringsCaseSensitive(a.slice(1).join("\n"), b.slice(1).join("\n"));
}
class DefinitelyTypedRunner extends ExternalCompileRunnerBase {
+4 -8
View File
@@ -211,14 +211,12 @@ namespace project {
.map(output => utils.removeTestPathPrefixes(vpath.isAbsolute(output) ? vpath.relative(cwd, output, ignoreCase) : output));
const content = JSON.stringify(resolutionInfo, undefined, " ");
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".json", () => content);
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".json", content);
}
public verifyDiagnostics() {
if (this.compilerResult.errors.length) {
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".errors.txt", () => {
return getErrorsBaseline(this.compilerResult);
});
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".errors.txt", getErrorsBaseline(this.compilerResult));
}
}
@@ -244,7 +242,7 @@ namespace project {
}
const content = utils.removeTestPathPrefixes(output.text, /*retainTrailingDirectorySeparator*/ true);
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + diskRelativeName, () => content as string | null); // TODO: GH#18217
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + diskRelativeName, content as string | null); // TODO: GH#18217
}
catch (e) {
errs.push(e);
@@ -271,9 +269,7 @@ namespace project {
if (!this.compilerResult.errors.length && this.testCase.declaration) {
const dTsCompileResult = this.compileDeclarations(this.compilerResult);
if (dTsCompileResult && dTsCompileResult.errors.length) {
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".dts.errors.txt", () => {
return getErrorsBaseline(dTsCompileResult);
});
Harness.Baseline.runBaseline(this.getBaselineFolder(this.compilerResult.moduleKind) + this.testCaseJustName + ".dts.errors.txt", getErrorsBaseline(dTsCompileResult));
}
}
}
+7 -16
View File
@@ -63,21 +63,14 @@ class Test262BaselineRunner extends RunnerBase {
});
it("has the expected emitted code", () => {
Harness.Baseline.runBaseline(testState.filename + ".output.js", () => {
const files = Array.from(testState.compilerResult.js.values()).filter(f => f.file !== Test262BaselineRunner.helpersFilePath);
return Harness.Compiler.collateOutputs(files);
}, Test262BaselineRunner.baselineOptions);
const files = Array.from(testState.compilerResult.js.values()).filter(f => f.file !== Test262BaselineRunner.helpersFilePath);
Harness.Baseline.runBaseline(testState.filename + ".output.js", Harness.Compiler.collateOutputs(files), Test262BaselineRunner.baselineOptions);
});
it("has the expected errors", () => {
Harness.Baseline.runBaseline(testState.filename + ".errors.txt", () => {
const errors = testState.compilerResult.diagnostics;
if (errors.length === 0) {
return null;
}
return Harness.Compiler.getErrorBaseline(testState.inputFiles, errors);
}, Test262BaselineRunner.baselineOptions);
const errors = testState.compilerResult.diagnostics;
const baseline = errors.length === 0 ? null : Harness.Compiler.getErrorBaseline(testState.inputFiles, errors);
Harness.Baseline.runBaseline(testState.filename + ".errors.txt", baseline, Test262BaselineRunner.baselineOptions);
});
it("satisfies invariants", () => {
@@ -86,10 +79,8 @@ class Test262BaselineRunner extends RunnerBase {
});
it("has the expected AST", () => {
Harness.Baseline.runBaseline(testState.filename + ".AST.txt", () => {
const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename))!;
return Utils.sourceFileToJSON(sourceFile);
}, Test262BaselineRunner.baselineOptions);
const sourceFile = testState.compilerResult.program!.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename))!;
Harness.Baseline.runBaseline(testState.filename + ".AST.txt", Utils.sourceFileToJSON(sourceFile), Test262BaselineRunner.baselineOptions);
});
});
}
+3
View File
@@ -46,8 +46,10 @@
"unittests/cancellableLanguageServiceOperations.ts",
"unittests/commandLineParsing.ts",
"unittests/compileOnSave.ts",
"unittests/compilerCore.ts",
"unittests/configurationExtension.ts",
"unittests/convertCompilerOptionsFromJson.ts",
"unittests/convertToAsyncFunction.ts",
"unittests/convertToBase64.ts",
"unittests/convertTypeAcquisitionFromJson.ts",
"unittests/customTransforms.ts",
@@ -79,6 +81,7 @@
"unittests/transform.ts",
"unittests/transpile.ts",
"unittests/tsbuild.ts",
"unittests/tsbuildWatchMode.ts",
"unittests/tsconfigParsing.ts",
"unittests/tscWatchMode.ts",
"unittests/versionCache.ts",
@@ -366,4 +366,120 @@ namespace ts {
});
});
});
describe("parseBuildOptions", () => {
function assertParseResult(commandLine: string[], expectedParsedBuildCommand: ParsedBuildCommand) {
const parsed = parseBuildCommand(commandLine);
const parsedBuildOptions = JSON.stringify(parsed.buildOptions);
const expectedBuildOptions = JSON.stringify(expectedParsedBuildCommand.buildOptions);
assert.equal(parsedBuildOptions, expectedBuildOptions);
const parsedErrors = parsed.errors;
const expectedErrors = expectedParsedBuildCommand.errors;
assert.isTrue(parsedErrors.length === expectedErrors.length, `Expected error: ${JSON.stringify(expectedErrors)}. Actual error: ${JSON.stringify(parsedErrors)}.`);
for (let i = 0; i < parsedErrors.length; i++) {
const parsedError = parsedErrors[i];
const expectedError = expectedErrors[i];
assert.equal(parsedError.code, expectedError.code);
assert.equal(parsedError.category, expectedError.category);
assert.equal(parsedError.messageText, expectedError.messageText);
}
const parsedProjects = parsed.projects;
const expectedProjects = expectedParsedBuildCommand.projects;
assert.deepEqual(parsedProjects, expectedProjects, `Expected projects: [${JSON.stringify(expectedProjects)}]. Actual projects: [${JSON.stringify(parsedProjects)}].`);
}
it("parse build without any options ", () => {
// --lib es6 0.ts
assertParseResult([],
{
errors: [],
projects: ["."],
buildOptions: {}
});
});
it("Parse multiple options", () => {
// --lib es5,es2015.symbol.wellknown 0.ts
assertParseResult(["--verbose", "--force", "tests"],
{
errors: [],
projects: ["tests"],
buildOptions: { verbose: true, force: true }
});
});
it("Parse option with invalid option ", () => {
// --lib es5,invalidOption 0.ts
assertParseResult(["--verbose", "--invalidOption"],
{
errors: [{
messageText: "Unknown build option '--invalidOption'.",
category: Diagnostics.Unknown_build_option_0.category,
code: Diagnostics.Unknown_build_option_0.code,
file: undefined,
start: undefined,
length: undefined,
}],
projects: ["."],
buildOptions: { verbose: true }
});
});
it("Parse multiple flags with input projects at the end", () => {
// --lib es5,es2015.symbol.wellknown --target es5 0.ts
assertParseResult(["--force", "--verbose", "src", "tests"],
{
errors: [],
projects: ["src", "tests"],
buildOptions: { force: true, verbose: true }
});
});
it("Parse multiple flags with input projects in the middle", () => {
// --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown
assertParseResult(["--force", "src", "tests", "--verbose"],
{
errors: [],
projects: ["src", "tests"],
buildOptions: { force: true, verbose: true }
});
});
it("Parse multiple flags with input projects in the beginning", () => {
// --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown
assertParseResult(["src", "tests", "--force", "--verbose"],
{
errors: [],
projects: ["src", "tests"],
buildOptions: { force: true, verbose: true }
});
});
describe("Combining options that make no sense together", () => {
function verifyInvalidCombination(flag1: keyof BuildOptions, flag2: keyof BuildOptions) {
it(`--${flag1} and --${flag2} together is invalid`, () => {
// --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown
assertParseResult([`--${flag1}`, `--${flag2}`],
{
errors: [{
messageText: `Options '${flag1}' and '${flag2}' cannot be combined.`,
category: Diagnostics.Options_0_and_1_cannot_be_combined.category,
code: Diagnostics.Options_0_and_1_cannot_be_combined.code,
file: undefined,
start: undefined,
length: undefined,
}],
projects: ["."],
buildOptions: { [flag1]: true, [flag2]: true }
});
});
}
verifyInvalidCombination("clean", "force");
verifyInvalidCombination("clean", "verbose");
verifyInvalidCombination("clean", "watch");
verifyInvalidCombination("watch", "dry");
});
});
}
+33
View File
@@ -0,0 +1,33 @@
namespace ts {
describe("compilerCore", () => {
describe("equalOwnProperties", () => {
it("correctly equates objects", () => {
assert.isTrue(equalOwnProperties({}, {}));
assert.isTrue(equalOwnProperties({ a: 1 }, { a: 1 }));
assert.isTrue(equalOwnProperties({ a: 1, b: 2 }, { b: 2, a: 1 }));
});
it("correctly identifies unmatched objects", () => {
assert.isFalse(equalOwnProperties({}, { a: 1 }), "missing left property");
assert.isFalse(equalOwnProperties({ a: 1 }, {}), "missing right property");
assert.isFalse(equalOwnProperties({ a: 1 }, { a: 2 }), "differing property");
});
it("correctly identifies undefined vs hasOwnProperty", () => {
assert.isFalse(equalOwnProperties({}, { a: undefined }), "missing left property");
assert.isFalse(equalOwnProperties({ a: undefined }, {}), "missing right property");
});
it("truthiness", () => {
const trythyTest = (l: any, r: any) => !!l === !!r;
assert.isFalse(equalOwnProperties({}, { a: 1 }, trythyTest), "missing left truthy property");
assert.isFalse(equalOwnProperties({}, { a: 0 }, trythyTest), "missing left falsey property");
assert.isFalse(equalOwnProperties({ a: 1 }, {}, trythyTest), "missing right truthy property");
assert.isFalse(equalOwnProperties({ a: 0 }, {}, trythyTest), "missing right falsey property");
assert.isTrue(equalOwnProperties({ a: 1 }, { a: "foo" }, trythyTest), "valid equality");
});
it("all equal", () => {
assert.isFalse(equalOwnProperties({}, { a: 1 }, () => true), "missing left property");
assert.isFalse(equalOwnProperties({ a: 1 }, {}, () => true), "missing right property");
assert.isTrue(equalOwnProperties({ a: 1 }, { a: 2 }, () => true), "valid equality");
});
});
});
}
File diff suppressed because it is too large Load Diff
+7 -9
View File
@@ -20,15 +20,13 @@ namespace ts {
const program = createProgram(arrayFrom(fileMap.keys()), options, host);
program.emit(/*targetSourceFile*/ undefined, host.writeFile, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ false, customTransformers);
Harness.Baseline.runBaseline(`customTransforms/${name}.js`, () => {
let content = "";
for (const [file, text] of arrayFrom(outputs.entries())) {
if (content) content += "\n\n";
content += `// [${file}]\n`;
content += text;
}
return content;
});
let content = "";
for (const [file, text] of arrayFrom(outputs.entries())) {
if (content) content += "\n\n";
content += `// [${file}]\n`;
content += text;
}
Harness.Baseline.runBaseline(`customTransforms/${name}.js`, content);
});
}
+15 -17
View File
@@ -86,7 +86,7 @@ namespace ts {
placeOpenBraceOnNewLineForControlBlocks: false,
};
const notImplementedHost: LanguageServiceHost = {
export const notImplementedHost: LanguageServiceHost = {
getCompilationSettings: notImplemented,
getScriptFileNames: notImplemented,
getScriptVersion: notImplemented,
@@ -131,23 +131,21 @@ namespace ts {
const infos = refactor.extractSymbol.getAvailableActions(context)!;
const actions = find(infos, info => info.description === description.message)!.actions;
Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, () => {
const data: string[] = [];
data.push(`// ==ORIGINAL==`);
data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/"));
for (const action of actions) {
const { renameLocation, edits } = refactor.extractSymbol.getEditsForAction(context, action.name)!;
assert.lengthOf(edits, 1);
data.push(`// ==SCOPE::${action.description}==`);
const newText = textChanges.applyChanges(sourceFile.text, edits[0].textChanges);
const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation);
data.push(newTextWithRename);
const data: string[] = [];
data.push(`// ==ORIGINAL==`);
data.push(text.replace("[#|", "/*[#|*/").replace("|]", "/*|]*/"));
for (const action of actions) {
const { renameLocation, edits } = refactor.extractSymbol.getEditsForAction(context, action.name)!;
assert.lengthOf(edits, 1);
data.push(`// ==SCOPE::${action.description}==`);
const newText = textChanges.applyChanges(sourceFile.text, edits[0].textChanges);
const newTextWithRename = newText.slice(0, renameLocation) + "/*RENAME*/" + newText.slice(renameLocation);
data.push(newTextWithRename);
const diagProgram = makeProgram({ path, content: newText }, includeLib);
assert.isFalse(hasSyntacticDiagnostics(diagProgram));
}
return data.join(newLineCharacter);
});
const diagProgram = makeProgram({ path, content: newText }, includeLib);
assert.isFalse(hasSyntacticDiagnostics(diagProgram));
}
Harness.Baseline.runBaseline(`${baselineFolder}/${caption}${extension}`, data.join(newLineCharacter));
}
function makeProgram(f: {path: string, content: string }, includeLib?: boolean) {
@@ -7,7 +7,7 @@ namespace ts {
const outputFileName = `tsConfig/${name.replace(/[^a-z0-9\-. ]/ig, "")}/tsconfig.json`;
it(`Correct output for ${outputFileName}`, () => {
Harness.Baseline.runBaseline(outputFileName, () => initResult);
Harness.Baseline.runBaseline(outputFileName, initResult);
});
});
}
+2 -2
View File
@@ -7,7 +7,7 @@ namespace ts {
assert.isTrue(typeAndDiagnostics && typeAndDiagnostics.diagnostics.length === 0, "no errors issued");
Harness.Baseline.runBaseline("JSDocParsing/TypeExpressions.parsesCorrectly." + name + ".json",
() => Utils.sourceFileToJSON(typeAndDiagnostics!.jsDocTypeExpression.type));
Utils.sourceFileToJSON(typeAndDiagnostics!.jsDocTypeExpression.type));
});
}
@@ -95,7 +95,7 @@ namespace ts {
}
Harness.Baseline.runBaseline("JSDocParsing/DocComments.parsesCorrectly." + name + ".json",
() => JSON.stringify(comment.jsDoc,
JSON.stringify(comment.jsDoc,
(_, v) => v && v.pos !== undefined ? JSON.parse(Utils.sourceFileToJSON(v)) : v, 4));
});
}
+143 -9
View File
@@ -1,14 +1,21 @@
namespace ts {
export function checkResolvedModule(expected: ResolvedModuleFull | undefined, actual: ResolvedModuleFull): boolean {
if (!expected === !actual) {
if (expected) {
assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`);
assert.isTrue(expected.extension === actual.extension, `'ext': expected '${expected.extension}' to be equal to '${actual.extension}'`);
assert.isTrue(expected.isExternalLibraryImport === actual.isExternalLibraryImport, `'isExternalLibraryImport': expected '${expected.isExternalLibraryImport}' to be equal to '${actual.isExternalLibraryImport}'`);
export function checkResolvedModule(actual: ResolvedModuleFull | undefined, expected: ResolvedModuleFull | undefined): boolean {
if (!expected) {
if (actual) {
assert.fail(actual, expected, "expected resolved module to be undefined");
return false;
}
return true;
}
return false;
else if (!actual) {
assert.fail(actual, expected, "expected resolved module to be defined");
return false;
}
assert.isTrue(actual.resolvedFileName === expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`);
assert.isTrue(actual.extension === expected.extension, `'ext': expected '${actual.extension}' to be equal to '${expected.extension}'`);
assert.isTrue(actual.isExternalLibraryImport === expected.isExternalLibraryImport, `'isExternalLibraryImport': expected '${actual.isExternalLibraryImport}' to be equal to '${expected.isExternalLibraryImport}'`);
return true;
}
export function checkResolvedModuleWithFailedLookupLocations(actual: ResolvedModuleWithFailedLookupLocations, expectedResolvedModule: ResolvedModuleFull, expectedFailedLookupLocations: string[]): void {
@@ -194,6 +201,88 @@ namespace ts {
});
describe("Node module resolution - non-relative paths", () => {
it("computes correct commonPrefix for moduleName cache", () => {
const resolutionCache = createModuleResolutionCache("/", (f) => f);
let cache = resolutionCache.getOrCreateCacheForModuleName("a");
cache.set("/sub", {
resolvedModule: {
originalPath: undefined,
resolvedFileName: "/sub/node_modules/a/index.ts",
isExternalLibraryImport: true,
extension: Extension.Ts,
},
failedLookupLocations: [],
});
assert.isDefined(cache.get("/sub"));
assert.isUndefined(cache.get("/"));
cache = resolutionCache.getOrCreateCacheForModuleName("b");
cache.set("/sub/dir/foo", {
resolvedModule: {
originalPath: undefined,
resolvedFileName: "/sub/directory/node_modules/b/index.ts",
isExternalLibraryImport: true,
extension: Extension.Ts,
},
failedLookupLocations: [],
});
assert.isDefined(cache.get("/sub/dir/foo"));
assert.isDefined(cache.get("/sub/dir"));
assert.isDefined(cache.get("/sub"));
assert.isUndefined(cache.get("/"));
cache = resolutionCache.getOrCreateCacheForModuleName("c");
cache.set("/foo/bar", {
resolvedModule: {
originalPath: undefined,
resolvedFileName: "/bar/node_modules/c/index.ts",
isExternalLibraryImport: true,
extension: Extension.Ts,
},
failedLookupLocations: [],
});
assert.isDefined(cache.get("/foo/bar"));
assert.isDefined(cache.get("/foo"));
assert.isDefined(cache.get("/"));
cache = resolutionCache.getOrCreateCacheForModuleName("d");
cache.set("/foo", {
resolvedModule: {
originalPath: undefined,
resolvedFileName: "/foo/index.ts",
isExternalLibraryImport: true,
extension: Extension.Ts,
},
failedLookupLocations: [],
});
assert.isDefined(cache.get("/foo"));
assert.isUndefined(cache.get("/"));
cache = resolutionCache.getOrCreateCacheForModuleName("e");
cache.set("c:/foo", {
resolvedModule: {
originalPath: undefined,
resolvedFileName: "d:/bar/node_modules/e/index.ts",
isExternalLibraryImport: true,
extension: Extension.Ts,
},
failedLookupLocations: [],
});
assert.isDefined(cache.get("c:/foo"));
assert.isDefined(cache.get("c:/"));
assert.isUndefined(cache.get("d:/"));
cache = resolutionCache.getOrCreateCacheForModuleName("f");
cache.set("/foo/bar/baz", {
resolvedModule: undefined,
failedLookupLocations: [],
});
assert.isDefined(cache.get("/foo/bar/baz"));
assert.isDefined(cache.get("/foo/bar"));
assert.isDefined(cache.get("/foo"));
assert.isDefined(cache.get("/"));
});
it("load module as file - ts files not loaded", () => {
test(/*hasDirectoryExists*/ false);
test(/*hasDirectoryExists*/ true);
@@ -314,13 +403,58 @@ namespace ts {
function testPreserveSymlinks(preserveSymlinks: boolean) {
it(`preserveSymlinks: ${preserveSymlinks}`, () => {
const realFileName = "/linked/index.d.ts";
const symlinkFileName = "/app/node_modulex/linked/index.d.ts";
const host = createModuleResolutionHost(/*hasDirectoryExists*/ true, { name: realFileName, symlinks: [symlinkFileName] });
const symlinkFileName = "/app/node_modules/linked/index.d.ts";
const host = createModuleResolutionHost(
/*hasDirectoryExists*/ true,
{ name: realFileName, symlinks: [symlinkFileName] },
{ name: "/app/node_modules/linked/package.json", content: '{"version": "0.0.0", "main": "./index"}' },
);
const resolution = nodeModuleNameResolver("linked", "/app/app.ts", { preserveSymlinks }, host);
const resolvedFileName = preserveSymlinks ? symlinkFileName : realFileName;
checkResolvedModule(resolution.resolvedModule, createResolvedModule(resolvedFileName, /*isExternalLibraryImport*/ true));
});
}
it("uses originalPath for caching", () => {
const host = createModuleResolutionHost(
/*hasDirectoryExists*/ true,
{
name: "/modules/a.ts",
symlinks: ["/sub/node_modules/a/index.ts"],
},
{
name: "/sub/node_modules/a/package.json",
content: '{"version": "0.0.0", "main": "./index"}'
}
);
const compilerOptions: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs };
const cache = createModuleResolutionCache("/", (f) => f);
let resolution = resolveModuleName("a", "/sub/dir/foo.ts", compilerOptions, host, cache);
checkResolvedModule(resolution.resolvedModule, createResolvedModule("/modules/a.ts", /*isExternalLibraryImport*/ true));
resolution = resolveModuleName("a", "/sub/foo.ts", compilerOptions, host, cache);
checkResolvedModule(resolution.resolvedModule, createResolvedModule("/modules/a.ts", /*isExternalLibraryImport*/ true));
resolution = resolveModuleName("a", "/foo.ts", compilerOptions, host, cache);
assert.isUndefined(resolution.resolvedModule, "lookup in parent directory doesn't hit the cache");
});
it("preserves originalPath on cache hit", () => {
const host = createModuleResolutionHost(
/*hasDirectoryExists*/ true,
{ name: "/linked/index.d.ts", symlinks: ["/app/node_modules/linked/index.d.ts"] },
{ name: "/app/node_modules/linked/package.json", content: '{"version": "0.0.0", "main": "./index"}' },
);
const cache = createModuleResolutionCache("/", (f) => f);
const compilerOptions: CompilerOptions = { moduleResolution: ModuleResolutionKind.NodeJs };
checkResolution(resolveModuleName("linked", "/app/src/app.ts", compilerOptions, host, cache));
checkResolution(resolveModuleName("linked", "/app/lib/main.ts", compilerOptions, host, cache));
function checkResolution(resolution: ResolvedModuleWithFailedLookupLocations) {
checkResolvedModule(resolution.resolvedModule, createResolvedModule("/linked/index.d.ts", /*isExternalLibraryImport*/ true));
assert.strictEqual(resolution.resolvedModule!.originalPath, "/app/node_modules/linked/index.d.ts");
}
});
});
describe("Module resolution - relative imports", () => {
+7 -9
View File
@@ -745,15 +745,13 @@ export * from "lib";
assert.equal(changes.length, 1);
assert.equal(changes[0].fileName, testPath);
Harness.Baseline.runBaseline(baselinePath, () => {
const newText = textChanges.applyChanges(testContent, changes[0].textChanges);
return [
"// ==ORIGINAL==",
testContent,
"// ==ORGANIZED==",
newText,
].join(newLineCharacter);
});
const newText = textChanges.applyChanges(testContent, changes[0].textChanges);
Harness.Baseline.runBaseline(baselinePath, [
"// ==ORIGINAL==",
testContent,
"// ==ORGANIZED==",
newText,
].join(newLineCharacter));
}
function makeLanguageService(...files: TestFSWithWatch.File[]) {
+1 -1
View File
@@ -3,7 +3,7 @@ namespace ts {
function makePrintsCorrectly(prefix: string) {
return function printsCorrectly(name: string, options: PrinterOptions, printCallback: (printer: Printer) => string) {
it(name, () => {
Harness.Baseline.runBaseline(`printerApi/${prefix}.${name}.js`, () =>
Harness.Baseline.runBaseline(`printerApi/${prefix}.${name}.js`,
printCallback(createPrinter({ newLine: NewLineKind.CarriageReturnLineFeed, ...options })));
});
};
+1 -1
View File
@@ -10,7 +10,7 @@ describe("Public APIs", () => {
});
it("should be acknowledged when they change", () => {
Harness.Baseline.runBaseline(api, () => fileContent);
Harness.Baseline.runBaseline(api, fileContent);
});
it("should compile", () => {
@@ -177,15 +177,10 @@ namespace ts {
file.text = file.text.updateProgram(newProgramText);
}
function checkResolvedTypeDirective(expected: ResolvedTypeReferenceDirective, actual: ResolvedTypeReferenceDirective): boolean {
if (!expected === !actual) {
if (expected) {
assert.equal(expected.resolvedFileName, actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`);
assert.equal(expected.primary, actual.primary, `'primary': expected '${expected.primary}' to be equal to '${actual.primary}'`);
}
return true;
}
return false;
function checkResolvedTypeDirective(actual: ResolvedTypeReferenceDirective, expected: ResolvedTypeReferenceDirective) {
assert.equal(actual.resolvedFileName, expected.resolvedFileName, `'resolvedFileName': expected '${actual.resolvedFileName}' to be equal to '${expected.resolvedFileName}'`);
assert.equal(actual.primary, expected.primary, `'primary': expected '${actual.primary}' to be equal to '${expected.primary}'`);
return true;
}
function checkCache<T>(caption: string, program: Program, fileName: string, expectedContent: Map<T> | undefined, getCache: (f: SourceFile) => Map<T> | undefined, entryChecker: (expected: T, original: T) => boolean): void {
@@ -399,6 +394,30 @@ namespace ts {
assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a"), "'a' is not an unresolved module after re-use");
});
it("works with updated SourceFiles", () => {
// adapted repro from https://github.com/Microsoft/TypeScript/issues/26166
const files = [
{ name: "/a.ts", text: SourceText.New("", "", 'import * as a from "a";a;') },
{ name: "/types/zzz/index.d.ts", text: SourceText.New("", "", 'declare module "a" { }') },
];
const host = createTestCompilerHost(files, target);
const options: CompilerOptions = { target, typeRoots: ["/types"] };
const program1 = createProgram(["/a.ts"], options, host);
let sourceFile = program1.getSourceFile("/a.ts")!;
assert.isDefined(sourceFile, "'/a.ts' is included in the program");
sourceFile = updateSourceFile(sourceFile, "'use strict';" + sourceFile.text, { newLength: "'use strict';".length, span: { start: 0, length: 0 } });
assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are updated");
const updateHost: TestCompilerHost = {
...host,
getSourceFile(fileName) {
return fileName === sourceFile.fileName ? sourceFile : program1.getSourceFile(fileName);
}
};
const program2 = createProgram(["/a.ts"], options, updateHost, program1);
assert.isDefined(program2.getSourceFile("/a.ts")!.resolvedModules!.get("a"), "'a' is not an unresolved module after re-use");
assert.strictEqual(sourceFile.statements[2].getSourceFile(), sourceFile, "parent pointers are not altered");
});
it("resolved type directives cache follows type directives", () => {
const files = [
{ name: "/a.ts", text: SourceText.New("/// <reference types='typedefs'/>", "", "var x = $") },
+2
View File
@@ -68,6 +68,7 @@ namespace ts.projectSystem {
}, "/hunter2/foo.csproj");
// Also test that opening an external project only sends an event once.
et.service.closeClientFile(file1.path);
et.service.closeExternalProject(projectFileName);
checkNumberOfProjects(et.service, { externalProjects: 0 });
@@ -82,6 +83,7 @@ namespace ts.projectSystem {
projectFileName,
});
checkNumberOfProjects(et.service, { externalProjects: 1 });
et.service.openClientFile(file1.path); // Only on file open the project will be updated
}
});
+9 -11
View File
@@ -47,17 +47,15 @@ namespace ts {
function runSingleFileTest(caption: string, placeOpenBraceOnNewLineForFunctions: boolean, text: string, validateNodes: boolean, testBlock: (sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker) => void) {
it(caption, () => {
Harness.Baseline.runBaseline(`textChanges/${caption}.js`, () => {
const sourceFile = createSourceFile("source.ts", text, ScriptTarget.ES2015, /*setParentNodes*/ true);
const rulesProvider = getRuleProvider(placeOpenBraceOnNewLineForFunctions);
const changeTracker = new textChanges.ChangeTracker(newLineCharacter, rulesProvider);
testBlock(sourceFile, changeTracker);
const changes = changeTracker.getChanges(validateNodes ? verifyPositions : undefined);
assert.equal(changes.length, 1);
assert.equal(changes[0].fileName, sourceFile.fileName);
const modified = textChanges.applyChanges(sourceFile.text, changes[0].textChanges);
return `===ORIGINAL===${newLineCharacter}${text}${newLineCharacter}===MODIFIED===${newLineCharacter}${modified}`;
});
const sourceFile = createSourceFile("source.ts", text, ScriptTarget.ES2015, /*setParentNodes*/ true);
const rulesProvider = getRuleProvider(placeOpenBraceOnNewLineForFunctions);
const changeTracker = new textChanges.ChangeTracker(newLineCharacter, rulesProvider);
testBlock(sourceFile, changeTracker);
const changes = changeTracker.getChanges(validateNodes ? verifyPositions : undefined);
assert.equal(changes.length, 1);
assert.equal(changes[0].fileName, sourceFile.fileName);
const modified = textChanges.applyChanges(sourceFile.text, changes[0].textChanges);
Harness.Baseline.runBaseline(`textChanges/${caption}.js`, `===ORIGINAL===${newLineCharacter}${text}${newLineCharacter}===MODIFIED===${newLineCharacter}${modified}`);
});
}
+5 -4
View File
@@ -13,9 +13,9 @@ namespace ts.textStorage {
it("text based storage should be have exactly the same as script version cache", () => {
const host = projectSystem.createServerHost([f]);
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path));
const ts2 = new server.TextStorage(host, server.asNormalizedPath(f.path));
// Since script info is not used in these tests, just cheat by passing undefined
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!);
const ts2 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!);
ts1.useScriptVersionCache_TestOnly();
ts2.useText();
@@ -48,7 +48,8 @@ namespace ts.textStorage {
it("should switch to script version cache if necessary", () => {
const host = projectSystem.createServerHost([f]);
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path));
// Since script info is not used in these tests, just cheat by passing undefined
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path), /*initialVersion*/ undefined, /*info*/undefined!);
ts1.getSnapshot();
assert.isTrue(!ts1.hasScriptVersionCache_TestOnly(), "should not have script version cache - 1");
+1 -1
View File
@@ -53,7 +53,7 @@ namespace ts {
function testBaseline(testName: string, test: () => string) {
it(testName, () => {
Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${testName}.js`, test);
Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${testName}.js`, test());
});
}
+8 -32
View File
@@ -57,50 +57,26 @@ namespace ts {
});
it("Correct errors for " + justName, () => {
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".errors.txt"), () => {
if (transpileResult.diagnostics!.length === 0) {
/* tslint:disable:no-null-keyword */
return null;
/* tslint:enable:no-null-keyword */
}
return Harness.Compiler.getErrorBaseline(toBeCompiled, transpileResult.diagnostics!);
});
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".errors.txt"),
// tslint:disable-next-line no-null-keyword
transpileResult.diagnostics!.length === 0 ? null : Harness.Compiler.getErrorBaseline(toBeCompiled, transpileResult.diagnostics!));
});
if (canUseOldTranspile) {
it("Correct errors (old transpile) for " + justName, () => {
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.errors.txt"), () => {
if (oldTranspileDiagnostics.length === 0) {
/* tslint:disable:no-null-keyword */
return null;
/* tslint:enable:no-null-keyword */
}
return Harness.Compiler.getErrorBaseline(toBeCompiled, oldTranspileDiagnostics);
});
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.errors.txt"),
// tslint:disable-next-line no-null-keyword
oldTranspileDiagnostics.length === 0 ? null : Harness.Compiler.getErrorBaseline(toBeCompiled, oldTranspileDiagnostics));
});
}
it("Correct output for " + justName, () => {
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, Extension.Js), () => {
if (transpileResult.outputText) {
return transpileResult.outputText;
}
else {
// This can happen if compiler recieve invalid compiler-options
/* tslint:disable:no-null-keyword */
return null;
/* tslint:enable:no-null-keyword */
}
});
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, Extension.Js), transpileResult.outputText);
});
if (canUseOldTranspile) {
it("Correct output (old transpile) for " + justName, () => {
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.js"), () => {
return oldTranspileResult;
});
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.js"), oldTranspileResult);
});
}
});
+53 -97
View File
@@ -1,32 +1,22 @@
namespace ts {
let currentTime = 100;
let lastDiagnostics: Diagnostic[] = [];
const reportDiagnostic: DiagnosticReporter = diagnostic => lastDiagnostics.push(diagnostic);
const report = (message: DiagnosticMessage, ...args: string[]) => reportDiagnostic(createCompilerDiagnostic(message, ...args));
const buildHost: BuildHost = {
error: report,
verbose: report,
message: report,
errorDiagnostic: d => reportDiagnostic(d)
};
export namespace Sample1 {
tick();
const projFs = loadProjectFromDisk("tests/projects/sample1");
const allExpectedOutputs = ["/src/tests/index.js",
"/src/core/index.js", "/src/core/index.d.ts",
"/src/logic/index.js", "/src/logic/index.d.ts"];
"/src/core/index.js", "/src/core/index.d.ts", "/src/core/index.d.ts.map",
"/src/logic/index.js", "/src/logic/index.js.map", "/src/logic/index.d.ts"];
describe("tsbuild - sanity check of clean build of 'sample1' project", () => {
it("can build the sample project 'sample1' without error", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false });
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
clearDiagnostics();
host.clearDiagnostics();
builder.buildAllProjects();
assertDiagnosticMessages(/*empty*/);
host.assertDiagnosticMessages(/*empty*/);
// Check for outputs. Not an exhaustive list
for (const output of allExpectedOutputs) {
@@ -37,12 +27,11 @@ namespace ts {
describe("tsbuild - dry builds", () => {
it("doesn't write any files in a dry build", () => {
clearDiagnostics();
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: true, force: false, verbose: false });
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false });
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.A_non_dry_build_would_build_project_0, Diagnostics.A_non_dry_build_would_build_project_0, Diagnostics.A_non_dry_build_would_build_project_0);
host.assertDiagnosticMessages(Diagnostics.A_non_dry_build_would_build_project_0, Diagnostics.A_non_dry_build_would_build_project_0, Diagnostics.A_non_dry_build_would_build_project_0);
// Check for outputs to not be written. Not an exhaustive list
for (const output of allExpectedOutputs) {
@@ -51,28 +40,26 @@ namespace ts {
});
it("indicates that it would skip builds during a dry build", () => {
clearDiagnostics();
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const host = new fakes.SolutionBuilderHost(fs);
let builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false });
let builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
builder.buildAllProjects();
tick();
clearDiagnostics();
builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: true, force: false, verbose: false });
host.clearDiagnostics();
builder = createSolutionBuilder(host, ["/src/tests"], { dry: true, force: false, verbose: false });
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Project_0_is_up_to_date, Diagnostics.Project_0_is_up_to_date, Diagnostics.Project_0_is_up_to_date);
host.assertDiagnosticMessages(Diagnostics.Project_0_is_up_to_date, Diagnostics.Project_0_is_up_to_date, Diagnostics.Project_0_is_up_to_date);
});
});
describe("tsbuild - clean builds", () => {
it("removes all files it built", () => {
clearDiagnostics();
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false });
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
builder.buildAllProjects();
// Verify they exist
for (const output of allExpectedOutputs) {
@@ -91,9 +78,9 @@ namespace ts {
describe("tsbuild - force builds", () => {
it("always builds under --force", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: true, verbose: false });
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: true, verbose: false });
builder.buildAllProjects();
let currentTime = time();
checkOutputTimestamps(currentTime);
@@ -116,14 +103,14 @@ namespace ts {
describe("tsbuild - can detect when and what to rebuild", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: true });
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: true });
it("Builds the project", () => {
clearDiagnostics();
host.clearDiagnostics();
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
host.assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
Diagnostics.Building_project_0,
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
@@ -135,10 +122,10 @@ namespace ts {
// All three projects are up to date
it("Detects that all projects are up to date", () => {
clearDiagnostics();
host.clearDiagnostics();
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
host.assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2);
@@ -147,12 +134,12 @@ namespace ts {
// Update a file in the leaf node (tests), only it should rebuild the last one
it("Only builds the leaf node project", () => {
clearDiagnostics();
host.clearDiagnostics();
fs.writeFileSync("/src/tests/index.ts", "const m = 10;");
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
host.assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2,
Diagnostics.Project_0_is_up_to_date_because_newest_input_1_is_older_than_oldest_output_2,
Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2,
@@ -162,12 +149,12 @@ namespace ts {
// Update a file in the parent (without affecting types), should get fast downstream builds
it("Detects type-only changes in upstream projects", () => {
clearDiagnostics();
host.clearDiagnostics();
replaceText(fs, "/src/core/index.ts", "HELLO WORLD", "WELCOME PLANET");
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
host.assertDiagnosticMessages(Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_out_of_date_because_oldest_output_1_is_older_than_newest_input_2,
Diagnostics.Building_project_0,
Diagnostics.Project_0_is_up_to_date_with_d_ts_files_from_its_dependencies,
@@ -180,15 +167,13 @@ namespace ts {
describe("tsbuild - downstream-blocked compilations", () => {
it("won't build downstream projects if upstream projects have errors", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: true });
clearDiagnostics();
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: true });
// Induce an error in the middle project
replaceText(fs, "/src/logic/index.ts", "c.multiply(10, 15)", `c.muitply()`);
builder.buildAllProjects();
assertDiagnosticMessages(
host.assertDiagnosticMessages(
Diagnostics.Projects_in_this_build_Colon_0,
Diagnostics.Project_0_is_out_of_date_because_output_file_1_does_not_exist,
Diagnostics.Building_project_0,
@@ -204,12 +189,11 @@ namespace ts {
describe("tsbuild - project invalidation", () => {
it("invalidates projects correctly", () => {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/tests"], { dry: false, force: false, verbose: false });
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/tests"], { dry: false, force: false, verbose: false });
clearDiagnostics();
builder.buildAllProjects();
assertDiagnosticMessages(/*empty*/);
host.assertDiagnosticMessages(/*empty*/);
// Update a timestamp in the middle project
tick();
@@ -221,14 +205,14 @@ namespace ts {
// Rebuild this project
tick();
builder.invalidateProject("/src/logic");
builder.buildInvalidatedProjects();
builder.buildInvalidatedProject();
// The file should be updated
assert.equal(fs.statSync("/src/logic/index.js").mtimeMs, time(), "JS file should have been rebuilt");
assert.isBelow(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should *not* have been rebuilt");
// Build downstream projects should update 'tests', but not 'core'
tick();
builder.buildDependentInvalidatedProjects();
builder.buildInvalidatedProject();
assert.equal(fs.statSync("/src/tests/index.js").mtimeMs, time(), "Downstream JS file should have been rebuilt");
assert.isBelow(fs.statSync("/src/core/index.js").mtimeMs, time(), "Upstream JS file should not have been rebuilt");
});
@@ -240,11 +224,10 @@ namespace ts {
function verifyProjectWithResolveJsonModule(configFile: string, ...expectedDiagnosticMessages: DiagnosticMessage[]) {
const fs = projFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, [configFile], { dry: false, force: false, verbose: false });
clearDiagnostics();
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, [configFile], { dry: false, force: false, verbose: false });
builder.buildAllProjects();
assertDiagnosticMessages(...expectedDiagnosticMessages);
host.assertDiagnosticMessages(...expectedDiagnosticMessages);
if (!expectedDiagnosticMessages.length) {
// Check for outputs. Not an exhaustive list
for (const output of allExpectedOutputs) {
@@ -274,46 +257,43 @@ namespace ts {
let fs: vfs.FileSystem | undefined;
before(() => {
fs = outFileFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/third"], { dry: false, force: false, verbose: false });
clearDiagnostics();
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: false });
host.clearDiagnostics();
builder.buildAllProjects();
assertDiagnosticMessages(/*none*/);
host.assertDiagnosticMessages(/*none*/);
});
after(() => {
fs = undefined;
});
it(`Generates files matching the baseline`, () => {
Harness.Baseline.runBaseline("outfile-concat.js", () => {
const patch = fs!.diff();
// tslint:disable-next-line:no-null-keyword
return patch ? vfs.formatPatch(patch) : null;
});
const patch = fs!.diff();
// tslint:disable-next-line:no-null-keyword
Harness.Baseline.runBaseline("outfile-concat.js", patch ? vfs.formatPatch(patch) : null);
});
});
describe("tsbuild - downstream prepend projects always get rebuilt", () => {
it("", () => {
const fs = outFileFs.shadow();
const host = new fakes.CompilerHost(fs);
const builder = createSolutionBuilder(host, buildHost, ["/src/third"], { dry: false, force: false, verbose: false });
clearDiagnostics();
const host = new fakes.SolutionBuilderHost(fs);
const builder = createSolutionBuilder(host, ["/src/third"], { dry: false, force: false, verbose: false });
builder.buildAllProjects();
assertDiagnosticMessages(/*none*/);
host.assertDiagnosticMessages(/*none*/);
assert.equal(fs.statSync("src/third/thirdjs/output/third-output.js").mtimeMs, time(), "First build timestamp is correct");
tick();
replaceText(fs, "src/first/first_PART1.ts", "Hello", "Hola");
tick();
builder.resetBuildContext();
builder.buildAllProjects();
assertDiagnosticMessages(/*none*/);
host.assertDiagnosticMessages(/*none*/);
assert.equal(fs.statSync("src/third/thirdjs/output/third-output.js").mtimeMs, time(), "Second build timestamp is correct");
});
});
}
describe("tsbuild - graph-ordering", () => {
let host: fakes.CompilerHost | undefined;
let host: fakes.SolutionBuilderHost | undefined;
const deps: [string, string][] = [
["A", "B"],
["B", "C"],
@@ -326,7 +306,7 @@ namespace ts {
before(() => {
const fs = new vfs.FileSystem(false);
host = new fakes.CompilerHost(fs);
host = new fakes.SolutionBuilderHost(fs);
writeProjects(fs, ["A", "B", "C", "D", "E", "F", "G"], deps);
});
@@ -351,7 +331,7 @@ namespace ts {
});
function checkGraphOrdering(rootNames: string[], expectedBuildSet: string[]) {
const builder = createSolutionBuilder(host!, buildHost, rootNames, { dry: true, force: false, verbose: false });
const builder = createSolutionBuilder(host!, rootNames, { dry: true, force: false, verbose: false });
const projFileNames = rootNames.map(getProjectFileName);
const graph = builder.getBuildGraph(projFileNames);
@@ -406,30 +386,6 @@ namespace ts {
fs.writeFileSync(path, newContent, "utf-8");
}
function assertDiagnosticMessages(...expected: DiagnosticMessage[]) {
const actual = lastDiagnostics.slice();
if (actual.length !== expected.length) {
assert.fail<any>(actual, expected, `Diagnostic arrays did not match - got\r\n${actual.map(a => " " + a.messageText).join("\r\n")}\r\nexpected\r\n${expected.map(e => " " + e.message).join("\r\n")}`);
}
for (let i = 0; i < actual.length; i++) {
if (actual[i].code !== expected[i].code) {
assert.fail(actual[i].messageText, expected[i].message, `Mismatched error code - expected diagnostic ${i} "${actual[i].messageText}" to match ${expected[i].message}`);
}
}
}
function clearDiagnostics() {
lastDiagnostics = [];
}
export function printDiagnostics(header = "== Diagnostics ==") {
const out = createDiagnosticReporter(sys);
sys.write(header + "\r\n");
for (const d of lastDiagnostics) {
out(d);
}
}
function tick() {
currentTime += 60_000;
}
@@ -0,0 +1,154 @@
namespace ts.tscWatch {
export import libFile = TestFSWithWatch.libFile;
function createSolutionBuilder(system: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
const host = createSolutionBuilderWithWatchHost(system);
return ts.createSolutionBuilder(host, rootNames, defaultOptions || { dry: false, force: false, verbose: false, watch: true });
}
function createSolutionBuilderWithWatch(host: WatchedSystem, rootNames: ReadonlyArray<string>, defaultOptions?: BuildOptions) {
const solutionBuilder = createSolutionBuilder(host, rootNames, defaultOptions);
solutionBuilder.buildAllProjects();
solutionBuilder.startWatching();
return solutionBuilder;
}
describe("tsbuild-watch program updates", () => {
const projectsLocation = "/user/username/projects";
const project = "sample1";
const enum SubProject {
core = "core",
logic = "logic",
tests = "tests",
ui = "ui"
}
type ReadonlyFile = Readonly<File>;
/** [tsconfig, index] | [tsconfig, index, anotherModule, someDecl] */
type SubProjectFiles = [ReadonlyFile, ReadonlyFile] | [ReadonlyFile, ReadonlyFile, ReadonlyFile, ReadonlyFile];
const root = Harness.IO.getWorkspaceRoot();
function projectFilePath(subProject: SubProject, baseFileName: string) {
return `${projectsLocation}/${project}/${subProject}/${baseFileName.toLowerCase()}`;
}
function projectFile(subProject: SubProject, baseFileName: string): File {
return {
path: projectFilePath(subProject, baseFileName),
content: Harness.IO.readFile(`${root}/tests/projects/${project}/${subProject}/${baseFileName}`)!
};
}
function subProjectFiles(subProject: SubProject, anotherModuleAndSomeDecl?: true): SubProjectFiles {
const tsconfig = projectFile(subProject, "tsconfig.json");
const index = projectFile(subProject, "index.ts");
if (!anotherModuleAndSomeDecl) {
return [tsconfig, index];
}
const anotherModule = projectFile(SubProject.core, "anotherModule.ts");
const someDecl = projectFile(SubProject.core, "some_decl.ts");
return [tsconfig, index, anotherModule, someDecl];
}
function getOutputFileNames(subProject: SubProject, baseFileNameWithoutExtension: string) {
const file = projectFilePath(subProject, baseFileNameWithoutExtension);
return [`${file}.js`, `${file}.d.ts`];
}
type OutputFileStamp = [string, Date | undefined];
function getOutputStamps(host: WatchedSystem, subProject: SubProject, baseFileNameWithoutExtension: string): OutputFileStamp[] {
return getOutputFileNames(subProject, baseFileNameWithoutExtension).map(f => [f, host.getModifiedTime(f)] as OutputFileStamp);
}
function getOutputFileStamps(host: WatchedSystem): OutputFileStamp[] {
return [
...getOutputStamps(host, SubProject.core, "anotherModule"),
...getOutputStamps(host, SubProject.core, "index"),
...getOutputStamps(host, SubProject.logic, "index"),
...getOutputStamps(host, SubProject.tests, "index"),
];
}
function verifyChangedFiles(actualStamps: OutputFileStamp[], oldTimeStamps: OutputFileStamp[], changedFiles: string[]) {
for (let i = 0; i < oldTimeStamps.length; i++) {
const actual = actualStamps[i];
const old = oldTimeStamps[i];
if (contains(changedFiles, actual[0])) {
assert.isTrue((actual[1] || 0) > (old[1] || 0), `${actual[0]} expected to written`);
}
else {
assert.equal(actual[1], old[1], `${actual[0]} expected to not change`);
}
}
}
const core = subProjectFiles(SubProject.core, /*anotherModuleAndSomeDecl*/ true);
const logic = subProjectFiles(SubProject.logic);
const tests = subProjectFiles(SubProject.tests);
const ui = subProjectFiles(SubProject.ui);
const allFiles: ReadonlyArray<File> = [libFile, ...core, ...logic, ...tests, ...ui];
const testProjectExpectedWatchedFiles = [core[0], core[1], core[2], ...logic, ...tests].map(f => f.path);
function createSolutionInWatchMode() {
const host = createWatchedSystem(allFiles, { currentDirectory: projectsLocation });
createSolutionBuilderWithWatch(host, [`${project}/${SubProject.tests}`]);
checkWatchedFiles(host, testProjectExpectedWatchedFiles);
checkWatchedDirectories(host, emptyArray, /*recursive*/ false);
checkWatchedDirectories(host, emptyArray, /*recursive*/ true); // TODO: #26524
checkOutputErrorsInitial(host, emptyArray);
const outputFileStamps = getOutputFileStamps(host);
for (const stamp of outputFileStamps) {
assert.isDefined(stamp[1], `${stamp[0]} expected to be present`);
}
return host;
}
it("creates solution in watch mode", () => {
createSolutionInWatchMode();
});
it("change builds changes and reports found errors message", () => {
const host = createSolutionInWatchMode();
verifyChange(`${core[1].content}
export class someClass { }`);
// Another change requeues and builds it
verifyChange(core[1].content);
// Two changes together report only single time message: File change detected. Starting incremental compilation...
const outputFileStamps = getOutputFileStamps(host);
const change1 = `${core[1].content}
export class someClass { }`;
host.writeFile(core[1].path, change1);
host.writeFile(core[1].path, `${change1}
export class someClass2 { }`);
verifyChangeAfterTimeout(outputFileStamps);
function verifyChange(coreContent: string) {
const outputFileStamps = getOutputFileStamps(host);
host.writeFile(core[1].path, coreContent);
verifyChangeAfterTimeout(outputFileStamps);
}
function verifyChangeAfterTimeout(outputFileStamps: OutputFileStamp[]) {
host.checkTimeoutQueueLengthAndRun(1); // Builds core
const changedCore = getOutputFileStamps(host);
verifyChangedFiles(changedCore, outputFileStamps, [
...getOutputFileNames(SubProject.core, "anotherModule"), // This should not be written really
...getOutputFileNames(SubProject.core, "index")
]);
host.checkTimeoutQueueLengthAndRun(1); // Builds tests
const changedTests = getOutputFileStamps(host);
verifyChangedFiles(changedTests, changedCore, [
...getOutputFileNames(SubProject.tests, "index") // Again these need not be written
]);
host.checkTimeoutQueueLengthAndRun(1); // Builds logic
const changedLogic = getOutputFileStamps(host);
verifyChangedFiles(changedLogic, changedTests, [
...getOutputFileNames(SubProject.logic, "index") // Again these need not be written
]);
host.checkTimeoutQueueLength(0);
checkOutputErrorsIncremental(host, emptyArray);
}
});
// TODO: write tests reporting errors but that will have more involved work since file
});
}
+58 -17
View File
@@ -1,17 +1,16 @@
namespace ts.tscWatch {
import WatchedSystem = TestFSWithWatch.TestServerHost;
type File = TestFSWithWatch.File;
type SymLink = TestFSWithWatch.SymLink;
import createWatchedSystem = TestFSWithWatch.createWatchedSystem;
import checkArray = TestFSWithWatch.checkArray;
import libFile = TestFSWithWatch.libFile;
import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed;
import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
import checkOutputContains = TestFSWithWatch.checkOutputContains;
import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain;
import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory;
export import WatchedSystem = TestFSWithWatch.TestServerHost;
export type File = TestFSWithWatch.File;
export type SymLink = TestFSWithWatch.SymLink;
export import createWatchedSystem = TestFSWithWatch.createWatchedSystem;
export import checkArray = TestFSWithWatch.checkArray;
export import checkWatchedFiles = TestFSWithWatch.checkWatchedFiles;
export import checkWatchedFilesDetailed = TestFSWithWatch.checkWatchedFilesDetailed;
export import checkWatchedDirectories = TestFSWithWatch.checkWatchedDirectories;
export import checkWatchedDirectoriesDetailed = TestFSWithWatch.checkWatchedDirectoriesDetailed;
export import checkOutputContains = TestFSWithWatch.checkOutputContains;
export import checkOutputDoesNotContain = TestFSWithWatch.checkOutputDoesNotContain;
export import Tsc_WatchDirectory = TestFSWithWatch.Tsc_WatchDirectory;
export function checkProgramActualFiles(program: Program, expectedFiles: string[]) {
checkArray(`Program actual files`, program.getSourceFiles().map(file => file.fileName), expectedFiles);
@@ -111,7 +110,7 @@ namespace ts.tscWatch {
function assertWatchDiagnostic(diagnostic: Diagnostic) {
const expected = getWatchDiagnosticWithoutDate(diagnostic);
if (!disableConsoleClears && !contains(nonClearingMessageCodes, diagnostic.code)) {
if (!disableConsoleClears && contains(screenStartingMessageCodes, diagnostic.code)) {
assert.equal(host.screenClears[screenClears], index, `Expected screen clear at this diagnostic: ${expected}`);
screenClears++;
}
@@ -137,7 +136,7 @@ namespace ts.tscWatch {
: createCompilerDiagnostic(Diagnostics.Found_0_errors_Watching_for_file_changes, errors.length);
}
function checkOutputErrorsInitial(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>, disableConsoleClears?: boolean, logsBeforeErrors?: string[]) {
export function checkOutputErrorsInitial(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>, disableConsoleClears?: boolean, logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
/*logsBeforeWatchDiagnostic*/ undefined,
@@ -148,7 +147,7 @@ namespace ts.tscWatch {
createErrorsFoundCompilerDiagnostic(errors));
}
function checkOutputErrorsIncremental(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
export function checkOutputErrorsIncremental(host: WatchedSystem, errors: ReadonlyArray<Diagnostic>, disableConsoleClears?: boolean, logsBeforeWatchDiagnostic?: string[], logsBeforeErrors?: string[]) {
checkOutputErrors(
host,
logsBeforeWatchDiagnostic,
@@ -1141,7 +1140,6 @@ namespace ts.tscWatch {
}
it("without outDir or outFile is specified", () => {
debugger;
verifyWithOptions({ module: ModuleKind.AMD }, ["file1.js", "src/file2.js"]);
});
@@ -1313,6 +1311,49 @@ export class B
// File a need not be rewritten
assert.equal(host.getModifiedTime(`${currentDirectory}/a.js`), modifiedTimeOfAJs);
});
it("updates errors when strictNullChecks changes", () => {
const currentDirectory = "/user/username/projects/myproject";
const aFile: File = {
path: `${currentDirectory}/a.ts`,
content: `declare function foo(): null | { hello: any };
foo().hello`
};
const compilerOptions: CompilerOptions = {
};
const config: File = {
path: `${currentDirectory}/tsconfig.json`,
content: JSON.stringify({ compilerOptions })
};
const files = [aFile, config, libFile];
const host = createWatchedSystem(files, { currentDirectory });
const watch = createWatchOfConfigFile("tsconfig.json", host);
checkProgramActualFiles(watch(), [aFile.path, libFile.path]);
checkOutputErrorsInitial(host, emptyArray);
const modifiedTimeOfAJs = host.getModifiedTime(`${currentDirectory}/a.js`);
compilerOptions.strictNullChecks = true;
host.writeFile(config.path, JSON.stringify({ compilerOptions }));
host.runQueuedTimeoutCallbacks();
const expectedStrictNullErrors = [
getDiagnosticOfFileFromProgram(watch(), aFile.path, aFile.content.lastIndexOf("foo()"), 5, Diagnostics.Object_is_possibly_null)
];
checkOutputErrorsIncremental(host, expectedStrictNullErrors);
// File a need not be rewritten
assert.equal(host.getModifiedTime(`${currentDirectory}/a.js`), modifiedTimeOfAJs);
compilerOptions.strict = true;
delete (compilerOptions.strictNullChecks);
host.writeFile(config.path, JSON.stringify({ compilerOptions }));
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, expectedStrictNullErrors);
// File a need not be rewritten
assert.equal(host.getModifiedTime(`${currentDirectory}/a.js`), modifiedTimeOfAJs);
delete (compilerOptions.strict);
host.writeFile(config.path, JSON.stringify({ compilerOptions }));
host.runQueuedTimeoutCallbacks();
checkOutputErrorsIncremental(host, emptyArray);
// File a need not be rewritten
assert.equal(host.getModifiedTime(`${currentDirectory}/a.js`), modifiedTimeOfAJs);
});
});
describe("tsc-watch emit with outFile or out setting", () => {
File diff suppressed because it is too large Load Diff
@@ -430,7 +430,6 @@ namespace ts.projectSystem {
const p = projectService.externalProjects[0];
projectService.checkNumberOfProjects({ externalProjects: 1 });
checkProjectActualFiles(p, [jqueryJs.path]);
installer.checkPendingCommands(/*expectedCount*/ 0);
+59 -13
View File
@@ -13,7 +13,7 @@ namespace ts {
}
let reportDiagnostic = createDiagnosticReporter(sys);
function updateReportDiagnostic(options: CompilerOptions) {
function updateReportDiagnostic(options?: CompilerOptions) {
if (shouldBePretty(options)) {
reportDiagnostic = createDiagnosticReporter(sys, /*pretty*/ true);
}
@@ -23,8 +23,8 @@ namespace ts {
return !!sys.writeOutputIsTTY && sys.writeOutputIsTTY();
}
function shouldBePretty(options: CompilerOptions) {
if (typeof options.pretty === "undefined") {
function shouldBePretty(options?: CompilerOptions) {
if (!options || typeof options.pretty === "undefined") {
return defaultIsPretty();
}
return options.pretty;
@@ -54,15 +54,7 @@ namespace ts {
export function executeCommandLine(args: string[]): void {
if (args.length > 0 && ((args[0].toLowerCase() === "--build") || (args[0].toLowerCase() === "-b"))) {
const reportDiag = createDiagnosticReporter(sys, defaultIsPretty());
const report = (message: DiagnosticMessage, ...args: string[]) => reportDiag(createCompilerDiagnostic(message, ...args));
const buildHost: BuildHost = {
error: report,
verbose: report,
message: report,
errorDiagnostic: d => reportDiag(d)
};
const result = performBuild(args.slice(1), createCompilerHost({}), buildHost, sys);
const result = performBuild(args.slice(1));
// undefined = in watch mode, do not exit
if (result !== undefined) {
return sys.exit(result);
@@ -172,6 +164,60 @@ namespace ts {
}
}
function performBuild(args: string[]): number | undefined {
const { buildOptions, projects: buildProjects, errors } = parseBuildCommand(args);
if (errors.length > 0) {
errors.forEach(reportDiagnostic);
return ExitStatus.DiagnosticsPresent_OutputsSkipped;
}
if (buildOptions.help) {
printVersion();
printHelp(buildOpts, "--build ");
return ExitStatus.Success;
}
// Update to pretty if host supports it
updateReportDiagnostic();
const projects = mapDefined(buildProjects, project => {
const fileName = resolvePath(sys.getCurrentDirectory(), project);
const refPath = resolveProjectReferencePath(sys, { path: fileName });
if (!sys.fileExists(refPath)) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.File_0_does_not_exist, fileName));
return undefined;
}
return refPath;
});
if (projects.length === 0) {
printVersion();
printHelp(buildOpts, "--build ");
return ExitStatus.Success;
}
if (!sys.getModifiedTime || !sys.setModifiedTime || (buildOptions.clean && !sys.deleteFile)) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--build"));
return ExitStatus.DiagnosticsPresent_OutputsSkipped;
}
if (buildOptions.watch) {
reportWatchModeWithoutSysSupport();
}
// TODO: change this to host if watch => watchHost otherwiue without wathc
const builder = createSolutionBuilder(createSolutionBuilderWithWatchHost(sys, reportDiagnostic, createBuilderStatusReporter(sys, shouldBePretty()), createWatchStatusReporter()), projects, buildOptions);
if (buildOptions.clean) {
return builder.cleanAllProjects();
}
if (buildOptions.watch) {
builder.buildAllProjects();
builder.startWatching();
return undefined;
}
return builder.buildAllProjects();
}
function performCompilation(rootNames: string[], projectReferences: ReadonlyArray<ProjectReference> | undefined, options: CompilerOptions, configFileParsingDiagnostics?: ReadonlyArray<Diagnostic>) {
const host = createCompilerHost(options);
enableStatistics(options);
@@ -205,7 +251,7 @@ namespace ts {
};
}
function createWatchStatusReporter(options: CompilerOptions) {
function createWatchStatusReporter(options?: CompilerOptions) {
return ts.createWatchStatusReporter(sys, shouldBePretty(options));
}
+1
View File
@@ -3,6 +3,7 @@
"pretty": true,
"lib": ["es2015"],
"target": "es5",
"rootDir": ".",
"declaration": true,
"declarationMap": true,
+6 -2
View File
@@ -644,14 +644,18 @@ namespace ts.server {
const cmdLineVerbosity = getLogLevel(findArgument("--logVerbosity"));
const envLogOptions = parseLoggingEnvironmentString(process.env.TSS_LOG);
const logFileName = cmdLineLogFileName
const unsubstitutedLogFileName = cmdLineLogFileName
? stripQuotes(cmdLineLogFileName)
: envLogOptions.logToFile
? envLogOptions.file || (__dirname + "/.log" + process.pid.toString())
: undefined;
const substitutedLogFileName = unsubstitutedLogFileName
? unsubstitutedLogFileName.replace("PID", process.pid.toString())
: undefined;
const logVerbosity = cmdLineVerbosity || envLogOptions.detailLevel;
return new Logger(logFileName!, envLogOptions.traceToConsole!, logVerbosity!); // TODO: GH#18217
return new Logger(substitutedLogFileName!, envLogOptions.traceToConsole!, logVerbosity!); // TODO: GH#18217
}
// This places log file in the directory containing editorServices.js
// TODO: check that this location is writable