Merge branch 'main' into release-5.1

This commit is contained in:
Daniel Rosenwasser
2023-05-14 20:33:24 +00:00
1227 changed files with 89501 additions and 10857 deletions
+3 -5
View File
@@ -43,10 +43,8 @@ Please keep and fill in the line that best applies:
### ⏯ Playground Link
<!--
A link to a TypeScript Playground "Share" link which shows this behavior
The TypeScript Workbench can be used for more complex setups, try
https://www.typescriptlang.org/dev/bug-workbench/
A link to a TypeScript Playground "Share" link which shows this behavior.
This should have the same code as the code snippet below, and use whichever settings are relevant to your report.
As a last resort, you can link to a repo, but these will be slower for us to investigate.
-->
@@ -54,7 +52,7 @@ Please keep and fill in the line that best applies:
### 💻 Code
<!-- Please post the relevant code sample here as well-->
<!-- Please post the relevant code sample here as well. This code and the Playground code should be the same, do not use separate examples -->
```ts
// We can quickly address your report if:
// - The code sample is short. Nearly all TypeScript bugs can be demonstrated in 20-30 lines of code!
+1
View File
@@ -12,3 +12,4 @@ gabritto
jakebailey
DanielRosenwasser
navya9singh
iisaduan
+28 -1
View File
@@ -20,7 +20,7 @@ jobs:
strategy:
matrix:
node-version:
- "19"
- "20"
- "18"
- "16"
- "14"
@@ -202,3 +202,30 @@ jobs:
- name: Self build
run: npx hereby build-src --built
unused-baselines:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: "*"
check-latest: true
- run: npm ci
- name: Remove all baselines
run: rm -rf tests/baselines/reference
- name: Run tests
run: npm test &> /dev/null || exit 0
- name: Accept baselines
run: npx hereby baseline-accept
- name: Check for unused baselines
run: |
if ! git diff --exit-code --quiet; then
echo "Unused baselines:"
git diff --exit-code --name-only
fi
-1
View File
@@ -616,7 +616,6 @@ export const runTestsAndWatch = task({
});
process.on("SIGINT", endWatchMode);
process.on("SIGKILL", endWatchMode);
process.on("beforeExit", endWatchMode);
watchTestsEmitter.on("rebuild", onRebuild);
testCaseWatcher.on("all", onChange);
+416 -410
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -108,9 +108,9 @@
"source-map-support": false,
"inspector": false
},
"packageManager": "npm@8.19.3",
"packageManager": "npm@8.19.4",
"volta": {
"node": "14.21.1",
"npm": "8.19.3"
"node": "20.1.0",
"npm": "8.19.4"
}
}
+2 -2
View File
@@ -89,7 +89,7 @@ import {
getEnclosingBlockScopeContainer,
getErrorSpanForNode,
getEscapedTextOfIdentifierOrLiteral,
getEscapedTextOfJsxAttributeName,
getEscapedTextOfJsxNamespacedName,
getExpandoInitializer,
getHostSignatureFromJSDoc,
getImmediatelyInvokedFunctionExpression,
@@ -682,7 +682,7 @@ function createBinder(): (file: SourceFile, options: CompilerOptions) => void {
return getSymbolNameForPrivateIdentifier(containingClassSymbol, name.escapedText);
}
if (isJsxNamespacedName(name)) {
return getEscapedTextOfJsxAttributeName(name);
return getEscapedTextOfJsxNamespacedName(name);
}
return isPropertyNameLiteral(name) ? getEscapedTextOfIdentifierOrLiteral(name) : undefined;
}
+91 -4
View File
@@ -22,6 +22,7 @@ import {
convertToOptionsWithAbsolutePaths,
createBuildInfo,
createGetCanonicalFileName,
createModuleNotFoundChain,
createProgram,
CustomTransformers,
Debug,
@@ -68,8 +69,11 @@ import {
ProjectReference,
ReadBuildProgramHost,
ReadonlyCollection,
RepopulateDiagnosticChainInfo,
RepopulateModuleNotFoundDiagnosticChain,
returnFalse,
returnUndefined,
sameMap,
SemanticDiagnosticsBuilderProgram,
skipTypeChecking,
some,
@@ -103,7 +107,18 @@ export interface ReusableDiagnosticRelatedInformation {
}
/** @internal */
export type ReusableDiagnosticMessageChain = DiagnosticMessageChain;
export interface ReusableRepopulateModuleNotFoundChain {
info: RepopulateModuleNotFoundDiagnosticChain;
next?: ReusableDiagnosticMessageChain[];
}
/** @internal */
export type SerializedDiagnosticMessageChain = Omit<DiagnosticMessageChain, "next" | "repopulateInfo"> & {
next?: ReusableDiagnosticMessageChain[];
};
/** @internal */
export type ReusableDiagnosticMessageChain = SerializedDiagnosticMessageChain | ReusableRepopulateModuleNotFoundChain;
/**
* Signature (Hash of d.ts emitted), is string if it was emitted using same d.ts.map option as what compilerOptions indicate, otherwise tuple of string
@@ -364,7 +379,12 @@ function createBuilderProgramState(newProgram: Program, oldState: Readonly<Reusa
// Unchanged file copy diagnostics
const diagnostics = oldState!.semanticDiagnosticsPerFile!.get(sourceFilePath);
if (diagnostics) {
state.semanticDiagnosticsPerFile!.set(sourceFilePath, oldState!.hasReusableDiagnostic ? convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram) : diagnostics as readonly Diagnostic[]);
state.semanticDiagnosticsPerFile!.set(
sourceFilePath,
oldState!.hasReusableDiagnostic ?
convertToDiagnostics(diagnostics as readonly ReusableDiagnostic[], newProgram) :
repopulateDiagnostics(diagnostics as readonly Diagnostic[], newProgram)
);
if (!state.semanticDiagnosticsFromOldState) {
state.semanticDiagnosticsFromOldState = new Set();
}
@@ -448,6 +468,43 @@ function getEmitSignatureFromOldSignature(options: CompilerOptions, oldOptions:
isString(oldEmitSignature) ? [oldEmitSignature] : oldEmitSignature[0];
}
function repopulateDiagnostics(diagnostics: readonly Diagnostic[], newProgram: Program): readonly Diagnostic[] {
if (!diagnostics.length) return diagnostics;
return sameMap(diagnostics, diag => {
if (isString(diag.messageText)) return diag;
const repopulatedChain = convertOrRepopulateDiagnosticMessageChain(diag.messageText, diag.file, newProgram, chain => chain.repopulateInfo?.());
return repopulatedChain === diag.messageText ?
diag :
{ ...diag, messageText: repopulatedChain };
});
}
function convertOrRepopulateDiagnosticMessageChain<T extends DiagnosticMessageChain | ReusableDiagnosticMessageChain>(
chain: T,
sourceFile: SourceFile | undefined,
newProgram: Program,
repopulateInfo: (chain: T) => RepopulateDiagnosticChainInfo | undefined,
): DiagnosticMessageChain {
const info = repopulateInfo(chain);
if (info) {
return {
...createModuleNotFoundChain(sourceFile!, newProgram, info.moduleReference, info.mode, info.packageName || info.moduleReference),
next: convertOrRepopulateDiagnosticMessageChainArray(chain.next as T[], sourceFile, newProgram, repopulateInfo),
};
}
const next = convertOrRepopulateDiagnosticMessageChainArray(chain.next as T[], sourceFile, newProgram, repopulateInfo);
return next === chain.next ? chain as DiagnosticMessageChain : { ...chain as DiagnosticMessageChain, next };
}
function convertOrRepopulateDiagnosticMessageChainArray<T extends DiagnosticMessageChain | ReusableDiagnosticMessageChain>(
array: T[] | undefined,
sourceFile: SourceFile | undefined,
newProgram: Program,
repopulateInfo: (chain: T) => RepopulateDiagnosticChainInfo | undefined,
): DiagnosticMessageChain[] | undefined {
return sameMap(array, chain => convertOrRepopulateDiagnosticMessageChain(chain, sourceFile, newProgram, repopulateInfo));
}
function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newProgram: Program): readonly Diagnostic[] {
if (!diagnostics.length) return emptyArray;
let buildInfoDirectory: string | undefined;
@@ -474,9 +531,13 @@ function convertToDiagnostics(diagnostics: readonly ReusableDiagnostic[], newPro
function convertToDiagnosticRelatedInformation(diagnostic: ReusableDiagnosticRelatedInformation, newProgram: Program, toPath: (path: string) => Path): DiagnosticRelatedInformation {
const { file } = diagnostic;
const sourceFile = file ? newProgram.getSourceFileByPath(toPath(file)) : undefined;
return {
...diagnostic,
file: file ? newProgram.getSourceFileByPath(toPath(file)) : undefined
file: sourceFile,
messageText: isString(diagnostic.messageText) ?
diagnostic.messageText :
convertOrRepopulateDiagnosticMessageChain(diagnostic.messageText, sourceFile, newProgram, chain => (chain as ReusableRepopulateModuleNotFoundChain).info),
};
}
@@ -1232,10 +1293,36 @@ function convertToReusableDiagnosticRelatedInformation(diagnostic: DiagnosticRel
const { file } = diagnostic;
return {
...diagnostic,
file: file ? relativeToBuildInfo(file.resolvedPath) : undefined
file: file ? relativeToBuildInfo(file.resolvedPath) : undefined,
messageText: isString(diagnostic.messageText) ? diagnostic.messageText : convertToReusableDiagnosticMessageChain(diagnostic.messageText),
};
}
function convertToReusableDiagnosticMessageChain(chain: DiagnosticMessageChain): ReusableDiagnosticMessageChain {
if (chain.repopulateInfo) {
return {
info: chain.repopulateInfo(),
next: convertToReusableDiagnosticMessageChainArray(chain.next),
};
}
const next = convertToReusableDiagnosticMessageChainArray(chain.next);
return next === chain.next ? chain : { ...chain, next };
}
function convertToReusableDiagnosticMessageChainArray(array: DiagnosticMessageChain[] | undefined): ReusableDiagnosticMessageChain[] | undefined {
if (!array) return array;
return forEach(array, (chain, index) => {
const reusable = convertToReusableDiagnosticMessageChain(chain);
if (chain === reusable) return undefined;
const result: ReusableDiagnosticMessageChain[] = index > 0 ? array.slice(0, index - 1) : [];
result.push(reusable);
for (let i = index + 1; i < array.length; i++) {
result.push(convertToReusableDiagnosticMessageChain(array[i]));
}
return result;
}) || array;
}
/** @internal */
export enum BuilderProgramKind {
SemanticDiagnosticsBuilderProgram,
+225 -135
View File
@@ -110,6 +110,7 @@ import {
createGetCanonicalFileName,
createGetSymbolWalker,
createModeAwareCacheKey,
createModuleNotFoundChain,
createMultiMap,
createPrinterWithDefaults,
createPrinterWithRemoveComments,
@@ -359,7 +360,6 @@ import {
getThisParameter,
getTrailingSemicolonDeferringWriter,
getTypeParameterFromJsDoc,
getTypesPackageName,
getUseDefineForClassFields,
group,
hasAbstractModifier,
@@ -776,6 +776,7 @@ import {
JSDocVariadicType,
JsxAttribute,
JsxAttributeLike,
JsxAttributeName,
JsxAttributes,
JsxChild,
JsxClosingElement,
@@ -805,7 +806,6 @@ import {
LiteralExpression,
LiteralType,
LiteralTypeNode,
mangleScopedPackageName,
map,
mapDefined,
MappedSymbol,
@@ -814,7 +814,6 @@ import {
MatchingKeys,
maybeBind,
MemberOverrideStatus,
memoize,
MetaProperty,
MethodDeclaration,
MethodSignature,
@@ -852,7 +851,6 @@ import {
nodeIsPresent,
nodeIsSynthesized,
NodeLinks,
nodeModulesPathPart,
nodeStartsNewLexicalEnvironment,
NodeWithTypeArguments,
NonNullChain,
@@ -1391,22 +1389,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Why var? It avoids TDZ checks in the runtime which can be costly.
// See: https://github.com/microsoft/TypeScript/issues/52924
/* eslint-disable no-var */
var getPackagesMap = memoize(() => {
// A package name maps to true when we detect it has .d.ts files.
// This is useful as an approximation of whether a package bundles its own types.
// Note: we only look at files already found by module resolution,
// so there may be files we did not consider.
var map = new Map<string, boolean>();
host.getSourceFiles().forEach(sf => {
if (!sf.resolvedModules) return;
sf.resolvedModules.forEach(({ resolvedModule }) => {
if (resolvedModule?.packageId) map.set(resolvedModule.packageId.name, resolvedModule.extension === Extension.Dts || !!map.get(resolvedModule.packageId.name));
});
});
return map;
});
var deferredDiagnosticsCallbacks: (() => void)[] = [];
var addLazyDiagnostic = (arg: () => void) => {
@@ -2036,7 +2018,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
getGlobalIterableType: getGlobalAsyncIterableType,
getGlobalIterableIteratorType: getGlobalAsyncIterableIteratorType,
getGlobalGeneratorType: getGlobalAsyncGeneratorType,
resolveIterationType: getAwaitedType,
resolveIterationType: (type, errorNode) => getAwaitedType(type, errorNode, Diagnostics.Type_of_await_operand_must_either_be_a_valid_promise_or_must_not_contain_a_callable_then_member),
mustHaveANextMethodDiagnostic: Diagnostics.An_async_iterator_must_have_a_next_method,
mustBeAMethodDiagnostic: Diagnostics.The_0_property_of_an_async_iterator_must_be_a_method,
mustHaveAValueDiagnostic: Diagnostics.The_type_returned_by_the_0_method_of_an_async_iterator_must_be_a_promise_for_a_type_with_a_value_property,
@@ -2392,13 +2374,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function isDeprecatedSymbol(symbol: Symbol) {
if (length(symbol.declarations) > 1) {
const parentSymbol = getParentOfSymbol(symbol);
if (parentSymbol && parentSymbol.flags & SymbolFlags.Interface) {
return some(symbol.declarations, d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated));
}
const parentSymbol = getParentOfSymbol(symbol);
if (parentSymbol && length(symbol.declarations) > 1) {
return parentSymbol.flags & SymbolFlags.Interface ? some(symbol.declarations, isDeprecatedDeclaration) : every(symbol.declarations, isDeprecatedDeclaration);
}
return !!(getDeclarationNodeFlagsFromSymbol(symbol) & NodeFlags.Deprecated);
return !!symbol.valueDeclaration && isDeprecatedDeclaration(symbol.valueDeclaration)
|| length(symbol.declarations) && every(symbol.declarations, isDeprecatedDeclaration);
}
function isDeprecatedDeclaration(declaration: Declaration) {
return !!(getCombinedNodeFlags(declaration) & NodeFlags.Deprecated);
}
function addDeprecatedSuggestion(location: Node, declarations: Node[], deprecatedEntity: string) {
@@ -2811,7 +2796,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return true;
}
if (isUsedInFunctionOrInstanceProperty(usage, declaration)) {
if (getEmitScriptTarget(compilerOptions) === ScriptTarget.ESNext && useDefineForClassFields
if (getEmitScriptTarget(compilerOptions) >= ScriptTarget.ES2022 && useDefineForClassFields
&& getContainingClass(declaration)
&& (isPropertyDeclaration(declaration) || isParameterPropertyDeclaration(declaration, declaration.parent))) {
return !isPropertyImmediatelyReferencedWithinDeclaration(declaration, usage, /*stopAtAnyPropertyDeclaration*/ true);
@@ -3037,6 +3022,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// (it refers to the constant type of the expression instead)
return undefined;
}
if (isModuleDeclaration(location) && lastLocation && location.name === lastLocation) {
// If this is the name of a namespace, skip the parent since it will have is own locals that could
// conflict.
lastLocation = location;
location = location.parent;
}
// Locals of a source file are not in scope (because they get merged into the global symbol table)
if (canHaveLocals(location) && location.locals && !isGlobalSourceFile(location)) {
if (result = lookup(location.locals, name, meaning)) {
@@ -3331,6 +3322,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
break;
case SyntaxKind.ExportSpecifier:
// External module export bindings shouldn't be resolved to local symbols.
if (lastLocation &&
lastLocation === (location as ExportSpecifier).propertyName &&
(location as ExportSpecifier).parent.parent.moduleSpecifier) {
location = location.parent.parent.parent;
}
break;
}
if (isSelfReferenceLocation(location)) {
lastSelfReferenceLocation = location;
@@ -3390,6 +3389,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (nameNotFoundMessage) {
addLazyDiagnostic(() => {
if (!errorLocation ||
errorLocation.parent.kind !== SyntaxKind.JSDocLink &&
!checkAndReportErrorForMissingPrefix(errorLocation, name, nameArg!) && // TODO: GH#18217
!checkAndReportErrorForInvalidInitializer() &&
!checkAndReportErrorForExtendingInterface(errorLocation) &&
@@ -5049,31 +5049,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function errorOnImplicitAnyModule(isError: boolean, errorNode: Node, sourceFile: SourceFile, mode: ResolutionMode, { packageId, resolvedFileName }: ResolvedModuleFull, moduleReference: string): void {
let errorInfo;
let errorInfo: DiagnosticMessageChain | undefined;
if (!isExternalModuleNameRelative(moduleReference) && packageId) {
const node10Result = sourceFile.resolvedModules?.get(moduleReference, mode)?.node10Result;
errorInfo = node10Result
? chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.There_are_types_at_0_but_this_result_could_not_be_resolved_when_respecting_package_json_exports_The_1_library_may_need_to_update_its_package_json_or_typings,
node10Result,
node10Result.indexOf(nodeModulesPathPart + "@types/") > -1 ? `@types/${mangleScopedPackageName(packageId.name)}` : packageId.name)
: typesPackageExists(packageId.name)
? chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1,
packageId.name, mangleScopedPackageName(packageId.name))
: packageBundlesTypes(packageId.name)
? chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1,
packageId.name,
moduleReference)
: chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
moduleReference,
mangleScopedPackageName(packageId.name));
errorInfo = createModuleNotFoundChain(sourceFile, host, moduleReference, mode, packageId.name);
}
errorOrSuggestion(isError, errorNode, chainDiagnosticMessages(
errorInfo,
@@ -5081,12 +5059,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
moduleReference,
resolvedFileName));
}
function typesPackageExists(packageName: string): boolean {
return getPackagesMap().has(getTypesPackageName(packageName));
}
function packageBundlesTypes(packageName: string): boolean {
return !!getPackagesMap().get(packageName);
}
function resolveExternalModuleSymbol(moduleSymbol: Symbol, dontResolveAlias?: boolean): Symbol;
function resolveExternalModuleSymbol(moduleSymbol: Symbol | undefined, dontResolveAlias?: boolean): Symbol | undefined;
@@ -8670,7 +8642,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
// Need to skip over export= symbols below - json source files get a single `Property` flagged
// symbol of name `export=` which needs to be handled like an alias. It's not great, but it is what it is.
if (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property)
if (symbol.flags & (SymbolFlags.BlockScopedVariable | SymbolFlags.FunctionScopedVariable | SymbolFlags.Property | SymbolFlags.Accessor)
&& symbol.escapedName !== InternalSymbolName.ExportEquals
&& !(symbol.flags & SymbolFlags.Prototype)
&& !(symbol.flags & SymbolFlags.Class)
@@ -9106,7 +9078,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
context.enclosingDeclaration = originalDecl || oldEnclosing;
const localParams = getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol);
const typeParamDecls = map(localParams, p => typeParameterToDeclaration(p, context));
const classType = getDeclaredTypeOfClassOrInterface(symbol);
const classType = getTypeWithThisArgument(getDeclaredTypeOfClassOrInterface(symbol)) as InterfaceType;
const baseTypes = getBaseTypes(classType);
const originalImplements = originalDecl && getEffectiveImplementsTypeNodes(originalDecl);
const implementsExpressions = originalImplements && sanitizeJSDocImplements(originalImplements)
@@ -10392,7 +10364,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// If the parent is a tuple type, the rest element has a tuple type of the
// remaining tuple element types. Otherwise, the rest element has an array type with same
// element type as the parent type.
const baseConstraint = getBaseConstraintOrType(parentType);
const baseConstraint = mapType(parentType, t => t.flags & TypeFlags.InstantiableNonPrimitive ? getBaseConstraintOrType(t) : t);
type = everyType(baseConstraint, isTupleType) ?
mapType(baseConstraint, t => sliceTupleType(t as TupleTypeReference, index)) :
createArrayType(elementType);
@@ -12539,6 +12511,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return needApparentType ? getApparentType(type) : type;
}
function getThisArgument(type: Type) {
return getObjectFlags(type) & ObjectFlags.Reference && length(getTypeArguments(type as TypeReference)) > getTypeReferenceArity(type as TypeReference) ? last(getTypeArguments(type as TypeReference)) : type;
}
function resolveObjectTypeMembers(type: ObjectType, source: InterfaceTypeWithDeclaredMembers, typeParameters: readonly TypeParameter[], typeArguments: readonly Type[]) {
let mapper: TypeMapper | undefined;
let members: SymbolTable;
@@ -12668,7 +12644,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return [sig.parameters];
function expandSignatureParametersWithTupleMembers(restType: TupleTypeReference, restIndex: number) {
const elementTypes = getTypeArguments(restType);
const elementTypes = getElementTypes(restType);
const associatedNames = getUniqAssociatedNamesFromTupleType(restType);
const restParams = map(elementTypes, (t, i) => {
// Lookup the label from the individual tuple passed in before falling back to the signature `rest` parameter name
@@ -13566,7 +13542,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
type.flags & TypeFlags.IndexedAccess && isConstTypeVariable((type as IndexedAccessType).objectType) ||
type.flags & TypeFlags.Conditional && isConstTypeVariable(getConstraintOfConditionalType(type as ConditionalType)) ||
type.flags & TypeFlags.Substitution && isConstTypeVariable((type as SubstitutionType).baseType) ||
isGenericTupleType(type) && findIndex(getTypeArguments(type), (t, i) => !!(type.target.elementFlags[i] & ElementFlags.Variadic) && isConstTypeVariable(t)) >= 0));
isGenericTupleType(type) && findIndex(getElementTypes(type), (t, i) => !!(type.target.elementFlags[i] & ElementFlags.Variadic) && isConstTypeVariable(t)) >= 0));
}
function getConstraintOfIndexedAccess(type: IndexedAccessType) {
@@ -13691,7 +13667,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function getBaseConstraintOfType(type: Type): Type | undefined {
if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping)) {
if (type.flags & (TypeFlags.InstantiableNonPrimitive | TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || isGenericTupleType(type)) {
const constraint = getResolvedBaseConstraint(type as InstantiableType | UnionOrIntersectionType);
return constraint !== noConstraintType && constraint !== circularConstraintType ? constraint : undefined;
}
@@ -13720,7 +13696,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return type.resolvedBaseConstraint;
}
const stack: object[] = [];
return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type);
return type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), getThisArgument(type));
function getImmediateBaseConstraint(t: Type): Type {
if (!t.immediateBaseConstraint) {
@@ -13822,6 +13798,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (t.flags & TypeFlags.Substitution) {
return getBaseConstraint(getSubstitutionIntersection(t as SubstitutionType));
}
if (isGenericTupleType(t)) {
// We substitute constraints for variadic elements only when the constraints are array types or
// non-variadic tuple types as we want to avoid further (possibly unbounded) recursion.
const newElements = map(getElementTypes(t), (v, i) => {
const constraint = t.target.elementFlags[i] & ElementFlags.Variadic && getBaseConstraint(v) || v;
return constraint && everyType(constraint, c => isArrayOrTupleType(c) && !isGenericTupleType(c)) ? constraint : v;
});
return createTupleType(newElements, t.target.elementFlags, t.target.readonly, t.target.labeledElementDeclarations);
}
return t;
}
}
@@ -14543,8 +14528,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
const classType = declaration.kind === SyntaxKind.Constructor ?
getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol))
const hostDeclaration = isJSDocSignature(declaration) ? getEffectiveJSDocHost(declaration) : declaration;
const classType = hostDeclaration && isConstructorDeclaration(hostDeclaration) ?
getDeclaredTypeOfClassOrInterface(getMergedSymbol((hostDeclaration.parent as ClassDeclaration).symbol))
: undefined;
const typeParameters = classType ? classType.localTypeParameters : getTypeParametersFromDeclaration(declaration);
if (hasRestParameter(declaration) || isInJSFile(declaration) && maybeAddJsSyntheticRestParameter(declaration, parameters)) {
@@ -14803,16 +14789,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (declaration.kind === SyntaxKind.Constructor) {
return getDeclaredTypeOfClassOrInterface(getMergedSymbol((declaration.parent as ClassDeclaration).symbol));
}
const typeNode = getEffectiveReturnTypeNode(declaration);
if (isJSDocSignature(declaration)) {
const root = getJSDocRoot(declaration);
if (root && isConstructorDeclaration(root.parent)) {
if (root && isConstructorDeclaration(root.parent) && !typeNode) {
return getDeclaredTypeOfClassOrInterface(getMergedSymbol((root.parent.parent as ClassDeclaration).symbol));
}
}
if (isJSDocConstructSignature(declaration)) {
return getTypeFromTypeNode((declaration.parameters[0] as ParameterDeclaration).type!); // TODO: GH#18217
}
const typeNode = getEffectiveReturnTypeNode(declaration);
if (typeNode) {
return getTypeFromTypeNode(typeNode);
}
@@ -16129,7 +16115,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
addElement(type, ElementFlags.Variadic, target.labeledElementDeclarations?.[i]);
}
else if (isTupleType(type)) {
const elements = getTypeArguments(type);
const elements = getElementTypes(type);
if (elements.length + expandedTypes.length >= 10_000) {
error(currentNode, isPartOfTypeNode(currentNode!)
? Diagnostics.Type_produces_a_tuple_type_that_is_too_large_to_represent
@@ -16211,6 +16197,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return type.elementFlags.length - findLastIndex(type.elementFlags, f => !(f & flags)) - 1;
}
function getElementTypes(type: TupleTypeReference): readonly Type[] {
const typeArguments = getTypeArguments(type);
const arity = getTypeReferenceArity(type);
return typeArguments.length === arity ? typeArguments : typeArguments.slice(0, arity);
}
function getTypeFromOptionalTypeNode(node: OptionalTypeNode): Type {
return addOptionality(getTypeFromTypeNode(node.type), /*isProperty*/ true);
}
@@ -16988,19 +16980,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
function getLiteralTypeFromPropertyName(name: PropertyName) {
function getLiteralTypeFromPropertyName(name: PropertyName | JsxAttributeName) {
if (isPrivateIdentifier(name)) {
return neverType;
}
return isIdentifier(name) ? getStringLiteralType(unescapeLeadingUnderscores(name.escapedText)) :
getRegularTypeOfLiteralType(isComputedPropertyName(name) ? checkComputedPropertyName(name) : checkExpression(name));
if (isNumericLiteral(name)) {
return getRegularTypeOfLiteralType(checkExpression(name));
}
if (isComputedPropertyName(name)) {
return getRegularTypeOfLiteralType(checkComputedPropertyName(name));
}
const propertyName = getPropertyNameForPropertyNameNode(name);
if (propertyName !== undefined) {
return getStringLiteralType(unescapeLeadingUnderscores(propertyName));
}
if (isExpression(name)) {
return getRegularTypeOfLiteralType(checkExpression(name));
}
return neverType;
}
function getLiteralTypeFromProperty(prop: Symbol, include: TypeFlags, includeNonPublic?: boolean) {
if (includeNonPublic || !(getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier)) {
let type = getSymbolLinks(getLateBoundSymbol(prop)).nameType;
if (!type) {
const name = getNameOfDeclaration(prop.valueDeclaration) as PropertyName;
const name = getNameOfDeclaration(prop.valueDeclaration) as PropertyName | JsxAttributeName;
type = prop.escapedName === InternalSymbolName.Default ? getStringLiteralType("default") :
name && getLiteralTypeFromPropertyName(name) || (!isKnownSymbol(prop) ? getStringLiteralType(symbolName(prop)) : undefined);
}
@@ -17761,7 +17765,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function isDeferredType(type: Type, checkTuples: boolean) {
return isGenericType(type) || checkTuples && isTupleType(type) && some(getTypeArguments(type), isGenericType);
return isGenericType(type) || checkTuples && isTupleType(type) && some(getElementTypes(type), isGenericType);
}
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
@@ -18902,7 +18906,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// M<[A, B?, ...T, ...C[]] into [...M<[A]>, ...M<[B?]>, ...M<T>, ...M<C[]>] and then rely on tuple type
// normalization to resolve the non-generic parts of the resulting tuple.
const elementFlags = tupleType.target.elementFlags;
const elementTypes = map(getTypeArguments(tupleType), (t, i) => {
const elementTypes = map(getElementTypes(tupleType), (t, i) => {
const singleton = elementFlags[i] & ElementFlags.Variadic ? t :
elementFlags[i] & ElementFlags.Rest ? createArrayType(t) :
createTupleType([t], [elementFlags[i]]);
@@ -18921,7 +18925,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function instantiateMappedTupleType(tupleType: TupleTypeReference, mappedType: MappedType, mapper: TypeMapper) {
const elementFlags = tupleType.target.elementFlags;
const elementTypes = map(getTypeArguments(tupleType), (_, i) =>
const elementTypes = map(getElementTypes(tupleType), (_, i) =>
instantiateMappedTypeTemplate(mappedType, getStringLiteralType("" + i), !!(elementFlags[i] & ElementFlags.Optional), mapper));
const modifiers = getMappedTypeModifiers(mappedType);
const newTupleModifiers = modifiers & MappedTypeModifiers.IncludeOptional ? map(elementFlags, f => f & ElementFlags.Required ? ElementFlags.Optional : f) :
@@ -20226,7 +20230,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function getNormalizedTupleType(type: TupleTypeReference, writing: boolean): Type {
const elements = getTypeArguments(type);
const elements = getElementTypes(type);
const normalizedElements = sameMap(elements, t => t.flags & TypeFlags.Simplifiable ? getSimplifiedType(t, writing) : t);
return elements !== normalizedElements ? createNormalizedTupleType(type.target, normalizedElements) : type;
}
@@ -20594,6 +20598,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* * Ternary.False if they are not related.
*/
function isRelatedTo(originalSource: Type, originalTarget: Type, recursionFlags: RecursionFlags = RecursionFlags.Both, reportErrors = false, headMessage?: DiagnosticMessage, intersectionState = IntersectionState.None): Ternary {
if (originalSource === originalTarget) return Ternary.True;
// Before normalization: if `source` is type an object type, and `target` is primitive,
// skip all the checks we don't need and just return `isSimpleTypeRelatedTo` result
if (originalSource.flags & TypeFlags.Object && originalTarget.flags & TypeFlags.Primitive) {
@@ -21782,6 +21788,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return Ternary.False;
}
}
else if (isGenericTupleType(source) && isTupleType(target) && !isGenericTupleType(target)) {
const constraint = getBaseConstraintOrType(source);
if (constraint !== source) {
return isRelatedTo(constraint, target, RecursionFlags.Source, reportErrors);
}
}
// A fresh empty object type is never a subtype of a non-empty object type. This ensures fresh({}) <: { [x: string]: xxx }
// but not vice-versa. Without this rule, those types would be mutual subtypes.
else if ((relation === subtypeRelation || relation === strictSubtypeRelation) && isEmptyObjectType(target) && getObjectFlags(target) & ObjectFlags.FreshLiteral && !isEmptyObjectType(source)) {
@@ -24063,7 +24075,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function isPartiallyInferableType(type: Type): boolean {
return !(getObjectFlags(type) & ObjectFlags.NonInferrableType) ||
isObjectLiteralType(type) && some(getPropertiesOfType(type), prop => isPartiallyInferableType(getTypeOfSymbol(prop))) ||
isTupleType(type) && some(getTypeArguments(type), isPartiallyInferableType);
isTupleType(type) && some(getElementTypes(type), isPartiallyInferableType);
}
function createReverseMappedType(source: Type, target: MappedType, constraint: IndexType) {
@@ -24078,7 +24090,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return createArrayType(inferReverseMappedType(getTypeArguments(source)[0], target, constraint), isReadonlyArrayType(source));
}
if (isTupleType(source)) {
const elementTypes = map(getTypeArguments(source), t => inferReverseMappedType(t, target, constraint));
const elementTypes = map(getElementTypes(source), t => inferReverseMappedType(t, target, constraint));
const elementFlags = getMappedTypeModifiers(target) & MappedTypeModifiers.IncludeOptional ?
sameMap(source.target.elementFlags, f => f & ElementFlags.Optional ? ElementFlags.Required : f) :
source.target.elementFlags;
@@ -25139,22 +25151,23 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const inference = context.inferences[index];
if (!inference.inferredType) {
let inferredType: Type | undefined;
const signature = context.signature;
if (signature) {
const inferredCovariantType = inference.candidates ? getCovariantInference(inference, signature) : undefined;
if (inference.contraCandidates) {
// If we have both co- and contra-variant inferences, we use the co-variant inference if it is not 'never',
// it is a subtype of some contra-variant inference, and no other type parameter is constrained to this type
// parameter and has inferences that would conflict. Otherwise, we use the contra-variant inference.
const useCovariantType = inferredCovariantType && !(inferredCovariantType.flags & TypeFlags.Never) &&
let fallbackType: Type | undefined;
if (context.signature) {
const inferredCovariantType = inference.candidates ? getCovariantInference(inference, context.signature) : undefined;
const inferredContravariantType = inference.contraCandidates ? getContravariantInference(inference) : undefined;
if (inferredCovariantType || inferredContravariantType) {
// If we have both co- and contra-variant inferences, we prefer the co-variant inference if it is not 'never',
// all co-variant inferences are subtypes of it (i.e. it isn't one of a conflicting set of candidates), it is
// a subtype of some contra-variant inference, and no other type parameter is constrained to this type parameter
// and has inferences that would conflict. Otherwise, we prefer the contra-variant inference.
const preferCovariantType = inferredCovariantType && (!inferredContravariantType ||
!(inferredCovariantType.flags & TypeFlags.Never) &&
some(inference.contraCandidates, t => isTypeSubtypeOf(inferredCovariantType, t)) &&
every(context.inferences, other =>
other !== inference && getConstraintOfTypeParameter(other.typeParameter) !== inference.typeParameter ||
every(other.candidates, t => isTypeSubtypeOf(t, inferredCovariantType)));
inferredType = useCovariantType ? inferredCovariantType : getContravariantInference(inference);
}
else if (inferredCovariantType) {
inferredType = inferredCovariantType;
every(other.candidates, t => isTypeSubtypeOf(t, inferredCovariantType))));
inferredType = preferCovariantType ? inferredCovariantType : inferredContravariantType;
fallbackType = preferCovariantType ? inferredContravariantType : inferredCovariantType;
}
else if (context.flags & InferenceFlags.NoDefault) {
// We use silentNeverType as the wildcard that signals no inferences.
@@ -25184,7 +25197,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (constraint) {
const instantiatedConstraint = instantiateType(constraint, context.nonFixingMapper);
if (!inferredType || !context.compareTypes(inferredType, getTypeWithThisArgument(instantiatedConstraint, inferredType))) {
inference.inferredType = inferredType = instantiatedConstraint;
// If the fallback type satisfies the constraint, we pick it. Otherwise, we pick the constraint.
inference.inferredType = fallbackType && context.compareTypes(fallbackType, getTypeWithThisArgument(instantiatedConstraint, fallbackType)) ? fallbackType : instantiatedConstraint;
}
}
}
@@ -27871,7 +27885,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
const localOrExportSymbol = getExportSymbolOfValueSymbolIfExported(symbol);
const targetSymbol = checkDeprecatedAliasedSymbol(localOrExportSymbol, node);
const targetSymbol = resolveAliasWithDeprecationCheck(localOrExportSymbol, node);
if (isDeprecatedSymbol(targetSymbol) && isUncalledFunctionReference(node, targetSymbol) && targetSymbol.declarations) {
addDeprecatedSuggestion(node, targetSymbol.declarations, node.escapedText as string);
}
@@ -29236,7 +29250,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function getContextualTypeForChildJsxExpression(node: JsxElement, child: JsxChild, contextFlags: ContextFlags | undefined) {
const attributesType = getApparentTypeOfContextualType(node.openingElement.tagName, contextFlags);
const attributesType = getApparentTypeOfContextualType(node.openingElement.attributes, contextFlags);
// JSX expression is in children of JSX Element, we will look for an "children" attribute (we get the name from JSX.ElementAttributesProperty)
const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node));
if (!(attributesType && !isTypeAny(attributesType) && jsxChildrenPropertyName && jsxChildrenPropertyName !== "")) {
@@ -29321,6 +29335,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function discriminateContextualTypeByJSXAttributes(node: JsxAttributes, contextualType: UnionType) {
const jsxChildrenPropertyName = getJsxElementChildrenPropertyName(getJsxNamespaceAt(node));
return discriminateTypeByDiscriminableItems(contextualType,
concatenate(
map(
@@ -29328,7 +29343,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
prop => ([!(prop as JsxAttribute).initializer ? (() => trueType) : (() => getContextFreeTypeOfExpression((prop as JsxAttribute).initializer!)), prop.symbol.escapedName] as [() => Type, __String])
),
map(
filter(getPropertiesOfType(contextualType), s => !!(s.flags & SymbolFlags.Optional) && !!node?.symbol?.members && !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName)),
filter(getPropertiesOfType(contextualType), s => {
if (!(s.flags & SymbolFlags.Optional) || !node?.symbol?.members) {
return false;
}
const element = node.parent.parent;
if (s.escapedName === jsxChildrenPropertyName && isJsxElement(element) && getSemanticJsxChildren(element.children).length) {
return false;
}
return !node.symbol.members.has(s.escapedName) && isDiscriminantProperty(contextualType, s.escapedName);
}),
s => [() => undefinedType, s.escapedName] as [() => Type, __String]
)
),
@@ -29620,18 +29644,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function getJsxManagedAttributesFromLocatedAttributes(context: JsxOpeningLikeElement, ns: Symbol, attributesType: Type) {
const managedSym = getJsxLibraryManagedAttributes(ns);
if (managedSym) {
const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); // fetches interface type, or initializes symbol links type parmaeters
const ctorType = getStaticTypeOfReferencedJsxConstructor(context);
if (managedSym.flags & SymbolFlags.TypeAlias) {
const params = getSymbolLinks(managedSym).typeParameters;
if (length(params) >= 2) {
const args = fillMissingTypeArguments([ctorType, attributesType], params, 2, isInJSFile(context));
return getTypeAliasInstantiation(managedSym, args);
}
}
if (length((declaredManagedType as GenericType).typeParameters) >= 2) {
const args = fillMissingTypeArguments([ctorType, attributesType], (declaredManagedType as GenericType).typeParameters, 2, isInJSFile(context));
return createTypeReference((declaredManagedType as GenericType), args);
const result = instantiateAliasOrInterfaceWithDefaults(managedSym, isInJSFile(context), ctorType, attributesType);
if (result) {
return result;
}
}
return attributesType;
@@ -30057,7 +30073,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return links.immediateTarget;
}
function checkObjectLiteral(node: ObjectLiteralExpression, checkMode?: CheckMode): Type {
function checkObjectLiteral(node: ObjectLiteralExpression, checkMode: CheckMode = CheckMode.Normal): Type {
const inDestructuringPattern = isAssignmentTarget(node);
// Grammar checking
checkGrammarObjectLiteralExpression(node, inDestructuringPattern);
@@ -30159,7 +30175,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
member = prop;
allPropertiesTable?.set(prop.escapedName, prop);
if (contextualType && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) &&
if (contextualType && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) &&
(memberDecl.kind === SyntaxKind.PropertyAssignment || memberDecl.kind === SyntaxKind.MethodDeclaration) && isContextSensitive(memberDecl)) {
const inferenceContext = getInferenceContext(node);
Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context
@@ -30179,7 +30195,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
hasComputedNumberProperty = false;
hasComputedSymbolProperty = false;
}
const type = getReducedType(checkExpression(memberDecl.expression));
const type = getReducedType(checkExpression(memberDecl.expression, checkMode & CheckMode.Inferential));
if (isValidSpreadType(type)) {
const mergedType = tryMergeUnionOfObjectTypeAndEmptyObject(type, inConstContext);
if (allPropertiesTable) {
@@ -30379,7 +30395,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
* @remarks Because this function calls getSpreadType, it needs to use the same checks as checkObjectLiteral,
* which also calls getSpreadType.
*/
function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode | undefined) {
function createJsxAttributesTypeFromAttributesProperty(openingLikeElement: JsxOpeningLikeElement, checkMode: CheckMode = CheckMode.Normal) {
const attributes = openingLikeElement.attributes;
const contextualType = getContextualType(attributes, ContextFlags.None);
const allAttributesTable = strictNullChecks ? createSymbolTable() : undefined;
@@ -30416,7 +30432,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
addDeprecatedSuggestion(attributeDecl.name, prop.declarations, attributeDecl.name.escapedText as string);
}
}
if (contextualType && checkMode && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(attributeDecl)) {
if (contextualType && checkMode & CheckMode.Inferential && !(checkMode & CheckMode.SkipContextSensitive) && isContextSensitive(attributeDecl)) {
const inferenceContext = getInferenceContext(attributes);
Debug.assert(inferenceContext); // In CheckMode.Inferential we should always have an inference context
const inferenceNode = (attributeDecl.initializer as JsxExpression).expression!;
@@ -30429,7 +30445,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
spread = getSpreadType(spread, createJsxAttributesType(), attributes.symbol, objectFlags, /*readonly*/ false);
attributesTable = createSymbolTable();
}
const exprType = getReducedType(checkExpressionCached(attributeDecl.expression, checkMode));
const exprType = getReducedType(checkExpression(attributeDecl.expression, checkMode & CheckMode.Inferential));
if (isTypeAny(exprType)) {
hasSpreadAnyType = true;
}
@@ -30690,6 +30706,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.LibraryManagedAttributes, SymbolFlags.Type);
}
function getJsxElementTypeSymbol(jsxNamespace: Symbol) {
// JSX.ElementType [symbol]
return jsxNamespace && getSymbol(jsxNamespace.exports!, JsxNames.ElementType, SymbolFlags.Type);
}
/// e.g. "props" for React.d.ts,
/// or 'undefined' if ElementAttributesProperty doesn't exist (which means all
/// non-intrinsic elements' attributes type is 'any'),
@@ -30826,11 +30847,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function getJsxElementTypeTypeAt(location: Node): Type | undefined {
const type = getJsxType(JsxNames.ElementType, location);
if (isErrorType(type)) return undefined;
const ns = getJsxNamespaceAt(location);
if (!ns) return undefined;
const sym = getJsxElementTypeSymbol(ns);
if (!sym) return undefined;
const type = instantiateAliasOrInterfaceWithDefaults(sym, isInJSFile(location));
if (!type || isErrorType(type)) return undefined;
return type;
}
function instantiateAliasOrInterfaceWithDefaults(managedSym: Symbol, inJs: boolean, ...typeArguments: Type[]) {
const declaredManagedType = getDeclaredTypeOfSymbol(managedSym); // fetches interface type, or initializes symbol links type parmaeters
if (managedSym.flags & SymbolFlags.TypeAlias) {
const params = getSymbolLinks(managedSym).typeParameters;
if (length(params) >= typeArguments.length) {
const args = fillMissingTypeArguments(typeArguments, params, typeArguments.length, inJs);
return length(args) === 0 ? declaredManagedType : getTypeAliasInstantiation(managedSym, args);
}
}
if (length((declaredManagedType as GenericType).typeParameters) >= typeArguments.length) {
const args = fillMissingTypeArguments(typeArguments, (declaredManagedType as GenericType).typeParameters, typeArguments.length, inJs);
return createTypeReference((declaredManagedType as GenericType), args);
}
return undefined;
}
/**
* Returns all the properties of the Jsx.IntrinsicElements interface
*/
@@ -31508,8 +31549,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}
else {
if (isDeprecatedSymbol(prop) && isUncalledFunctionReference(node, prop) && prop.declarations) {
addDeprecatedSuggestion(right, prop.declarations, right.escapedText as string);
const targetPropSymbol = resolveAliasWithDeprecationCheck(prop, right);
if (isDeprecatedSymbol(targetPropSymbol) && isUncalledFunctionReference(node, targetPropSymbol) && targetPropSymbol.declarations) {
addDeprecatedSuggestion(right, targetPropSymbol.declarations, right.escapedText as string);
}
checkPropertyNotUsedBeforeDeclaration(prop, node, right);
markPropertyAsReferenced(prop, node, isSelfTypeAccess(left, parentSymbol));
@@ -32441,7 +32483,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function getMutableArrayOrTupleType(type: Type) {
return type.flags & TypeFlags.Union ? mapType(type, getMutableArrayOrTupleType) :
type.flags & TypeFlags.Any || isMutableArrayOrTuple(getBaseConstraintOfType(type) || type) ? type :
isTupleType(type) ? createTupleType(getTypeArguments(type), type.target.elementFlags, /*readonly*/ false, type.target.labeledElementDeclarations) :
isTupleType(type) ? createTupleType(getElementTypes(type), type.target.elementFlags, /*readonly*/ false, type.target.labeledElementDeclarations) :
createTupleType([type], [ElementFlags.Variadic]);
}
@@ -32571,7 +32613,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (getJsxNamespaceContainerForImplicitImport(node)) {
return true; // factory is implicitly jsx/jsxdev - assume it fits the bill, since we don't strongly look for the jsx/jsxs/jsxDEV factory APIs anywhere else (at least not yet)
}
const tagType = isJsxOpeningElement(node) || isJsxSelfClosingElement(node) && !isJsxIntrinsicTagName(node.tagName) ? checkExpression(node.tagName) : undefined;
const tagType = (isJsxOpeningElement(node) || isJsxSelfClosingElement(node)) && !(isJsxIntrinsicTagName(node.tagName) || isJsxNamespacedName(node.tagName)) ? checkExpression(node.tagName) : undefined;
if (!tagType) {
return true;
}
@@ -32775,7 +32817,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// We can call checkExpressionCached because spread expressions never have a contextual type.
const spreadType = arg.kind === SyntaxKind.SpreadElement && (flowLoopCount ? checkExpression((arg as SpreadElement).expression) : checkExpressionCached((arg as SpreadElement).expression));
if (spreadType && isTupleType(spreadType)) {
forEach(getTypeArguments(spreadType), (t, i) => {
forEach(getElementTypes(spreadType), (t, i) => {
const flags = spreadType.target.elementFlags[i];
const syntheticArg = createSyntheticExpression(arg, flags & ElementFlags.Rest ? createArrayType(t) : t,
!!(flags & ElementFlags.Variable), spreadType.target.labeledElementDeclarations?.[i]);
@@ -34481,6 +34523,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
return getRegularTypeOfLiteralType(exprType);
}
const links = getNodeLinks(node);
links.assertionExpressionType = exprType;
checkSourceElement(type);
checkNodeDeferred(node);
return getTypeFromTypeNode(type);
@@ -34505,9 +34549,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
function checkAssertionDeferred(node: JSDocTypeAssertion | AssertionExpression) {
const { type, expression } = getAssertionTypeAndExpression(node);
const { type } = getAssertionTypeAndExpression(node);
const errNode = isParenthesizedExpression(node) ? type : node;
const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(checkExpression(expression)));
const links = getNodeLinks(node);
Debug.assertIsDefined(links.assertionExpressionType);
const exprType = getRegularTypeOfObjectLiteral(getBaseTypeOfLiteralType(links.assertionExpressionType));
const targetType = getTypeFromTypeNode(type);
if (!isErrorType(targetType)) {
addLazyDiagnostic(() => {
@@ -35673,6 +35719,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
forEachReturnStatement(func.body as Block, returnStatement => {
const expr = returnStatement.expression;
if (expr) {
// Bare calls to this same function don't contribute to inference
if (expr.kind === SyntaxKind.CallExpression &&
(expr as CallExpression).expression.kind === SyntaxKind.Identifier &&
checkExpressionCached((expr as CallExpression).expression).symbol === func.symbol) {
hasReturnOfTypeNever = true;
return;
}
let type = checkExpressionCached(expr, checkMode && checkMode & ~CheckMode.SkipGenericFunctions);
if (functionFlags & FunctionFlags.Async) {
// From within an async function you can return either a non-promise value or a promise. Any
@@ -37386,7 +37440,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function padTupleType(type: TupleTypeReference, pattern: ArrayBindingPattern) {
const patternElements = pattern.elements;
const elementTypes = getTypeArguments(type).slice();
const elementTypes = getElementTypes(type).slice();
const elementFlags = type.target.elementFlags.slice();
for (let i = getTypeReferenceArity(type); i < patternElements.length; i++) {
const e = patternElements[i];
@@ -44122,20 +44176,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
if (isImportSpecifier(node)) {
const targetSymbol = checkDeprecatedAliasedSymbol(symbol, node);
if (isDeprecatedAliasedSymbol(targetSymbol) && targetSymbol.declarations) {
const targetSymbol = resolveAliasWithDeprecationCheck(symbol, node);
if (isDeprecatedSymbol(targetSymbol) && targetSymbol.declarations) {
addDeprecatedSuggestion(node, targetSymbol.declarations, targetSymbol.escapedName as string);
}
}
}
}
function isDeprecatedAliasedSymbol(symbol: Symbol) {
return !!symbol.declarations && every(symbol.declarations, d => !!(getCombinedNodeFlags(d) & NodeFlags.Deprecated));
}
function checkDeprecatedAliasedSymbol(symbol: Symbol, location: Node) {
if (!(symbol.flags & SymbolFlags.Alias)) return symbol;
function resolveAliasWithDeprecationCheck(symbol: Symbol, location: Node) {
if (!(symbol.flags & SymbolFlags.Alias) || isDeprecatedSymbol(symbol) || !getDeclarationOfAliasSymbol(symbol)) {
return symbol;
}
const targetSymbol = resolveAlias(symbol);
if (targetSymbol === unknownSymbol) return targetSymbol;
@@ -44145,7 +44197,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
if (target) {
if (target === targetSymbol) break;
if (target.declarations && length(target.declarations)) {
if (isDeprecatedAliasedSymbol(target)) {
if (isDeprecatedSymbol(target)) {
addDeprecatedSuggestion(location, target.declarations, target.escapedName as string);
break;
}
@@ -45445,11 +45497,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
const symbol = getIntrinsicTagSymbol(name.parent as JsxOpeningLikeElement);
return symbol === unknownSymbol ? undefined : symbol;
}
const result = resolveEntityName(name, meaning, /*ignoreErrors*/ false, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name));
const result = resolveEntityName(name, meaning, /*ignoreErrors*/ true, /*dontResolveAlias*/ true, getHostSignatureFromJSDoc(name));
if (!result && isJSDoc) {
const container = findAncestor(name, or(isClassLike, isInterfaceDeclaration));
if (container) {
return resolveJSDocMemberName(name, /*ignoreErrors*/ false, getSymbolOfDeclaration(container));
return resolveJSDocMemberName(name, /*ignoreErrors*/ true, getSymbolOfDeclaration(container));
}
}
if (result && isJSDoc) {
@@ -46473,6 +46525,43 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return undefined;
}
function getReferencedValueDeclarations(referenceIn: Identifier): Declaration[] | undefined {
if (!isGeneratedIdentifier(referenceIn)) {
const reference = getParseTreeNode(referenceIn, isIdentifier);
if (reference) {
const symbol = getReferencedValueSymbol(reference);
if (symbol) {
return filter(getExportSymbolOfValueSymbolIfExported(symbol).declarations, declaration => {
switch (declaration.kind) {
case SyntaxKind.VariableDeclaration:
case SyntaxKind.Parameter:
case SyntaxKind.BindingElement:
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertyAssignment:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.EnumMember:
case SyntaxKind.ObjectLiteralExpression:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.ModuleDeclaration:
return true;
}
return false;
});
}
}
}
return undefined;
}
function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean {
if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) {
return isFreshLiteralType(getTypeOfSymbol(getSymbolOfDeclaration(node)));
@@ -46580,6 +46669,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
},
collectLinkedAliases,
getReferencedValueDeclaration,
getReferencedValueDeclarations,
getTypeReferenceSerializationKind,
isOptionalParameter,
moduleExportsSomeValue,
+8 -8
View File
@@ -364,21 +364,21 @@ export function *mapIterator<T, U>(iter: Iterable<T>, mapFn: (x: T) => U) {
* Maps from T to T and avoids allocation if all elements map to themselves
*
* @internal */
export function sameMap<T>(array: T[], f: (x: T, i: number) => T): T[];
export function sameMap<T, U = T>(array: T[], f: (x: T, i: number) => U): U[];
/** @internal */
export function sameMap<T>(array: readonly T[], f: (x: T, i: number) => T): readonly T[];
export function sameMap<T, U = T>(array: readonly T[], f: (x: T, i: number) => U): readonly U[];
/** @internal */
export function sameMap<T>(array: T[] | undefined, f: (x: T, i: number) => T): T[] | undefined;
export function sameMap<T, U = T>(array: T[] | undefined, f: (x: T, i: number) => U): U[] | undefined;
/** @internal */
export function sameMap<T>(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined;
export function sameMap<T, U = T>(array: readonly T[] | undefined, f: (x: T, i: number) => U): readonly U[] | undefined;
/** @internal */
export function sameMap<T>(array: readonly T[] | undefined, f: (x: T, i: number) => T): readonly T[] | undefined {
export function sameMap<T, U = T>(array: readonly T[] | undefined, f: (x: T, i: number) => U): readonly U[] | undefined {
if (array) {
for (let i = 0; i < array.length; i++) {
const item = array[i];
const mapped = f(item, i);
if (item !== mapped) {
const result = array.slice(0, i);
if (item as unknown !== mapped) {
const result: U[] = array.slice(0, i) as unknown[] as U[];
result.push(mapped);
for (i++; i < array.length; i++) {
result.push(f(array[i], i));
@@ -387,7 +387,7 @@ export function sameMap<T>(array: readonly T[] | undefined, f: (x: T, i: number)
}
}
}
return array;
return array as unknown[] as U[];
}
/**
+12
View File
@@ -4917,6 +4917,10 @@
"category": "Message",
"code": 6163
},
"Skipping module '{0}' that looks like an absolute URI, target file types: {1}.": {
"category": "Message",
"code": 6164
},
"Do not truncate error messages.": {
"category": "Message",
"code": 6165
@@ -6592,6 +6596,10 @@
"category": "Error",
"code": 8038
},
"A JSDoc '@template' tag may not follow a '@typedef', '@callback', or '@overload' tag": {
"category": "Error",
"code": 8039
},
"Declaration emit for this file requires using private name '{0}'. An explicit type annotation may unblock declaration emit.": {
"category": "Error",
@@ -7600,6 +7608,10 @@
"category": "Message",
"code": 95177
},
"Move to file": {
"category": "Message",
"code": 95178
},
"No value exists in scope for the shorthand property '{0}'. Either declare one or provide an initializer.": {
"category": "Error",
+16 -10
View File
@@ -179,6 +179,7 @@ import {
getSyntheticLeadingComments,
getSyntheticTrailingComments,
getTextOfJSDocComment,
getTextOfJsxNamespacedName,
getTrailingCommentRanges,
getTrailingSemicolonDeferringWriter,
getTransformers,
@@ -228,6 +229,7 @@ import {
isJSDocLikeText,
isJsonSourceFile,
isJsxClosingElement,
isJsxNamespacedName,
isJsxOpeningElement,
isKeyword,
isLet,
@@ -1155,6 +1157,7 @@ export const notImplementedResolver: EmitResolver = {
// Returns the constant value this property access resolves to: notImplemented, or 'undefined' for a non-constant
getConstantValue: notImplemented,
getReferencedValueDeclaration: notImplemented,
getReferencedValueDeclarations: notImplemented,
getTypeReferenceSerializationKind: notImplemented,
isOptionalParameter: notImplemented,
moduleExportsSomeValue: notImplemented,
@@ -2065,6 +2068,8 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
return emitJsxSpreadAttribute(node as JsxSpreadAttribute);
case SyntaxKind.JsxExpression:
return emitJsxExpression(node as JsxExpression);
case SyntaxKind.JsxNamespacedName:
return emitJsxNamespacedName(node as JsxNamespacedName);
// Clauses
case SyntaxKind.CaseClause:
@@ -2178,8 +2183,6 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
// Transformation nodes
case SyntaxKind.NotEmittedStatement:
case SyntaxKind.EndOfDeclarationMarker:
case SyntaxKind.MergeDeclarationMarker:
return;
}
if (isExpression(node)) {
@@ -2284,8 +2287,6 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
return emitJsxSelfClosingElement(node as JsxSelfClosingElement);
case SyntaxKind.JsxFragment:
return emitJsxFragment(node as JsxFragment);
case SyntaxKind.JsxNamespacedName:
return emitJsxNamespacedName(node as JsxNamespacedName);
// Synthesized list
case SyntaxKind.SyntaxList:
@@ -2298,9 +2299,6 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
return emitPartiallyEmittedExpression(node as PartiallyEmittedExpression);
case SyntaxKind.CommaListExpression:
return emitCommaList(node as CommaListExpression);
case SyntaxKind.MergeDeclarationMarker:
case SyntaxKind.EndOfDeclarationMarker:
return;
case SyntaxKind.SyntheticReferenceExpression:
return Debug.fail("SyntheticReferenceExpression should not be printed");
}
@@ -4875,7 +4873,10 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
}
function emitEmbeddedStatement(parent: Node, node: Statement) {
if (isBlock(node) || getEmitFlags(parent) & EmitFlags.SingleLine) {
if (isBlock(node) ||
getEmitFlags(parent) & EmitFlags.SingleLine ||
preserveSourceNewlines && !getLeadingLineTerminatorCount(parent, node, ListFormat.None)
) {
writeSpace();
emit(node);
}
@@ -5529,7 +5530,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
return node;
}
function getTextOfNode(node: Identifier | PrivateIdentifier | LiteralExpression, includeTrivia?: boolean): string {
function getTextOfNode(node: Identifier | PrivateIdentifier | LiteralExpression | JsxNamespacedName, includeTrivia?: boolean): string {
if (isGeneratedIdentifier(node) || isGeneratedPrivateIdentifier(node)) {
return generateName(node);
}
@@ -5543,6 +5544,11 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
return idText(node);
}
}
else if (isJsxNamespacedName(node)) {
if (!canUseSourceFile || getSourceFileOfNode(node) !== getOriginalNode(sourceFile)) {
return getTextOfJsxNamespacedName(node);
}
}
else {
Debug.assertNode(node, isLiteralExpression); // not strictly necessary
if (!canUseSourceFile) {
@@ -5555,7 +5561,7 @@ export function createPrinter(printerOptions: PrinterOptions = {}, handlers: Pri
function getLiteralTextOfNode(node: LiteralLikeNode, neverAsciiEscape: boolean | undefined, jsxAttributeEscape: boolean): string {
if (node.kind === SyntaxKind.StringLiteral && (node as StringLiteral).textSourceNode) {
const textSourceNode = (node as StringLiteral).textSourceNode!;
if (isIdentifier(textSourceNode) || isPrivateIdentifier(textSourceNode) || isNumericLiteral(textSourceNode)) {
if (isIdentifier(textSourceNode) || isPrivateIdentifier(textSourceNode) || isNumericLiteral(textSourceNode) || isJsxNamespacedName(textSourceNode)) {
const text = isNumericLiteral(textSourceNode) ? textSourceNode.text : getTextOfNode(textSourceNode);
return jsxAttributeEscape ? `"${escapeJsxAttributeString(text)}"` :
neverAsciiEscape || (getEmitFlags(node) & EmitFlags.NoAsciiEscaping) ? `"${escapeString(text)}"` :
+6 -32
View File
@@ -79,7 +79,6 @@ import {
EmitNode,
emptyArray,
EmptyStatement,
EndOfDeclarationMarker,
EndOfFileToken,
EntityName,
EnumDeclaration,
@@ -121,6 +120,7 @@ import {
getLineAndCharacterOfPosition,
getNameOfDeclaration,
getNodeId,
getNonAssignedNameOfDeclaration,
getSourceMapRange,
getSyntheticLeadingComments,
getSyntheticTrailingComments,
@@ -298,7 +298,6 @@ import {
MemberName,
memoize,
memoizeOne,
MergeDeclarationMarker,
MetaProperty,
MethodDeclaration,
MethodSignature,
@@ -946,8 +945,6 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
updatePartiallyEmittedExpression,
createCommaListExpression,
updateCommaListExpression,
createEndOfDeclarationMarker,
createMergeDeclarationMarker,
createSyntheticReferenceExpression,
updateSyntheticReferenceExpression,
cloneNode,
@@ -6210,30 +6207,6 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
: node;
}
/**
* Creates a synthetic element to act as a placeholder for the end of an emitted declaration in
* order to properly emit exports.
*/
// @api
function createEndOfDeclarationMarker(original: Node) {
const node = createBaseNode<EndOfDeclarationMarker>(SyntaxKind.EndOfDeclarationMarker);
node.emitNode = {} as EmitNode;
node.original = original;
return node;
}
/**
* Creates a synthetic element to act as a placeholder for the beginning of a merged declaration in
* order to properly emit exports.
*/
// @api
function createMergeDeclarationMarker(original: Node) {
const node = createBaseNode<MergeDeclarationMarker>(SyntaxKind.MergeDeclarationMarker);
node.emitNode = {} as EmitNode;
node.original = original;
return node;
}
// @api
function createSyntheticReferenceExpression(expression: Expression, thisArg: Expression) {
const node = createBaseNode<SyntheticReferenceExpression>(SyntaxKind.SyntheticReferenceExpression);
@@ -6672,8 +6645,8 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
: reduceLeft(expressions, factory.createComma)!;
}
function getName(node: Declaration | undefined, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags: EmitFlags = 0) {
const nodeName = getNameOfDeclaration(node);
function getName(node: Declaration | undefined, allowComments?: boolean, allowSourceMaps?: boolean, emitFlags: EmitFlags = 0, ignoreAssignedName?: boolean) {
const nodeName = ignoreAssignedName ? node && getNonAssignedNameOfDeclaration(node) : getNameOfDeclaration(node);
if (nodeName && isIdentifier(nodeName) && !isGeneratedIdentifier(nodeName)) {
// TODO(rbuckton): Does this need to be parented?
const name = setParent(setTextRange(cloneNode(nodeName), nodeName), nodeName.parent);
@@ -6710,9 +6683,10 @@ export function createNodeFactory(flags: NodeFactoryFlags, baseFactory: BaseNode
* @param node The declaration.
* @param allowComments A value indicating whether comments may be emitted for the name.
* @param allowSourceMaps A value indicating whether source maps may be emitted for the name.
* @param ignoreAssignedName Indicates that the assigned name of a declaration shouldn't be considered.
*/
function getLocalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean) {
return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName);
function getLocalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean, ignoreAssignedName?: boolean) {
return getName(node, allowComments, allowSourceMaps, EmitFlags.LocalName, ignoreAssignedName);
}
/**
-12
View File
@@ -46,7 +46,6 @@ import {
DotDotDotToken,
ElementAccessExpression,
EmptyStatement,
EndOfDeclarationMarker,
EnumDeclaration,
EnumMember,
EqualsGreaterThanToken,
@@ -137,7 +136,6 @@ import {
LabeledStatement,
LiteralTypeNode,
MappedTypeNode,
MergeDeclarationMarker,
MetaProperty,
MethodDeclaration,
MethodSignature,
@@ -902,16 +900,6 @@ export function isSyntheticReference(node: Node): node is SyntheticReferenceExpr
return node.kind === SyntaxKind.SyntheticReferenceExpression;
}
/** @internal */
export function isMergeDeclarationMarker(node: Node): node is MergeDeclarationMarker {
return node.kind === SyntaxKind.MergeDeclarationMarker;
}
/** @internal */
export function isEndOfDeclarationMarker(node: Node): node is EndOfDeclarationMarker {
return node.kind === SyntaxKind.EndOfDeclarationMarker;
}
// Module References
export function isExternalModuleReference(node: Node): node is ExternalModuleReference {
+25 -11
View File
@@ -1249,13 +1249,14 @@ function createModuleOrTypeReferenceResolutionCache<T>(
export function createModuleResolutionCache(
currentDirectory: string,
getCanonicalFileName: (s: string) => string,
options?: CompilerOptions
options?: CompilerOptions,
packageJsonInfoCache?: PackageJsonInfoCache,
): ModuleResolutionCache {
const result = createModuleOrTypeReferenceResolutionCache(
currentDirectory,
getCanonicalFileName,
options,
/*packageJsonInfoCache*/ undefined,
packageJsonInfoCache,
getOriginalOrResolvedModuleFileName,
) as ModuleResolutionCache;
result.getOrCreateCacheForModuleName = (nonRelativeName, mode, redirectedReference) => result.getOrCreateCacheForNonRelativeName(nonRelativeName, mode, redirectedReference);
@@ -1277,6 +1278,16 @@ export function createTypeReferenceDirectiveResolutionCache(
);
}
/** @internal */
export function getOptionsForLibraryResolution(options: CompilerOptions) {
return { moduleResolution: ModuleResolutionKind.Node10, traceResolution: options.traceResolution };
}
/** @internal */
export function resolveLibrary(libraryName: string, resolveFrom: string, compilerOptions: CompilerOptions, host: ModuleResolutionHost, cache?: ModuleResolutionCache): ResolvedModuleWithFailedLookupLocations {
return resolveModuleName(libraryName, resolveFrom, getOptionsForLibraryResolution(compilerOptions), host, cache);
}
export function resolveModuleNameFromCache(moduleName: string, containingFile: string, cache: ModuleResolutionCache, mode?: ResolutionMode): ResolvedModuleWithFailedLookupLocations | undefined {
const containingDirectory = getDirectoryPath(containingFile);
return cache.getFromDirectoryCache(moduleName, mode, containingDirectory, /*redirectedReference*/ undefined);
@@ -1747,8 +1758,6 @@ function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleNa
const diagnosticState = {
...state,
features: state.features & ~NodeResolutionFeatures.Exports,
failedLookupLocations: [],
affectingLocations: [],
reportDiagnostic: noop,
};
const diagnosticResult = tryResolve(extensions & (Extensions.TypeScript | Extensions.Declaration), diagnosticState);
@@ -1784,6 +1793,12 @@ function nodeModuleNameResolverWorker(features: NodeResolutionFeatures, moduleNa
resolved = loadModuleFromSelfNameReference(extensions, moduleName, containingDirectory, state, cache, redirectedReference);
}
if (!resolved) {
if (moduleName.indexOf(":") > -1) {
if (traceEnabled) {
trace(host, Diagnostics.Skipping_module_0_that_looks_like_an_absolute_URI_target_file_types_Colon_1, moduleName, formatExtensions(extensions));
}
return undefined;
}
if (traceEnabled) {
trace(host, Diagnostics.Loading_module_0_from_node_modules_folder_target_file_types_Colon_1, moduleName, formatExtensions(extensions));
}
@@ -1889,7 +1904,7 @@ export function pathContainsNodeModules(path: string): boolean {
*
* @internal
*/
export function parseNodeModuleFromPath(resolved: string): string | undefined {
export function parseNodeModuleFromPath(resolved: string, isFolder?: boolean): string | undefined {
const path = normalizePath(resolved);
const idx = path.lastIndexOf(nodeModulesPathPart);
if (idx === -1) {
@@ -1897,16 +1912,16 @@ export function parseNodeModuleFromPath(resolved: string): string | undefined {
}
const indexAfterNodeModules = idx + nodeModulesPathPart.length;
let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules);
let indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterNodeModules, isFolder);
if (path.charCodeAt(indexAfterNodeModules) === CharacterCodes.at) {
indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName);
indexAfterPackageName = moveToNextDirectorySeparatorIfAvailable(path, indexAfterPackageName, isFolder);
}
return path.slice(0, indexAfterPackageName);
}
function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number): number {
function moveToNextDirectorySeparatorIfAvailable(path: string, prevSeparatorIndex: number, isFolder: boolean | undefined): number {
const nextSeparatorIndex = path.indexOf(directorySeparator, prevSeparatorIndex + 1);
return nextSeparatorIndex === -1 ? prevSeparatorIndex : nextSeparatorIndex;
return nextSeparatorIndex === -1 ? isFolder ? path.length : prevSeparatorIndex : nextSeparatorIndex;
}
function loadModuleFromFileNoPackageId(extensions: Extensions, candidate: string, onlyRecordFailures: boolean, state: ModuleResolutionState): Resolved | undefined {
@@ -2885,7 +2900,7 @@ function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, modu
const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => {
let pathAndExtension =
loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) ||
(rest || !(state.features & NodeResolutionFeatures.EsmMode)) && loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) ||
loadNodeModuleFromDirectoryWorker(
extensions,
candidate,
@@ -2926,7 +2941,6 @@ function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, modu
return fromPaths.value;
}
}
return loader(extensions, candidate, !nodeModulesDirectoryExists, state);
}
+37 -10
View File
@@ -24,6 +24,7 @@ import {
ExportAssignment,
Extension,
extensionFromPath,
extensionsNotSupportingExtensionlessResolution,
fileExtensionIsOneOf,
FileIncludeKind,
firstDefined,
@@ -91,6 +92,7 @@ import {
removeExtension,
removeFileExtension,
removeSuffix,
removeTrailingDirectorySeparator,
ResolutionMode,
resolvePath,
ScriptKind,
@@ -151,11 +153,18 @@ function getPreferences(
? [ModuleSpecifierEnding.JsExtension, ModuleSpecifierEnding.Index]
: [ModuleSpecifierEnding.Index, ModuleSpecifierEnding.JsExtension];
}
const allowImportingTsExtension = shouldAllowImportingTsExtension(compilerOptions, importingSourceFile.fileName);
switch (preferredEnding) {
case ModuleSpecifierEnding.JsExtension: return [ModuleSpecifierEnding.JsExtension, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index];
case ModuleSpecifierEnding.JsExtension: return allowImportingTsExtension
? [ModuleSpecifierEnding.JsExtension, ModuleSpecifierEnding.TsExtension, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index]
: [ModuleSpecifierEnding.JsExtension, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index];
case ModuleSpecifierEnding.TsExtension: return [ModuleSpecifierEnding.TsExtension, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.JsExtension, ModuleSpecifierEnding.Index];
case ModuleSpecifierEnding.Index: return [ModuleSpecifierEnding.Index, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.JsExtension];
case ModuleSpecifierEnding.Minimal: return [ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index, ModuleSpecifierEnding.JsExtension];
case ModuleSpecifierEnding.Index: return allowImportingTsExtension
? [ModuleSpecifierEnding.Index, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.TsExtension, ModuleSpecifierEnding.JsExtension]
: [ModuleSpecifierEnding.Index, ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.JsExtension];
case ModuleSpecifierEnding.Minimal: return allowImportingTsExtension
? [ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index, ModuleSpecifierEnding.TsExtension, ModuleSpecifierEnding.JsExtension]
: [ModuleSpecifierEnding.Minimal, ModuleSpecifierEnding.Index, ModuleSpecifierEnding.JsExtension];
default: Debug.assertNever(preferredEnding);
}
},
@@ -452,7 +461,7 @@ function getLocalModuleSpecifier(moduleFileName: string, info: Info, compilerOpt
}
const baseDirectory = getNormalizedAbsolutePath(getPathsBasePath(compilerOptions, host) || baseUrl!, host.getCurrentDirectory());
const relativeToBaseUrl = getRelativePathIfInDirectory(moduleFileName, baseDirectory, getCanonicalFileName);
const relativeToBaseUrl = getRelativePathIfInSameVolume(moduleFileName, baseDirectory, getCanonicalFileName);
if (!relativeToBaseUrl) {
return pathsOnly ? undefined : relativePath;
}
@@ -766,7 +775,7 @@ function tryGetModuleNameFromPaths(relativeToBaseUrl: string, paths: MapLike<rea
validateEnding({ ending, value })
) {
const matchedStar = value.substring(prefix.length, value.length - suffix.length);
return key.replace("*", matchedStar);
return pathIsRelative(matchedStar) ? undefined : key.replace("*", matchedStar);
}
}
}
@@ -1000,10 +1009,24 @@ function tryGetModuleNameAsNodeModule({ path, isRedirect }: ModulePath, { getCan
// happens very easily in fourslash tests though, since every test file listed gets included. See
// importNameCodeFix_typesVersions.ts for an example.)
const mainExportFile = toPath(mainFileRelative, packageRootPath, getCanonicalFileName);
if (removeFileExtension(mainExportFile) === removeFileExtension(getCanonicalFileName(moduleFileToTry))) {
const canonicalModuleFileToTry = getCanonicalFileName(moduleFileToTry);
if (removeFileExtension(mainExportFile) === removeFileExtension(canonicalModuleFileToTry)) {
// ^ An arbitrary removal of file extension for this comparison is almost certainly wrong
return { packageRootPath, moduleFileToTry };
}
else if (
packageJsonContent.type !== "module" &&
!fileExtensionIsOneOf(canonicalModuleFileToTry, extensionsNotSupportingExtensionlessResolution) &&
startsWith(canonicalModuleFileToTry, mainExportFile) &&
getDirectoryPath(canonicalModuleFileToTry) === removeTrailingDirectorySeparator(mainExportFile) &&
removeFileExtension(getBaseFileName(canonicalModuleFileToTry)) === "index"
) {
// if mainExportFile is a directory, which contains moduleFileToTry, we just try index file
// example mainExportFile: `pkg/lib` and moduleFileToTry: `pkg/lib/index`, we can use packageRootPath
// but this behavior is deprecated for packages with "type": "module", so we only do this for packages without "type": "module"
// and make sure that the extension on index.{???} is something that supports omitting the extension
return { packageRootPath, moduleFileToTry };
}
}
}
else {
@@ -1031,7 +1054,7 @@ function tryGetAnyFileFromPath(host: ModuleSpecifierResolutionHost, path: string
function getPathsRelativeToRootDirs(path: string, rootDirs: readonly string[], getCanonicalFileName: GetCanonicalFileName): string[] | undefined {
return mapDefined(rootDirs, rootDir => {
const relativePath = getRelativePathIfInDirectory(path, rootDir, getCanonicalFileName);
const relativePath = getRelativePathIfInSameVolume(path, rootDir, getCanonicalFileName);
return relativePath !== undefined && isPathRelativeToParent(relativePath) ? undefined : relativePath;
});
}
@@ -1046,7 +1069,12 @@ function processEnding(fileName: string, allowedEndings: readonly ModuleSpecifie
return fileName;
}
if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) {
const jsPriority = allowedEndings.indexOf(ModuleSpecifierEnding.JsExtension);
const tsPriority = allowedEndings.indexOf(ModuleSpecifierEnding.TsExtension);
if (fileExtensionIsOneOf(fileName, [Extension.Mts, Extension.Cts]) && tsPriority !== -1 && tsPriority < jsPriority) {
return fileName;
}
else if (fileExtensionIsOneOf(fileName, [Extension.Dmts, Extension.Mts, Extension.Dcts, Extension.Cts])) {
return noExtension + getJSExtensionForFile(fileName, options);
}
else if (!fileExtensionIsOneOf(fileName, [Extension.Dts]) && fileExtensionIsOneOf(fileName, [Extension.Ts]) && stringContains(fileName, ".d.")) {
@@ -1072,7 +1100,6 @@ function processEnding(fileName: string, allowedEndings: readonly ModuleSpecifie
// know if a .d.ts extension is valid, so use no extension or a .js extension
if (isDeclarationFileName(fileName)) {
const extensionlessPriority = allowedEndings.findIndex(e => e === ModuleSpecifierEnding.Minimal || e === ModuleSpecifierEnding.Index);
const jsPriority = allowedEndings.indexOf(ModuleSpecifierEnding.JsExtension);
return extensionlessPriority !== -1 && extensionlessPriority < jsPriority
? noExtension
: noExtension + getJSExtensionForFile(fileName, options);
@@ -1122,7 +1149,7 @@ export function tryGetJSExtensionForFile(fileName: string, options: CompilerOpti
}
}
function getRelativePathIfInDirectory(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined {
function getRelativePathIfInSameVolume(path: string, directoryPath: string, getCanonicalFileName: GetCanonicalFileName): string | undefined {
const relativePath = getRelativePathToDirectoryOrUrl(directoryPath, path, directoryPath, getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
return isRootedDiskPath(relativePath) ? undefined : relativePath;
}
+58 -21
View File
@@ -147,6 +147,7 @@ import {
isJSDocNullableType,
isJSDocReturnTag,
isJSDocTypeTag,
isJsxNamespacedName,
isJsxOpeningElement,
isJsxOpeningFragment,
isKeyword,
@@ -228,7 +229,6 @@ import {
JsxSelfClosingElement,
JsxSpreadAttribute,
JsxTagNameExpression,
JsxTagNamePropertyAccess,
JsxText,
JsxTokenSyntaxKind,
LabeledStatement,
@@ -1491,6 +1491,8 @@ namespace Parser {
var identifiers: Map<string, string>;
var identifierCount: number;
// TODO(jakebailey): This type is a lie; this value actually contains the result
// of ORing a bunch of `1 << ParsingContext.XYZ`.
var parsingContext: ParsingContext;
var notParenthesizedArrow: Set<number> | undefined;
@@ -2872,9 +2874,13 @@ namespace Parser {
return tokenIsIdentifierOrKeyword(token()) || token() === SyntaxKind.OpenBraceToken;
case ParsingContext.JsxChildren:
return true;
case ParsingContext.JSDocComment:
return true;
case ParsingContext.Count:
return Debug.fail("ParsingContext.Count used as a context"); // Not a real context, only a marker.
default:
Debug.assertNever(parsingContext, "Non-exhaustive case in 'isListElement'.");
}
return Debug.fail("Non-exhaustive case in 'isListElement'.");
}
function isValidHeritageClauseObjectLiteral() {
@@ -3010,6 +3016,9 @@ namespace Parser {
// True if positioned at element or terminator of the current list or any enclosing list
function isInSomeParsingContext(): boolean {
// We should be in at least one parsing context, be it SourceElements while parsing
// a SourceFile, or JSDocComment when lazily parsing JSDoc.
Debug.assert(parsingContext, "Missing parsing context");
for (let kind = 0; kind < ParsingContext.Count; kind++) {
if (parsingContext & (1 << kind)) {
if (isListElement(kind, /*inErrorRecovery*/ true) || isListTerminator(kind)) {
@@ -3385,6 +3394,7 @@ namespace Parser {
case ParsingContext.JsxAttributes: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected);
case ParsingContext.JsxChildren: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected);
case ParsingContext.AssertEntries: return parseErrorAtCurrentToken(Diagnostics.Identifier_or_string_literal_expected); // AssertionKey.
case ParsingContext.JSDocComment: return parseErrorAtCurrentToken(Diagnostics.Identifier_expected);
case ParsingContext.Count: return Debug.fail("ParsingContext.Count used as a context"); // Not a real context, only a marker.
default: Debug.assertNever(context);
}
@@ -6112,11 +6122,15 @@ namespace Parser {
// primaryExpression in the form of an identifier and "this" keyword
// We can't just simply use parseLeftHandSideExpressionOrHigher because then we will start consider class,function etc as a keyword
// We only want to consider "this" as a primaryExpression
let expression: JsxTagNameExpression = parseJsxTagName();
while (parseOptional(SyntaxKind.DotToken)) {
expression = finishNode(factoryCreatePropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos) as JsxTagNamePropertyAccess;
const initialExpression = parseJsxTagName();
if (isJsxNamespacedName(initialExpression)) {
return initialExpression; // `a:b.c` is invalid syntax, don't even look for the `.` if we parse `a:b`, and let `parseAttribute` report "unexpected :" instead.
}
return expression;
let expression: PropertyAccessExpression | Identifier | ThisExpression = initialExpression;
while (parseOptional(SyntaxKind.DotToken)) {
expression = finishNode(factoryCreatePropertyAccessExpression(expression, parseRightSideOfDot(/*allowIdentifierNames*/ true, /*allowPrivateIdentifiers*/ false)), pos);
}
return expression as JsxTagNameExpression;
}
function parseJsxTagName(): Identifier | JsxNamespacedName | ThisExpression {
@@ -7289,6 +7303,8 @@ namespace Parser {
function tryReuseAmbientDeclaration(pos: number): Statement | undefined {
return doInsideOfContext(NodeFlags.Ambient, () => {
// TODO(jakebailey): this is totally wrong; `parsingContext` is the result of ORing a bunch of `1 << ParsingContext.XYZ`.
// The enum should really be a bunch of flags.
const node = currentNode(parsingContext, pos);
if (node) {
return consumeNode(node) as Statement;
@@ -7835,12 +7851,12 @@ namespace Parser {
function parseClassElement(): ClassElement {
const pos = getNodePos();
const hasJSDoc = hasPrecedingJSDocComment();
if (token() === SyntaxKind.SemicolonToken) {
nextToken();
return finishNode(factory.createSemicolonClassElement(), pos);
return withJSDoc(finishNode(factory.createSemicolonClassElement(), pos), hasJSDoc);
}
const hasJSDoc = hasPrecedingJSDocComment();
const modifiers = parseModifiers(/*allowDecorators*/ true, /*permitConstAsModifier*/ true, /*stopOnStartOfClassStaticBlock*/ true);
if (token() === SyntaxKind.StaticKeyword && lookAhead(nextTokenIsOpenBrace)) {
return parseClassStaticBlockDeclaration(pos, hasJSDoc, modifiers);
@@ -8492,7 +8508,8 @@ namespace Parser {
TupleElementTypes, // Element types in tuple element type list
HeritageClauses, // Heritage clauses for a class or interface declaration.
ImportOrExportSpecifiers, // Named import clause's import specifier list,
AssertEntries, // Import entries list.
AssertEntries, // Import entries list.
JSDocComment, // Parsing via JSDocParser
Count // Number of parsing contexts
}
@@ -8598,6 +8615,9 @@ namespace Parser {
}
function parseJSDocCommentWorker(start = 0, length: number | undefined): JSDoc | undefined {
const saveParsingContext = parsingContext;
parsingContext |= 1 << ParsingContext.JSDocComment;
const content = sourceText;
const end = length === undefined ? content.length : start + length;
length = end - start;
@@ -8620,7 +8640,11 @@ namespace Parser {
const parts: JSDocComment[] = [];
// + 3 for leading /**, - 5 in total for /** */
return scanner.scanRange(start + 3, length - 5, () => {
const result = scanner.scanRange(start + 3, length - 5, doJSDocScan);
parsingContext = saveParsingContext;
return result;
function doJSDocScan() {
// Initially we can parse out a tag. We also have seen a starting asterisk.
// This is so that /** * @type */ doesn't parse.
let state = JSDocState.SawAsterisk;
@@ -8726,7 +8750,7 @@ namespace Parser {
if (parts.length && tags) Debug.assertIsDefined(commentsPos, "having parsed tags implies that the end of the comment span should be set");
const tagsArray = tags && createNodeArray(tags, tagsPos, tagsEnd);
return finishNode(factory.createJSDocComment(parts.length ? createNodeArray(parts, start, commentsPos) : trimmedComments.length ? trimmedComments : undefined, tagsArray), start, end);
});
}
function removeLeadingNewlines(comments: string[]) {
while (comments.length && (comments[0] === "\n" || comments[0] === "\r")) {
@@ -9140,12 +9164,15 @@ namespace Parser {
function parseNestedTypeLiteral(typeExpression: JSDocTypeExpression | undefined, name: EntityName, target: PropertyLikeParse, indent: number) {
if (typeExpression && isObjectOrObjectArrayTypeReference(typeExpression.type)) {
const pos = getNodePos();
let child: JSDocPropertyLikeTag | JSDocTypeTag | false;
let child: JSDocPropertyLikeTag | JSDocTypeTag | JSDocTemplateTag | false;
let children: JSDocPropertyLikeTag[] | undefined;
while (child = tryParse(() => parseChildParameterOrPropertyTag(target, indent, name))) {
if (child.kind === SyntaxKind.JSDocParameterTag || child.kind === SyntaxKind.JSDocPropertyTag) {
children = append(children, child);
}
else if (child.kind === SyntaxKind.JSDocTemplateTag) {
parseErrorAtRange(child.tagName, Diagnostics.A_JSDoc_template_tag_may_not_follow_a_typedef_callback_or_overload_tag);
}
}
if (children) {
const literal = finishNode(factory.createJSDocTypeLiteral(children, typeExpression.type.kind === SyntaxKind.ArrayType), pos);
@@ -9244,7 +9271,9 @@ namespace Parser {
const usedBrace = parseOptional(SyntaxKind.OpenBraceToken);
const pos = getNodePos();
const expression = parsePropertyAccessEntityNameExpression();
scanner.setInJSDocType(true);
const typeArguments = tryParseTypeArguments();
scanner.setInJSDocType(false);
const node = factory.createExpressionWithTypeArguments(expression, typeArguments) as ExpressionWithTypeArguments & { expression: Identifier | PropertyAccessEntityNameExpression };
const res = finishNode(node, pos);
if (usedBrace) {
@@ -9289,11 +9318,14 @@ namespace Parser {
let end: number | undefined;
if (!typeExpression || isObjectOrObjectArrayTypeReference(typeExpression.type)) {
let child: JSDocTypeTag | JSDocPropertyTag | false;
let child: JSDocTypeTag | JSDocPropertyTag | JSDocTemplateTag | false;
let childTypeTag: JSDocTypeTag | undefined;
let jsDocPropertyTags: JSDocPropertyTag[] | undefined;
let hasChildren = false;
while (child = tryParse(() => parseChildPropertyTag(indent))) {
if (child.kind === SyntaxKind.JSDocTemplateTag) {
break;
}
hasChildren = true;
if (child.kind === SyntaxKind.JSDocTypeTag) {
if (childTypeTag) {
@@ -9357,12 +9389,15 @@ namespace Parser {
return typeNameOrNamespaceName;
}
function parseCallbackTagParameters(indent: number) {
const pos = getNodePos();
let child: JSDocParameterTag | false;
let child: JSDocParameterTag | JSDocTemplateTag | false;
let parameters;
while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter, indent) as JSDocParameterTag)) {
while (child = tryParse(() => parseChildParameterOrPropertyTag(PropertyLikeParse.CallbackParameter, indent) as JSDocParameterTag | JSDocTemplateTag)) {
if (child.kind === SyntaxKind.JSDocTemplateTag) {
parseErrorAtRange(child.tagName, Diagnostics.A_JSDoc_template_tag_may_not_follow_a_typedef_callback_or_overload_tag);
break;
}
parameters = append(parameters, child);
}
return createNodeArray(parameters || [], pos);
@@ -9418,10 +9453,10 @@ namespace Parser {
}
function parseChildPropertyTag(indent: number) {
return parseChildParameterOrPropertyTag(PropertyLikeParse.Property, indent) as JSDocTypeTag | JSDocPropertyTag | false;
return parseChildParameterOrPropertyTag(PropertyLikeParse.Property, indent) as JSDocTypeTag | JSDocPropertyTag | JSDocTemplateTag | false;
}
function parseChildParameterOrPropertyTag(target: PropertyLikeParse, indent: number, name?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false {
function parseChildParameterOrPropertyTag(target: PropertyLikeParse, indent: number, name?: EntityName): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | JSDocTemplateTag | false {
let canParseTag = true;
let seenAsterisk = false;
while (true) {
@@ -9457,13 +9492,13 @@ namespace Parser {
}
}
function tryParseChildTag(target: PropertyLikeParse, indent: number): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | false {
function tryParseChildTag(target: PropertyLikeParse, indent: number): JSDocTypeTag | JSDocPropertyTag | JSDocParameterTag | JSDocTemplateTag | false {
Debug.assert(token() === SyntaxKind.AtToken);
const start = scanner.getTokenFullStart();
nextTokenJSDoc();
const tagName = parseJSDocIdentifierName();
skipWhitespace();
const indentText = skipWhitespaceOrAsterisk();
let t: PropertyLikeParse;
switch (tagName.escapedText) {
case "type":
@@ -9477,6 +9512,8 @@ namespace Parser {
case "param":
t = PropertyLikeParse.Parameter | PropertyLikeParse.CallbackParameter;
break;
case "template":
return parseTemplateTag(start, tagName, indent, indentText);
default:
return false;
}
+143 -25
View File
@@ -153,9 +153,11 @@ import {
getTsBuildInfoEmitOutputFilePath,
getTsConfigObjectLiteralExpression,
getTsConfigPropArrayElementValue,
getTypesPackageName,
HasChangedAutomaticTypeDirectiveNames,
hasChangesInResolutions,
hasExtension,
HasInvalidatedLibResolutions,
HasInvalidatedResolutions,
hasJSDocNodes,
hasJSFileExtension,
@@ -211,6 +213,7 @@ import {
JsxEmit,
length,
libMap,
LibResolution,
libs,
mapDefined,
mapDefinedIterator,
@@ -276,6 +279,7 @@ import {
ResolvedModuleWithFailedLookupLocations,
ResolvedProjectReference,
ResolvedTypeReferenceDirectiveWithFailedLookupLocations,
resolveLibrary,
resolveModuleName,
resolveTypeReferenceDirective,
returnFalse,
@@ -1097,6 +1101,32 @@ function forEachProjectReference<T>(
/** @internal */
export const inferredTypesContainingFile = "__inferred type names__.ts";
/** @internal */
export function getInferredLibraryNameResolveFrom(options: CompilerOptions, currentDirectory: string, libFileName: string) {
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
return combinePaths(containingDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`);
}
function getLibraryNameFromLibFileName(libFileName: string) {
// Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and
// lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable
// lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown
const components = libFileName.split(".");
let path = components[1];
let i = 2;
while (components[i] && components[i] !== "d") {
path += (i === 2 ? "/" : "-") + components[i];
i++;
}
return "@typescript/lib-" + path;
}
function getLibFileNameFromLibReference(libReference: FileReference) {
const libName = toFileNameLowerCase(libReference.fileName);
const libFileName = libMap.get(libName);
return { libName, libFileName };
}
interface DiagnosticCache<T extends Diagnostic> {
perFile?: Map<Path, readonly T[]>;
allDiagnostics?: readonly T[];
@@ -1176,6 +1206,7 @@ export function isProgramUptoDate(
getSourceVersion: (path: Path, fileName: string) => string | undefined,
fileExists: (fileName: string) => boolean,
hasInvalidatedResolutions: HasInvalidatedResolutions,
hasInvalidatedLibResolutions: HasInvalidatedLibResolutions,
hasChangedAutomaticTypeDirectiveNames: HasChangedAutomaticTypeDirectiveNames | undefined,
getParsedCommandLine: (fileName: string) => ParsedCommandLine | undefined,
projectReferences: readonly ProjectReference[] | undefined
@@ -1201,6 +1232,9 @@ export function isProgramUptoDate(
// If the compilation settings do no match, then the program is not up-to-date
if (!compareDataObjects(currentOptions, newOptions)) return false;
// If library resolution is invalidated, then the program is not up-to-date
if (program.resolvedLibReferences && forEachEntry(program.resolvedLibReferences, (_value, libFileName) => hasInvalidatedLibResolutions(libFileName))) return false;
// If everything matches but the text of config file is changed,
// error locations can change for program options, so update the program
if (currentOptions.configFile && newOptions.configFile) return currentOptions.configFile.text === newOptions.configFile.text;
@@ -1469,6 +1503,12 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
let automaticTypeDirectiveNames: string[] | undefined;
let automaticTypeDirectiveResolutions: ModeAwareCache<ResolvedTypeReferenceDirectiveWithFailedLookupLocations>;
let resolvedLibReferences: Map<string, LibResolution> | undefined;
let resolvedLibProcessing: Map<string, LibResolution> | undefined;
let packageMap: Map<string, boolean> | undefined;
// The below settings are to track if a .js file should be add to the program if loaded via searching under node_modules.
// This works as imported modules are discovered recursively in a depth first manner, specifically:
// - For each root file, findSourceFile is called.
@@ -1592,6 +1632,17 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
);
}
const hasInvalidatedLibResolutions = host.hasInvalidatedLibResolutions || returnFalse;
let actualResolveLibrary: (libraryName: string, resolveFrom: string, options: CompilerOptions, libFileName: string) => ResolvedModuleWithFailedLookupLocations;
if (host.resolveLibrary) {
actualResolveLibrary = host.resolveLibrary.bind(host);
}
else {
const libraryResolutionCache = createModuleResolutionCache(currentDirectory, getCanonicalFileName, options, moduleResolutionCache?.getPackageJsonInfoCache());
actualResolveLibrary = (libraryName, resolveFrom, options) =>
resolveLibrary(libraryName, resolveFrom, options, host, libraryResolutionCache);
}
// Map from a stringified PackageId to the source file with that id.
// Only one source file may have a given packageId. Others become redirects (see createRedirectSourceFile).
// `packageIdToSourceFile` is only used while building the program, while `sourceFileToPackageName` and `isSourceFileTargetOfRedirect` are kept around.
@@ -1688,7 +1739,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
if (automaticTypeDirectiveNames.length) {
tracing?.push(tracing.Phase.Program, "processTypeReferences", { count: automaticTypeDirectiveNames.length });
// This containingFilename needs to match with the one used in managed-side
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : host.getCurrentDirectory();
const containingDirectory = options.configFilePath ? getDirectoryPath(options.configFilePath) : currentDirectory;
const containingFilename = combinePaths(containingDirectory, inferredTypesContainingFile);
const resolutions = resolveTypeReferenceDirectiveNamesReusingOldState(automaticTypeDirectiveNames, containingFilename);
for (let i = 0; i < automaticTypeDirectiveNames.length; i++) {
@@ -1772,6 +1823,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
// unconditionally set oldProgram to undefined to prevent it from being captured in closure
oldProgram = undefined;
resolvedLibProcessing = undefined;
const program: Program = {
getRootFileNames: () => rootNames,
@@ -1813,6 +1865,10 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
sourceFileToPackageName,
redirectTargetsMap,
usesUriStyleNodeCoreModules,
resolvedLibReferences,
getCurrentPackagesMap: () => packageMap,
typesPackageExists,
packageBundlesTypes,
isEmittedFile,
getConfigFileParsingDiagnostics,
getProjectReferences,
@@ -1859,6 +1915,30 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
return program;
function getPackagesMap() {
if (packageMap) return packageMap;
packageMap = new Map();
// A package name maps to true when we detect it has .d.ts files.
// This is useful as an approximation of whether a package bundles its own types.
// Note: we only look at files already found by module resolution,
// so there may be files we did not consider.
files.forEach(sf => {
if (!sf.resolvedModules) return;
sf.resolvedModules.forEach(({ resolvedModule }) => {
if (resolvedModule?.packageId) packageMap!.set(resolvedModule.packageId.name, resolvedModule.extension === Extension.Dts || !!packageMap!.get(resolvedModule.packageId.name));
});
});
return packageMap;
}
function typesPackageExists(packageName: string): boolean {
return getPackagesMap().has(getTypesPackageName(packageName));
}
function packageBundlesTypes(packageName: string): boolean {
return !!getPackagesMap().get(packageName);
}
function addResolutionDiagnostics(resolution: ResolvedModuleWithFailedLookupLocations | ResolvedTypeReferenceDirectiveWithFailedLookupLocations) {
if (!resolution.resolutionDiagnostics?.length) return;
(fileProcessingDiagnostics ??= []).push({
@@ -2441,6 +2521,11 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
return StructureIsReused.SafeModules;
}
if (oldProgram.resolvedLibReferences &&
forEachEntry(oldProgram.resolvedLibReferences, (resolution, libFileName) => pathForLibFileWorker(libFileName).actual !== resolution.actual)) {
return StructureIsReused.SafeModules;
}
if (host.hasChangedAutomaticTypeDirectiveNames) {
if (host.hasChangedAutomaticTypeDirectiveNames()) return StructureIsReused.SafeModules;
}
@@ -2481,6 +2566,8 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
sourceFileToPackageName = oldProgram.sourceFileToPackageName;
redirectTargetsMap = oldProgram.redirectTargetsMap;
usesUriStyleNodeCoreModules = oldProgram.usesUriStyleNodeCoreModules;
resolvedLibReferences = oldProgram.resolvedLibReferences;
packageMap = oldProgram.getCurrentPackagesMap();
return StructureIsReused.Completely;
}
@@ -2596,7 +2683,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
return equalityComparer(file.fileName, getDefaultLibraryFileName());
}
else {
return some(options.lib, libFileName => equalityComparer(file.fileName, pathForLibFile(libFileName)));
return some(options.lib, libFileName => equalityComparer(file.fileName, resolvedLibReferences!.get(libFileName)!.actual));
}
}
@@ -3310,11 +3397,9 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
function getLibFileFromReference(ref: FileReference) {
const libName = toFileNameLowerCase(ref.fileName);
const libFileName = libMap.get(libName);
if (libFileName) {
return getSourceFile(pathForLibFile(libFileName));
}
const { libFileName } = getLibFileNameFromLibReference(ref);
const actualFileName = libFileName && resolvedLibReferences?.get(libFileName)?.actual;
return actualFileName !== undefined ? getSourceFile(actualFileName) : undefined;
}
/** This should have similar behavior to 'processSourceFile' without diagnostics or mutation. */
@@ -3810,28 +3895,61 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
function pathForLibFile(libFileName: string): string {
// Support resolving to lib.dom.d.ts -> @typescript/lib-dom, and
// lib.dom.iterable.d.ts -> @typescript/lib-dom/iterable
// lib.es2015.symbol.wellknown.d.ts -> @typescript/lib-es2015/symbol-wellknown
const components = libFileName.split(".");
let path = components[1];
let i = 2;
while (components[i] && components[i] !== "d") {
path += (i === 2 ? "/" : "-") + components[i];
i++;
const existing = resolvedLibReferences?.get(libFileName);
if (existing) return existing.actual;
const result = pathForLibFileWorker(libFileName);
(resolvedLibReferences ??= new Map()).set(libFileName, result);
return result.actual;
}
function pathForLibFileWorker(libFileName: string): LibResolution {
const existing = resolvedLibProcessing?.get(libFileName);
if (existing) return existing;
if (structureIsReused !== StructureIsReused.Not && oldProgram && !hasInvalidatedLibResolutions(libFileName)) {
const oldResolution = oldProgram.resolvedLibReferences?.get(libFileName);
if (oldResolution) {
if (oldResolution.resolution && isTraceEnabled(options, host)) {
const libraryName = getLibraryNameFromLibFileName(libFileName);
const resolveFrom = getInferredLibraryNameResolveFrom(options, currentDirectory, libFileName);
trace(host,
oldResolution.resolution.resolvedModule ?
oldResolution.resolution.resolvedModule.packageId ?
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 :
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved,
libraryName,
getNormalizedAbsolutePath(resolveFrom, currentDirectory),
oldResolution.resolution.resolvedModule?.resolvedFileName,
oldResolution.resolution.resolvedModule?.packageId && packageIdToString(oldResolution.resolution.resolvedModule.packageId)
);
}
(resolvedLibProcessing ??= new Map()).set(libFileName, oldResolution);
return oldResolution;
}
}
const resolveFrom = combinePaths(currentDirectory, `__lib_node_modules_lookup_${libFileName}__.ts`);
const localOverrideModuleResult = resolveModuleName("@typescript/lib-" + path, resolveFrom, { moduleResolution: ModuleResolutionKind.Node10 }, host, moduleResolutionCache);
if (localOverrideModuleResult?.resolvedModule) {
return localOverrideModuleResult.resolvedModule.resolvedFileName;
}
return combinePaths(defaultLibraryPath, libFileName);
const libraryName = getLibraryNameFromLibFileName(libFileName);
const resolveFrom = getInferredLibraryNameResolveFrom(options, currentDirectory, libFileName);
tracing?.push(tracing.Phase.Program, "resolveLibrary", { resolveFrom });
performance.mark("beforeResolveLibrary");
const resolution = actualResolveLibrary(libraryName, resolveFrom, options, libFileName);
performance.mark("afterResolveLibrary");
performance.measure("ResolveLibrary", "beforeResolveLibrary", "afterResolveLibrary");
tracing?.pop();
const result: LibResolution = {
resolution,
actual: resolution.resolvedModule ?
resolution.resolvedModule.resolvedFileName :
combinePaths(defaultLibraryPath, libFileName)
};
(resolvedLibProcessing ??= new Map()).set(libFileName, result);
return result;
}
function processLibReferenceDirectives(file: SourceFile) {
forEach(file.libReferenceDirectives, (libReference, index) => {
const libName = toFileNameLowerCase(libReference.fileName);
const libFileName = libMap.get(libName);
const { libName, libFileName } = getLibFileNameFromLibReference(libReference);
if (libFileName) {
// we ignore any 'no-default-lib' reference set on this file.
processRootFile(pathForLibFile(libFileName), /*isDefaultLib*/ true, /*ignoreNoDefaultLib*/ true, { kind: FileIncludeKind.LibReferenceDirective, file: file.path, index, });
@@ -3971,7 +4089,7 @@ export function createProgram(rootNamesOrOptions: readonly string[] | CreateProg
}
else {
// An absolute path pointing to the containing directory of the config file
const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), host.getCurrentDirectory());
const basePath = getNormalizedAbsolutePath(getDirectoryPath(refPath), currentDirectory);
sourceFile = host.getSourceFile(refPath, ScriptTarget.JSON) as JsonSourceFile | undefined;
addFileToFilesByName(sourceFile, sourceFilePath, /*redirectedPath*/ undefined);
if (sourceFile === undefined) {
+206 -71
View File
@@ -26,9 +26,12 @@ import {
GetCanonicalFileName,
getDirectoryPath,
getEffectiveTypeRoots,
getInferredLibraryNameResolveFrom,
getNormalizedAbsolutePath,
getOptionsForLibraryResolution,
getPathComponents,
getPathFromPathComponents,
HasInvalidatedLibResolutions,
HasInvalidatedResolutions,
hasTrailingDirectorySeparator,
ignoredPaths,
@@ -63,6 +66,7 @@ import {
ResolvedModuleWithFailedLookupLocations,
ResolvedProjectReference,
ResolvedTypeReferenceDirectiveWithFailedLookupLocations,
resolveLibrary as ts_resolveLibrary,
resolveModuleName as ts_resolveModuleName,
returnTrue,
some,
@@ -75,6 +79,11 @@ import {
WatchDirectoryFlags,
} from "./_namespaces/ts";
/** @internal */
export interface HasInvalidatedFromResolutionCache {
hasInvalidatedResolutions: HasInvalidatedResolutions;
hasInvalidatedLibResolutions: HasInvalidatedLibResolutions;
}
/**
* This is the cache of module/typedirectives resolution that can be retained across program
*
@@ -100,7 +109,12 @@ export interface ResolutionCache {
containingSourceFile: SourceFile | undefined,
reusedNames: readonly T[] | undefined
): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[];
resolveLibrary(
libraryName: string,
resolveFrom: string,
options: CompilerOptions,
libFileName: string,
): ResolvedModuleWithFailedLookupLocations;
resolveSingleModuleNameWithoutWatching(
moduleName: string,
containingFile: string,
@@ -111,7 +125,10 @@ export interface ResolutionCache {
removeResolutionsOfFile(filePath: Path): void;
removeResolutionsFromProjectReferenceRedirects(filePath: Path): void;
setFilesWithInvalidatedNonRelativeUnresolvedImports(filesWithUnresolvedImports: Map<Path, readonly string[]>): void;
createHasInvalidatedResolutions(customHasInvalidatedResolutions: HasInvalidatedResolutions): HasInvalidatedResolutions;
createHasInvalidatedResolutions(
customHasInvalidatedResolutions: HasInvalidatedResolutions,
customHasInvalidatedLibResolutions: HasInvalidatedLibResolutions,
): HasInvalidatedFromResolutionCache;
hasChangedAutomaticTypeDirectiveNames(): boolean;
isFileWithInvalidatedNonRelativeUnresolvedImports(path: Path): boolean;
@@ -125,6 +142,7 @@ export interface ResolutionCache {
getModuleResolutionCache(): ModuleResolutionCache;
clear(): void;
onChangesAffectModuleResolution(): void;
}
/** @internal */
@@ -135,6 +153,7 @@ export interface ResolutionWithFailedLookupLocations {
refCount?: number;
// Files that have this resolution using
files?: Set<Path>;
node10Result?: string;
}
interface ResolutionWithResolvedFileName {
@@ -412,6 +431,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
let failedLookupChecks: Set<Path> | undefined;
let startsWithPathChecks: Set<Path> | undefined;
let isInDirectoryChecks: Set<Path> | undefined;
let allModuleAndTypeResolutionsAreInvalidated = false;
const getCurrentDirectory = memoize(() => resolutionHost.getCurrentDirectory!()); // TODO: GH#18217
const cachedDirectoryStructureHost = resolutionHost.getCachedDirectoryStructureHost();
@@ -434,6 +454,14 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
moduleResolutionCache.getPackageJsonInfoCache(),
);
const resolvedLibraries = new Map<string, CachedResolvedModuleWithFailedLookupLocations>();
const libraryResolutionCache = createModuleResolutionCache(
getCurrentDirectory(),
resolutionHost.getCanonicalFileName,
getOptionsForLibraryResolution(resolutionHost.getCompilationSettings()),
moduleResolutionCache.getPackageJsonInfoCache(),
);
const directoryWatchesOfFailedLookups = new Map<string, DirectoryWatchesOfFailedLookup>();
const fileWatchesOfAffectingLocations = new Map<string, FileWatcherOfAffectingLocation>();
const rootDir = getRootDirectoryOfResolutionCache(rootDirForResolution, getCurrentDirectory);
@@ -453,6 +481,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
finishCachingPerDirectoryResolution,
resolveModuleNameLiterals,
resolveTypeReferenceDirectiveReferences,
resolveLibrary,
resolveSingleModuleNameWithoutWatching,
removeResolutionsFromProjectReferenceRedirects,
removeResolutionsOfFile,
@@ -464,7 +493,8 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
isFileWithInvalidatedNonRelativeUnresolvedImports,
updateTypeRootsWatch,
closeTypeRootsWatch,
clear
clear,
onChangesAffectModuleResolution,
};
function getResolvedModule(resolution: CachedResolvedModuleWithFailedLookupLocations) {
@@ -490,14 +520,25 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
isInDirectoryChecks = undefined;
affectingPathChecks = undefined;
affectingPathChecksForFile = undefined;
allModuleAndTypeResolutionsAreInvalidated = false;
moduleResolutionCache.clear();
typeReferenceDirectiveResolutionCache.clear();
moduleResolutionCache.update(resolutionHost.getCompilationSettings());
typeReferenceDirectiveResolutionCache.update(resolutionHost.getCompilationSettings());
libraryResolutionCache.clear();
impliedFormatPackageJsons.clear();
resolvedLibraries.clear();
hasChangedAutomaticTypeDirectiveNames = false;
}
function onChangesAffectModuleResolution() {
allModuleAndTypeResolutionsAreInvalidated = true;
moduleResolutionCache.clearAllExceptPackageJsonInfoCache();
typeReferenceDirectiveResolutionCache.clearAllExceptPackageJsonInfoCache();
moduleResolutionCache.update(resolutionHost.getCompilationSettings());
typeReferenceDirectiveResolutionCache.update(resolutionHost.getCompilationSettings());
}
function startRecordingFilesWithChangedResolutions() {
filesWithChangedSetOfUnresolvedImports = [];
}
@@ -518,31 +559,55 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
return !!value && !!value.length;
}
function createHasInvalidatedResolutions(customHasInvalidatedResolutions: HasInvalidatedResolutions): HasInvalidatedResolutions {
function createHasInvalidatedResolutions(
customHasInvalidatedResolutions: HasInvalidatedResolutions,
customHasInvalidatedLibResolutions: HasInvalidatedLibResolutions,
): HasInvalidatedFromResolutionCache {
// Ensure pending resolutions are applied
invalidateResolutionsOfFailedLookupLocations();
const collected = filesWithInvalidatedResolutions;
filesWithInvalidatedResolutions = undefined;
return path => customHasInvalidatedResolutions(path) ||
!!collected?.has(path) ||
isFileWithInvalidatedNonRelativeUnresolvedImports(path);
return {
hasInvalidatedResolutions: path => customHasInvalidatedResolutions(path) ||
allModuleAndTypeResolutionsAreInvalidated ||
!!collected?.has(path) ||
isFileWithInvalidatedNonRelativeUnresolvedImports(path),
hasInvalidatedLibResolutions: libFileName => customHasInvalidatedLibResolutions(libFileName) ||
!!resolvedLibraries?.get(libFileName)?.isInvalidated,
};
}
function startCachingPerDirectoryResolution() {
moduleResolutionCache.clearAllExceptPackageJsonInfoCache();
typeReferenceDirectiveResolutionCache.clearAllExceptPackageJsonInfoCache();
libraryResolutionCache.clearAllExceptPackageJsonInfoCache();
// perDirectoryResolvedModuleNames and perDirectoryResolvedTypeReferenceDirectives could be non empty if there was exception during program update
// (between startCachingPerDirectoryResolution and finishCachingPerDirectoryResolution)
nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions);
nonRelativeExternalModuleResolutions.clear();
}
function cleanupLibResolutionWatching(newProgram: Program | undefined) {
resolvedLibraries.forEach((resolution, libFileName) => {
if (!newProgram?.resolvedLibReferences?.has(libFileName)) {
stopWatchFailedLookupLocationOfResolution(
resolution,
resolutionHost.toPath(getInferredLibraryNameResolveFrom(newProgram!.getCompilerOptions(), getCurrentDirectory(), libFileName)),
getResolvedModule,
);
resolvedLibraries.delete(libFileName);
}
});
}
function finishCachingPerDirectoryResolution(newProgram: Program | undefined, oldProgram: Program | undefined) {
filesWithInvalidatedNonRelativeUnresolvedImports = undefined;
allModuleAndTypeResolutionsAreInvalidated = false;
nonRelativeExternalModuleResolutions.forEach(watchFailedLookupLocationOfNonRelativeModuleResolutions);
nonRelativeExternalModuleResolutions.clear();
// Update file watches
if (newProgram !== oldProgram) {
cleanupLibResolutionWatching(newProgram);
newProgram?.getSourceFiles().forEach(newFile => {
const expected = isExternalOrCommonJsModule(newFile) ? newFile.packageJsonLocations?.length ?? 0 : 0;
const existing = impliedFormatPackageJsons.get(newFile.path) ?? emptyArray;
@@ -644,11 +709,12 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>;
shouldRetryResolution: (t: T) => boolean;
logChanges?: boolean;
deferWatchingNonRelativeResolution: boolean;
}
function resolveNamesWithLocalCache<Entry, SourceFile, T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>({
entries, containingFile, containingSourceFile, redirectedReference, options,
perFileCache, reusedNames,
loader, getResolutionWithResolvedFileName,
loader, getResolutionWithResolvedFileName, deferWatchingNonRelativeResolution,
shouldRetryResolution, logChanges,
}: ResolveNamesWithLocalCacheInput<Entry, SourceFile, T, R>): readonly T[] {
const path = resolutionHost.toPath(containingFile);
@@ -670,16 +736,16 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
let resolution = resolutionsInFile.get(name, mode);
// Resolution is valid if it is present and not invalidated
if (!seenNamesInFile.has(name, mode) &&
unmatchedRedirects || !resolution || resolution.isInvalidated ||
// If the name is unresolved import that was invalidated, recalculate
(hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution))) {
(allModuleAndTypeResolutionsAreInvalidated || unmatchedRedirects || !resolution || resolution.isInvalidated ||
// If the name is unresolved import that was invalidated, recalculate
(hasInvalidatedNonRelativeUnresolvedImport && !isExternalModuleNameRelative(name) && shouldRetryResolution(resolution)))) {
const existingResolution = resolution;
resolution = loader.resolve(name, mode);
if (resolutionHost.onDiscoveredSymlink && resolutionIsSymlink(resolution)) {
resolutionHost.onDiscoveredSymlink();
}
resolutionsInFile.set(name, mode, resolution);
watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution, path, getResolutionWithResolvedFileName);
watchFailedLookupLocationsOfExternalModuleResolutions(name, resolution, path, getResolutionWithResolvedFileName, deferWatchingNonRelativeResolution);
if (existingResolution) {
stopWatchFailedLookupLocationOfResolution(existingResolution, path, getResolutionWithResolvedFileName);
}
@@ -693,7 +759,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
else {
const host = resolutionHost.getCompilerHost?.() || resolutionHost;
if (isTraceEnabled(options, host) && !seenNamesInFile.has(name, mode)) {
const resolved = getResolutionWithResolvedFileName(resolution);
const resolved = getResolutionWithResolvedFileName(resolution!);
trace(
host,
perFileCache === resolvedModuleNames as unknown ?
@@ -778,6 +844,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
),
getResolutionWithResolvedFileName: getResolvedTypeReferenceDirective,
shouldRetryResolution: resolution => resolution.resolvedTypeReferenceDirective === undefined,
deferWatchingNonRelativeResolution: false,
});
}
@@ -805,9 +872,48 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
getResolutionWithResolvedFileName: getResolvedModule,
shouldRetryResolution: resolution => !resolution.resolvedModule || !resolutionExtensionIsTSOrJson(resolution.resolvedModule.extension),
logChanges: logChangesWhenResolvingModule,
deferWatchingNonRelativeResolution: true, // Defer non relative resolution watch because we could be using ambient modules
});
}
function resolveLibrary(
libraryName: string,
resolveFrom: string,
options: CompilerOptions,
libFileName: string,
) {
const host = resolutionHost.getCompilerHost?.() || resolutionHost;
let resolution = resolvedLibraries?.get(libFileName);
if (!resolution || resolution.isInvalidated) {
const existingResolution = resolution;
resolution = ts_resolveLibrary(libraryName, resolveFrom, options, host, libraryResolutionCache);
const path = resolutionHost.toPath(resolveFrom);
watchFailedLookupLocationsOfExternalModuleResolutions(libraryName, resolution, path, getResolvedModule, /*deferWatchingNonRelativeResolution*/ false);
resolvedLibraries.set(libFileName, resolution);
if (existingResolution) {
stopWatchFailedLookupLocationOfResolution(existingResolution, path, getResolvedModule);
}
}
else {
if (isTraceEnabled(options, host)) {
const resolved = getResolvedModule(resolution);
trace(
host,
resolved?.resolvedFileName ?
resolved.packageId ?
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2_with_Package_ID_3 :
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_successfully_resolved_to_2 :
Diagnostics.Reusing_resolution_of_module_0_from_1_of_old_program_it_was_not_resolved,
libraryName,
resolveFrom,
resolved?.resolvedFileName,
resolved?.packageId && packageIdToString(resolved.packageId)
);
}
}
return resolution;
}
function resolveSingleModuleNameWithoutWatching(moduleName: string, containingFile: string) {
const path = resolutionHost.toPath(containingFile);
const resolutionsInFile = resolvedModuleNames.get(path);
@@ -825,6 +931,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
resolution: T,
filePath: Path,
getResolutionWithResolvedFileName: GetResolutionWithResolvedFileName<T, R>,
deferWatchingNonRelativeResolution: boolean,
) {
if (resolution.refCount) {
resolution.refCount++;
@@ -833,7 +940,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
else {
resolution.refCount = 1;
Debug.assert(!resolution.files?.size); // This resolution shouldnt be referenced by any file yet
if (isExternalModuleNameRelative(name)) {
if (!deferWatchingNonRelativeResolution || isExternalModuleNameRelative(name)) {
watchFailedLookupLocationOfResolution(resolution);
}
else {
@@ -850,43 +957,48 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
(resolution.files ??= new Set()).add(filePath);
}
function watchFailedLookupLocation(failedLookupLocation: string, setAtRoot: boolean) {
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
const toWatch = getDirectoryToWatchFailedLookupLocation(
failedLookupLocation,
failedLookupLocationPath,
rootDir,
rootPath,
rootPathComponents,
getCurrentDirectory,
);
if (toWatch) {
const { dir, dirPath, nonRecursive } = toWatch;
if (dirPath === rootPath) {
Debug.assert(nonRecursive);
setAtRoot = true;
}
else {
setDirectoryWatcher(dir, dirPath, nonRecursive);
}
}
return setAtRoot;
}
function watchFailedLookupLocationOfResolution(resolution: ResolutionWithFailedLookupLocations) {
Debug.assert(!!resolution.refCount);
const { failedLookupLocations, affectingLocations } = resolution;
if (!failedLookupLocations?.length && !affectingLocations?.length) return;
if (failedLookupLocations?.length) resolutionsWithFailedLookups.add(resolution);
const { failedLookupLocations, affectingLocations, node10Result } = resolution;
if (!failedLookupLocations?.length && !affectingLocations?.length && !node10Result) return;
if (failedLookupLocations?.length || node10Result) resolutionsWithFailedLookups.add(resolution);
let setAtRoot = false;
if (failedLookupLocations) {
for (const failedLookupLocation of failedLookupLocations) {
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
const toWatch = getDirectoryToWatchFailedLookupLocation(
failedLookupLocation,
failedLookupLocationPath,
rootDir,
rootPath,
rootPathComponents,
getCurrentDirectory,
);
if (toWatch) {
const { dir, dirPath, nonRecursive } = toWatch;
if (dirPath === rootPath) {
Debug.assert(nonRecursive);
setAtRoot = true;
}
else {
setDirectoryWatcher(dir, dirPath, nonRecursive);
}
}
}
if (setAtRoot) {
// This is always non recursive
setDirectoryWatcher(rootDir, rootPath, /*nonRecursive*/ true); // TODO: GH#18217
setAtRoot = watchFailedLookupLocation(failedLookupLocation, setAtRoot);
}
}
watchAffectingLocationsOfResolution(resolution, !failedLookupLocations?.length);
if (node10Result) setAtRoot = watchFailedLookupLocation(node10Result, setAtRoot);
if (setAtRoot) {
// This is always non recursive
setDirectoryWatcher(rootDir, rootPath, /*nonRecursive*/ true);
}
watchAffectingLocationsOfResolution(resolution, !failedLookupLocations?.length && !node10Result);
}
function watchAffectingLocationsOfResolution(resolution: ResolutionWithFailedLookupLocations, addToResolutionsWithOnlyAffectingLocations: boolean) {
@@ -974,6 +1086,28 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
}
}
function stopWatchFailedLookupLocation(failedLookupLocation: string, removeAtRoot: boolean) {
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
const toWatch = getDirectoryToWatchFailedLookupLocation(
failedLookupLocation,
failedLookupLocationPath,
rootDir,
rootPath,
rootPathComponents,
getCurrentDirectory,
);
if (toWatch) {
const { dirPath } = toWatch;
if (dirPath === rootPath) {
removeAtRoot = true;
}
else {
removeDirectoryWatcher(dirPath);
}
}
return removeAtRoot;
}
function stopWatchFailedLookupLocationOfResolution<T extends ResolutionWithFailedLookupLocations, R extends ResolutionWithResolvedFileName>(
resolution: T,
filePath: Path,
@@ -991,32 +1125,16 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
if (resolutions?.delete(resolution) && !resolutions.size) resolvedFileToResolution.delete(key);
}
const { failedLookupLocations, affectingLocations } = resolution;
const { failedLookupLocations, affectingLocations, node10Result } = resolution;
if (resolutionsWithFailedLookups.delete(resolution)) {
let removeAtRoot = false;
for (const failedLookupLocation of failedLookupLocations!) {
const failedLookupLocationPath = resolutionHost.toPath(failedLookupLocation);
const toWatch = getDirectoryToWatchFailedLookupLocation(
failedLookupLocation,
failedLookupLocationPath,
rootDir,
rootPath,
rootPathComponents,
getCurrentDirectory,
);
if (toWatch) {
const { dirPath } = toWatch;
if (dirPath === rootPath) {
removeAtRoot = true;
}
else {
removeDirectoryWatcher(dirPath);
}
if (failedLookupLocations) {
for (const failedLookupLocation of failedLookupLocations) {
removeAtRoot = stopWatchFailedLookupLocation(failedLookupLocation, removeAtRoot);
}
}
if (removeAtRoot) {
removeDirectoryWatcher(rootPath);
}
if (node10Result) removeAtRoot = stopWatchFailedLookupLocation(node10Result, removeAtRoot);
if (removeAtRoot) removeDirectoryWatcher(rootPath);
}
else if (affectingLocations?.length) {
resolutionsWithOnlyAffectingLocations.delete(resolution);
@@ -1080,7 +1198,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
removeResolutionsOfFileFromCache(resolvedTypeReferenceDirectives, filePath, getResolvedTypeReferenceDirective);
}
function invalidateResolutions(resolutions: Set<ResolutionWithFailedLookupLocations> | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean | undefined) {
function invalidateResolutions(resolutions: Set<ResolutionWithFailedLookupLocations> | Map<string, ResolutionWithFailedLookupLocations> | undefined, canInvalidate: (resolution: ResolutionWithFailedLookupLocations) => boolean | undefined) {
if (!resolutions) return false;
let invalidated = false;
resolutions.forEach(resolution => {
@@ -1152,14 +1270,33 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
// If the invalidated file is from a node_modules package, invalidate everything else
// in the package since we might not get notifications for other files in the package.
// This hardens our logic against unreliable file watchers.
const packagePath = parseNodeModuleFromPath(fileOrDirectoryPath);
const packagePath = parseNodeModuleFromPath(fileOrDirectoryPath, /*isFolder*/ true);
if (packagePath) (startsWithPathChecks ||= new Set()).add(packagePath as Path);
}
}
resolutionHost.scheduleInvalidateResolutionsOfFailedLookupLocations();
}
function invalidatePackageJsonMap() {
const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap();
if (packageJsonMap && (failedLookupChecks || startsWithPathChecks || isInDirectoryChecks)) {
packageJsonMap.forEach((_value, path) => isInvalidatedFailedLookup(path) ? packageJsonMap.delete(path) : undefined);
}
}
function invalidateResolutionsOfFailedLookupLocations() {
if (allModuleAndTypeResolutionsAreInvalidated) {
affectingPathChecksForFile = undefined;
invalidatePackageJsonMap();
if (failedLookupChecks || startsWithPathChecks || isInDirectoryChecks || affectingPathChecks) {
invalidateResolutions(resolvedLibraries, canInvalidateFailedLookupResolution);
}
failedLookupChecks = undefined;
startsWithPathChecks = undefined;
isInDirectoryChecks = undefined;
affectingPathChecks = undefined;
return true;
}
let invalidated = false;
if (affectingPathChecksForFile) {
resolutionHost.getCurrentProgram()?.getSourceFiles().forEach(f => {
@@ -1176,10 +1313,7 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
}
invalidated = invalidateResolutions(resolutionsWithFailedLookups, canInvalidateFailedLookupResolution) || invalidated;
const packageJsonMap = moduleResolutionCache.getPackageJsonInfoCache().getInternalMap();
if (packageJsonMap && (failedLookupChecks || startsWithPathChecks || isInDirectoryChecks)) {
packageJsonMap.forEach((_value, path) => isInvalidatedFailedLookup(path) ? packageJsonMap.delete(path) : undefined);
}
invalidatePackageJsonMap();
failedLookupChecks = undefined;
startsWithPathChecks = undefined;
isInDirectoryChecks = undefined;
@@ -1191,7 +1325,8 @@ export function createResolutionCache(resolutionHost: ResolutionCacheHost, rootD
function canInvalidateFailedLookupResolution(resolution: ResolutionWithFailedLookupLocations) {
if (canInvalidatedFailedLookupResolutionWithAffectingLocation(resolution)) return true;
if (!failedLookupChecks && !startsWithPathChecks && !isInDirectoryChecks) return false;
return resolution.failedLookupLocations?.some(location => isInvalidatedFailedLookup(resolutionHost.toPath(location)));
return resolution.failedLookupLocations?.some(location => isInvalidatedFailedLookup(resolutionHost.toPath(location))) ||
(!!resolution.node10Result && isInvalidatedFailedLookup(resolutionHost.toPath(resolution.node10Result)));
}
function isInvalidatedFailedLookup(locationPath: Path) {
+2 -2
View File
@@ -28,6 +28,7 @@ import {
matchesExclude,
matchFiles,
memoize,
ModuleImportResult,
noop,
normalizePath,
normalizeSlashes,
@@ -35,7 +36,6 @@ import {
Path,
perfLogger,
PollingWatchKind,
RequireResult,
resolveJSModule,
some,
startsWith,
@@ -1428,7 +1428,7 @@ export interface System {
base64decode?(input: string): string;
base64encode?(input: string): string;
/** @internal */ bufferFrom?(input: string, encoding?: string): Buffer;
/** @internal */ require?(baseDir: string, moduleName: string): RequireResult;
/** @internal */ require?(baseDir: string, moduleName: string): ModuleImportResult;
// For testing
/** @internal */ now?(): Date;
+27 -15
View File
@@ -93,6 +93,7 @@ import {
isConstructorDeclaration,
isDestructuringAssignment,
isElementAccessExpression,
isExportOrDefaultModifier,
isExpression,
isExpressionStatement,
isForInitializer,
@@ -1843,25 +1844,13 @@ export function transformClassFields(context: TransformationContext): (x: Source
}
}
const modifiers = visitNodes(node.modifiers, modifierVisitor, isModifier);
const isExport = hasSyntacticModifier(node, ModifierFlags.Export);
const isDefault = hasSyntacticModifier(node, ModifierFlags.Default);
let modifiers = visitNodes(node.modifiers, modifierVisitor, isModifier);
const heritageClauses = visitNodes(node.heritageClauses, heritageClauseVisitor, isHeritageClause);
const { members, prologue } = transformClassMembers(node);
const classDecl = factory.updateClassDeclaration(
node,
modifiers,
node.name,
/*typeParameters*/ undefined,
heritageClauses,
members
);
const statements: Statement[] = [];
if (prologue) {
statements.push(factory.createExpressionStatement(prologue));
}
statements.push(classDecl);
if (pendingClassReferenceAssignment) {
getPendingExpressions().unshift(pendingClassReferenceAssignment);
}
@@ -1884,6 +1873,29 @@ export function transformClassFields(context: TransformationContext): (x: Source
}
}
if (statements.length > 0 && isExport && isDefault) {
modifiers = visitNodes(modifiers, node => isExportOrDefaultModifier(node) ? undefined : node, isModifier);
statements.push(factory.createExportAssignment(
/*modifiers*/ undefined,
/*isExportEquals*/ false,
factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true)
));
}
const classDecl = factory.updateClassDeclaration(
node,
modifiers,
node.name,
/*typeParameters*/ undefined,
heritageClauses,
members
);
statements.unshift(classDecl);
if (prologue) {
statements.unshift(factory.createExpressionStatement(prologue));
}
return statements;
}
-7
View File
@@ -966,13 +966,6 @@ export function transformES2015(context: TransformationContext): (x: SourceFile
statements.push(exportStatement);
}
const emitFlags = getEmitFlags(node);
if ((emitFlags & EmitFlags.HasEndOfDeclarationMarker) === 0) {
// Add a DeclarationMarker as a marker for the end of the declaration
statements.push(factory.createEndOfDeclarationMarker(node));
setEmitFlags(statement, emitFlags | EmitFlags.HasEndOfDeclarationMarker);
}
return singleOrMany(statements);
}
+7 -26
View File
@@ -767,11 +767,7 @@ export function transformES2018(context: TransformationContext): (x: SourceFile
const exitNonUserCodeStatement = factory.createExpressionStatement(exitNonUserCodeExpression);
setSourceMapRange(exitNonUserCodeStatement, node.expression);
const enterNonUserCodeExpression = factory.createAssignment(nonUserCode, factory.createTrue());
const enterNonUserCodeStatement = factory.createExpressionStatement(enterNonUserCodeExpression);
setSourceMapRange(exitNonUserCodeStatement, node.expression);
const statements: Statement[] = [];
const statements: Statement[] = [iteratorValueStatement, exitNonUserCodeStatement];
const binding = createForOfBindingStatement(factory, node.initializer, value);
statements.push(visitNode(binding, visitor, isStatement));
@@ -787,28 +783,13 @@ export function transformES2018(context: TransformationContext): (x: SourceFile
statements.push(statement);
}
const body = setEmitFlags(
setTextRange(
factory.createBlock(
setTextRange(factory.createNodeArray(statements), statementsLocation),
/*multiLine*/ true
),
bodyLocation
return setTextRange(
factory.createBlock(
setTextRange(factory.createNodeArray(statements), statementsLocation),
/*multiLine*/ true
),
EmitFlags.NoSourceMap | EmitFlags.NoTokenSourceMaps
bodyLocation
);
return factory.createBlock([
iteratorValueStatement,
exitNonUserCodeStatement,
factory.createTryStatement(
body,
/*catchClause*/ undefined,
factory.createBlock([
enterNonUserCodeStatement
])
)
]);
}
function createDownlevelAwait(expression: Expression) {
@@ -859,7 +840,7 @@ export function transformES2018(context: TransformationContext): (x: SourceFile
factory.createAssignment(done, getDone),
factory.createLogicalNot(done)
]),
/*incrementor*/ undefined,
/*incrementor*/ factory.createAssignment(nonUserCode, factory.createTrue()),
/*statement*/ convertForOfStatementHead(node, getValue, nonUserCode)
),
/*location*/ node
+3 -1
View File
@@ -640,7 +640,7 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
function transformClassLike(node: ClassLikeDeclaration, className: Expression) {
startLexicalEnvironment();
const classReference = node.name ?? factory.getGeneratedNameForNode(node);
const classReference = factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ false, /*ignoreAssignedName*/ true);
const classInfo = createClassInfo(node);
const classDefinitionStatements: Statement[] = [];
let leadingBlockStatements: Statement[] | undefined;
@@ -1013,6 +1013,8 @@ export function transformESDecorators(context: TransformationContext): (x: Sourc
factory.getInternalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true) :
factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
const varDecl = factory.createVariableDeclaration(declName, /*exclamationToken*/ undefined, /*type*/ undefined, iife);
setOriginalNode(varDecl, node);
const varDecls = factory.createVariableDeclarationList([varDecl], NodeFlags.Let);
const statement = factory.createVariableStatement(modifiers, varDecls);
setOriginalNode(statement, node);
+57 -18
View File
@@ -26,7 +26,6 @@ import {
GetAccessorDeclaration,
getAllDecoratorsOfClass,
getAllDecoratorsOfClassElement,
getEmitFlags,
getEmitScriptTarget,
getOriginalNodeId,
groupBy,
@@ -38,13 +37,16 @@ import {
isBlock,
isCallToHelper,
isClassElement,
isClassStaticBlockDeclaration,
isComputedPropertyName,
isDecorator,
isExportOrDefaultModifier,
isExpression,
isGeneratedIdentifier,
isHeritageClause,
isIdentifier,
isModifier,
isModifierLike,
isParameter,
isPrivateIdentifier,
isPropertyDeclaration,
@@ -53,6 +55,7 @@ import {
isStatic,
map,
MethodDeclaration,
Modifier,
ModifierFlags,
moveRangePastModifiers,
Node,
@@ -157,12 +160,6 @@ export function transformLegacyDecorators(context: TransformationContext): (x: S
transformClassDeclarationWithClassDecorators(node, node.name) :
transformClassDeclarationWithoutClassDecorators(node, node.name);
if (statements.length > 1) {
// Add a DeclarationMarker as a marker for the end of the declaration
statements.push(factory.createEndOfDeclarationMarker(node));
setEmitFlags(statements[0], getEmitFlags(statements[0]) | EmitFlags.HasEndOfDeclarationMarker);
}
return singleOrMany(statements);
}
@@ -321,12 +318,16 @@ export function transformLegacyDecorators(context: TransformationContext): (x: S
// ---------------------------------------------------------------------
//
const isExport = hasSyntacticModifier(node, ModifierFlags.Export);
const isDefault = hasSyntacticModifier(node, ModifierFlags.Default);
const modifiers = visitNodes(node.modifiers, node => isExportOrDefaultModifier(node) || isDecorator(node) ? undefined : node, isModifierLike);
const location = moveRangePastModifiers(node);
const classAlias = getClassAliasIfNeeded(node);
// When we transform to ES5/3 this will be moved inside an IIFE and should reference the name
// without any block-scoped variable collision handling
const declName = languageVersion <= ScriptTarget.ES2015 ?
const declName = languageVersion < ScriptTarget.ES2015 ?
factory.getInternalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true) :
factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
@@ -339,8 +340,29 @@ export function transformLegacyDecorators(context: TransformationContext): (x: S
let decorationStatements: Statement[] | undefined = [];
({ members, decorationStatements } = transformDecoratorsOfClassElements(node, members));
// If we're emitting to ES2022 or later then we need to reassign the class alias before
// static initializers are evaluated.
const assignClassAliasInStaticBlock =
languageVersion >= ScriptTarget.ES2022 &&
!!classAlias &&
some(members, member =>
isPropertyDeclaration(member) && hasSyntacticModifier(member, ModifierFlags.Static) ||
isClassStaticBlockDeclaration(member));
if (assignClassAliasInStaticBlock) {
members = setTextRange(factory.createNodeArray([
factory.createClassStaticBlockDeclaration(
factory.createBlock([
factory.createExpressionStatement(
factory.createAssignment(classAlias, factory.createThis())
)
])
),
...members
]), members);
}
const classExpression = factory.createClassExpression(
/*modifiers*/ undefined,
modifiers,
name && isGeneratedIdentifier(name) ? undefined : name,
/*typeParameters*/ undefined,
heritageClauses,
@@ -351,15 +373,23 @@ export function transformLegacyDecorators(context: TransformationContext): (x: S
// let ${name} = ${classExpression} where name is either declaredName if the class doesn't contain self-reference
// or decoratedClassAlias if the class contain self-reference.
const varDecl = factory.createVariableDeclaration(
declName,
/*exclamationToken*/ undefined,
/*type*/ undefined,
classAlias && !assignClassAliasInStaticBlock ? factory.createAssignment(classAlias, classExpression) : classExpression
);
setOriginalNode(varDecl, node);
let varModifiers: Modifier[] | undefined;
if (isExport && !isDefault) {
varModifiers = factory.createModifiersFromModifierFlags(ModifierFlags.Export);
}
const statement = factory.createVariableStatement(
/*modifiers*/ undefined,
varModifiers,
factory.createVariableDeclarationList([
factory.createVariableDeclaration(
declName,
/*exclamationToken*/ undefined,
/*type*/ undefined,
classAlias ? factory.createAssignment(classAlias, classExpression) : classExpression
)
varDecl
], NodeFlags.Let)
);
setOriginalNode(statement, node);
@@ -369,6 +399,15 @@ export function transformLegacyDecorators(context: TransformationContext): (x: S
const statements: Statement[] = [statement];
addRange(statements, decorationStatements);
addConstructorDecorationStatement(statements, node);
if (isExport && isDefault) {
statements.push(factory.createExportAssignment(
/*modifiers*/ undefined,
/*isExportEquals*/ false,
declName
));
}
return statements;
}
@@ -645,9 +684,9 @@ export function transformLegacyDecorators(context: TransformationContext): (x: S
// When we transform to ES5/3 this will be moved inside an IIFE and should reference the name
// without any block-scoped variable collision handling
const localName = languageVersion <= ScriptTarget.ES2015 ?
const localName = languageVersion < ScriptTarget.ES2015 ?
factory.getInternalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true) :
factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
factory.getDeclarationName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
const decorate = emitHelpers().createDecorateHelper(decoratorExpressions, localName);
const expression = factory.createAssignment(localName, classAlias ? factory.createAssignment(classAlias, decorate) : decorate);
setEmitFlags(expression, EmitFlags.NoComments);
+348 -132
View File
@@ -4,22 +4,28 @@ import {
addInternalEmitFlags,
addRange,
append,
arrayFrom,
ArrowFunction,
BinaryExpression,
BindingElement,
Block,
Bundle,
CallExpression,
CaseBlock,
CaseClause,
CatchClause,
chainBundle,
ClassDeclaration,
collectExternalModuleInfo,
Debug,
Declaration,
DefaultClause,
DestructuringAssignment,
DoStatement,
EmitFlags,
EmitHelper,
EmitHint,
emptyArray,
EndOfDeclarationMarker,
ExportAssignment,
ExportDeclaration,
Expression,
@@ -28,6 +34,8 @@ import {
firstOrUndefined,
flattenDestructuringAssignment,
FlattenLevel,
ForInStatement,
ForOfStatement,
ForStatement,
FunctionDeclaration,
FunctionExpression,
@@ -52,6 +60,7 @@ import {
hasSyntacticModifier,
Identifier,
idText,
IfStatement,
ImportCall,
ImportDeclaration,
ImportEqualsDeclaration,
@@ -62,6 +71,9 @@ import {
isArrowFunction,
isAssignmentOperator,
isBindingPattern,
isBlock,
isCaseBlock,
isCaseOrDefaultClause,
isClassElement,
isClassExpression,
isDeclarationNameOfEnumOrNamespace,
@@ -99,9 +111,11 @@ import {
isSpreadElement,
isStatement,
isStringLiteral,
isVariableDeclaration,
isVariableDeclarationList,
LabeledStatement,
length,
mapDefined,
MergeDeclarationMarker,
Modifier,
ModifierFlags,
ModuleKind,
@@ -123,22 +137,28 @@ import {
setTextRange,
ShorthandPropertyAssignment,
singleOrMany,
some,
SourceFile,
startOnNewLine,
Statement,
SwitchStatement,
SyntaxKind,
TaggedTemplateExpression,
TextRange,
TransformationContext,
TransformFlags,
tryGetModuleNameFromFile,
TryStatement,
VariableDeclaration,
VariableDeclarationList,
VariableStatement,
visitEachChild,
visitIterationBody,
visitNode,
visitNodes,
VisitResult,
WhileStatement,
WithStatement,
} from "../../_namespaces/ts";
/** @internal */
@@ -182,7 +202,6 @@ export function transformModule(context: TransformationContext): (x: SourceFile
context.enableEmitNotification(SyntaxKind.SourceFile); // Restore state when substituting nodes in a file.
const moduleInfoMap: ExternalModuleInfo[] = []; // The ExternalModuleInfo for each file.
const deferredExports: (Statement[] | undefined)[] = []; // Exports to defer until an EndOfDeclarationMarker is found.
let currentSourceFile: SourceFile; // The current file.
let currentModuleInfo: ExternalModuleInfo; // The ExternalModuleInfo for the current file.
@@ -660,6 +679,24 @@ export function transformModule(context: TransformationContext): (x: SourceFile
case SyntaxKind.ExportAssignment:
return visitExportAssignment(node as ExportAssignment);
case SyntaxKind.FunctionDeclaration:
return visitFunctionDeclaration(node as FunctionDeclaration);
case SyntaxKind.ClassDeclaration:
return visitClassDeclaration(node as ClassDeclaration);
default:
return topLevelNestedVisitor(node);
}
}
/**
* Visit nested elements at the top-level of a module.
*
* @param node The node to visit.
*/
function topLevelNestedVisitor(node: Node): VisitResult<Node | undefined> {
switch (node.kind) {
case SyntaxKind.VariableStatement:
return visitVariableStatement(node as VariableStatement);
@@ -669,11 +706,50 @@ export function transformModule(context: TransformationContext): (x: SourceFile
case SyntaxKind.ClassDeclaration:
return visitClassDeclaration(node as ClassDeclaration);
case SyntaxKind.MergeDeclarationMarker:
return visitMergeDeclarationMarker(node as MergeDeclarationMarker);
case SyntaxKind.ForStatement:
return visitForStatement(node as ForStatement, /*isTopLevel*/ true);
case SyntaxKind.EndOfDeclarationMarker:
return visitEndOfDeclarationMarker(node as EndOfDeclarationMarker);
case SyntaxKind.ForInStatement:
return visitForInStatement(node as ForInStatement);
case SyntaxKind.ForOfStatement:
return visitForOfStatement(node as ForOfStatement);
case SyntaxKind.DoStatement:
return visitDoStatement(node as DoStatement);
case SyntaxKind.WhileStatement:
return visitWhileStatement(node as WhileStatement);
case SyntaxKind.LabeledStatement:
return visitLabeledStatement(node as LabeledStatement);
case SyntaxKind.WithStatement:
return visitWithStatement(node as WithStatement);
case SyntaxKind.IfStatement:
return visitIfStatement(node as IfStatement);
case SyntaxKind.SwitchStatement:
return visitSwitchStatement(node as SwitchStatement);
case SyntaxKind.CaseBlock:
return visitCaseBlock(node as CaseBlock);
case SyntaxKind.CaseClause:
return visitCaseClause(node as CaseClause);
case SyntaxKind.DefaultClause:
return visitDefaultClause(node as DefaultClause);
case SyntaxKind.TryStatement:
return visitTryStatement(node as TryStatement);
case SyntaxKind.CatchClause:
return visitCatchClause(node as CatchClause);
case SyntaxKind.Block:
return visitBlock(node as Block);
default:
return visitor(node);
@@ -689,7 +765,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile
switch (node.kind) {
case SyntaxKind.ForStatement:
return visitForStatement(node as ForStatement);
return visitForStatement(node as ForStatement, /*isTopLevel*/ false);
case SyntaxKind.ExpressionStatement:
return visitExpressionStatement(node as ExpressionStatement);
case SyntaxKind.ParenthesizedExpression:
@@ -774,16 +850,232 @@ export function transformModule(context: TransformationContext): (x: SourceFile
return visitEachChild(node, visitor, context);
}
function visitForStatement(node: ForStatement) {
function visitForStatement(node: ForStatement, isTopLevel: boolean) {
if (isTopLevel && node.initializer &&
isVariableDeclarationList(node.initializer) &&
!(node.initializer.flags & NodeFlags.BlockScoped)) {
const exportStatements = appendExportsOfVariableDeclarationList(/*statements*/ undefined, node.initializer, /*isForInOrOfInitializer*/ false);
if (exportStatements) {
const statements: Statement[] = [];
const varDeclList = visitNode(node.initializer, discardedValueVisitor, isVariableDeclarationList);
const varStatement = factory.createVariableStatement(/*modifiers*/ undefined, varDeclList);
statements.push(varStatement);
addRange(statements, exportStatements);
const condition = visitNode(node.condition, visitor, isExpression);
const incrementor = visitNode(node.incrementor, discardedValueVisitor, isExpression);
const body = visitIterationBody(node.statement, isTopLevel ? topLevelNestedVisitor : visitor, context);
statements.push(factory.updateForStatement(node, /*initializer*/ undefined, condition, incrementor, body));
return statements;
}
}
return factory.updateForStatement(
node,
visitNode(node.initializer, discardedValueVisitor, isForInitializer),
visitNode(node.condition, visitor, isExpression),
visitNode(node.incrementor, discardedValueVisitor, isExpression),
visitIterationBody(node.statement, visitor, context)
visitIterationBody(node.statement, isTopLevel ? topLevelNestedVisitor : visitor, context)
);
}
/**
* Visits the body of a ForInStatement to hoist declarations.
*
* @param node The node to visit.
*/
function visitForInStatement(node: ForInStatement): VisitResult<Statement> {
if (isVariableDeclarationList(node.initializer) && !(node.initializer.flags & NodeFlags.BlockScoped)) {
const exportStatements = appendExportsOfVariableDeclarationList(/*statements*/ undefined, node.initializer, /*isForInOrOfInitializer*/ true);
if (some(exportStatements)) {
const initializer = visitNode(node.initializer, discardedValueVisitor, isForInitializer);
const expression = visitNode(node.expression, visitor, isExpression);
const body = visitIterationBody(node.statement, topLevelNestedVisitor, context);
const mergedBody = isBlock(body) ?
factory.updateBlock(body, [...exportStatements, ...body.statements]) :
factory.createBlock([...exportStatements, body], /*multiLine*/ true);
return factory.updateForInStatement(node, initializer, expression, mergedBody);
}
}
return factory.updateForInStatement(
node,
visitNode(node.initializer, discardedValueVisitor, isForInitializer),
visitNode(node.expression, visitor, isExpression),
visitIterationBody(node.statement, topLevelNestedVisitor, context)
);
}
/**
* Visits the body of a ForOfStatement to hoist declarations.
*
* @param node The node to visit.
*/
function visitForOfStatement(node: ForOfStatement): VisitResult<Statement> {
if (isVariableDeclarationList(node.initializer) && !(node.initializer.flags & NodeFlags.BlockScoped)) {
const exportStatements = appendExportsOfVariableDeclarationList(/*statements*/ undefined, node.initializer, /*isForInOrOfInitializer*/ true);
const initializer = visitNode(node.initializer, discardedValueVisitor, isForInitializer);
const expression = visitNode(node.expression, visitor, isExpression);
let body = visitIterationBody(node.statement, topLevelNestedVisitor, context);
if (some(exportStatements)) {
body = isBlock(body) ?
factory.updateBlock(body, [...exportStatements, ...body.statements]) :
factory.createBlock([...exportStatements, body], /*multiLine*/ true);
}
return factory.updateForOfStatement(node, node.awaitModifier, initializer, expression, body);
}
return factory.updateForOfStatement(
node,
node.awaitModifier,
visitNode(node.initializer, discardedValueVisitor, isForInitializer),
visitNode(node.expression, visitor, isExpression),
visitIterationBody(node.statement, topLevelNestedVisitor, context)
);
}
/**
* Visits the body of a DoStatement to hoist declarations.
*
* @param node The node to visit.
*/
function visitDoStatement(node: DoStatement): DoStatement {
return factory.updateDoStatement(
node,
visitIterationBody(node.statement, topLevelNestedVisitor, context),
visitNode(node.expression, visitor, isExpression)
);
}
/**
* Visits the body of a WhileStatement to hoist declarations.
*
* @param node The node to visit.
*/
function visitWhileStatement(node: WhileStatement): WhileStatement {
return factory.updateWhileStatement(
node,
visitNode(node.expression, visitor, isExpression),
visitIterationBody(node.statement, topLevelNestedVisitor, context)
);
}
/**
* Visits the body of a LabeledStatement to hoist declarations.
*
* @param node The node to visit.
*/
function visitLabeledStatement(node: LabeledStatement): LabeledStatement {
return factory.updateLabeledStatement(
node,
node.label,
Debug.checkDefined(visitNode(node.statement, topLevelNestedVisitor, isStatement, factory.liftToBlock))
);
}
/**
* Visits the body of a WithStatement to hoist declarations.
*
* @param node The node to visit.
*/
function visitWithStatement(node: WithStatement): WithStatement {
return factory.updateWithStatement(
node,
visitNode(node.expression, visitor, isExpression),
Debug.checkDefined(visitNode(node.statement, topLevelNestedVisitor, isStatement, factory.liftToBlock))
);
}
/**
* Visits the body of a IfStatement to hoist declarations.
*
* @param node The node to visit.
*/
function visitIfStatement(node: IfStatement): IfStatement {
return factory.updateIfStatement(
node,
visitNode(node.expression, visitor, isExpression),
Debug.checkDefined(visitNode(node.thenStatement, topLevelNestedVisitor, isStatement, factory.liftToBlock)),
visitNode(node.elseStatement, topLevelNestedVisitor, isStatement, factory.liftToBlock)
);
}
/**
* Visits the body of a SwitchStatement to hoist declarations.
*
* @param node The node to visit.
*/
function visitSwitchStatement(node: SwitchStatement): SwitchStatement {
return factory.updateSwitchStatement(
node,
visitNode(node.expression, visitor, isExpression),
Debug.checkDefined(visitNode(node.caseBlock, topLevelNestedVisitor, isCaseBlock))
);
}
/**
* Visits the body of a CaseBlock to hoist declarations.
*
* @param node The node to visit.
*/
function visitCaseBlock(node: CaseBlock): CaseBlock {
return factory.updateCaseBlock(
node,
visitNodes(node.clauses, topLevelNestedVisitor, isCaseOrDefaultClause)
);
}
/**
* Visits the body of a CaseClause to hoist declarations.
*
* @param node The node to visit.
*/
function visitCaseClause(node: CaseClause): CaseClause {
return factory.updateCaseClause(
node,
visitNode(node.expression, visitor, isExpression),
visitNodes(node.statements, topLevelNestedVisitor, isStatement)
);
}
/**
* Visits the body of a DefaultClause to hoist declarations.
*
* @param node The node to visit.
*/
function visitDefaultClause(node: DefaultClause): DefaultClause {
return visitEachChild(node, topLevelNestedVisitor, context);
}
/**
* Visits the body of a TryStatement to hoist declarations.
*
* @param node The node to visit.
*/
function visitTryStatement(node: TryStatement): TryStatement {
return visitEachChild(node, topLevelNestedVisitor, context);
}
/**
* Visits the body of a CatchClause to hoist declarations.
*
* @param node The node to visit.
*/
function visitCatchClause(node: CatchClause): CatchClause {
return factory.updateCatchClause(
node,
node.variableDeclaration,
Debug.checkDefined(visitNode(node.block, topLevelNestedVisitor, isBlock))
);
}
/**
* Visits the body of a Block to hoist declarations.
*
* @param node The node to visit.
*/
function visitBlock(node: Block): Block {
node = visitEachChild(node, topLevelNestedVisitor, context);
return node;
}
function visitExpressionStatement(node: ExpressionStatement) {
return factory.updateExpressionStatement(
node,
@@ -1154,15 +1446,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile
);
}
if (hasAssociatedEndOfDeclarationMarker(node)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportsOfImportDeclaration(deferredExports[id], node);
}
else {
statements = appendExportsOfImportDeclaration(statements, node);
}
statements = appendExportsOfImportDeclaration(statements, node);
return singleOrMany(statements);
}
@@ -1245,15 +1529,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile
}
}
if (hasAssociatedEndOfDeclarationMarker(node)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportsOfImportEqualsDeclaration(deferredExports[id], node);
}
else {
statements = appendExportsOfImportEqualsDeclaration(statements, node);
}
statements = appendExportsOfImportEqualsDeclaration(statements, node);
return singleOrMany(statements);
}
@@ -1377,18 +1653,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile
return undefined;
}
let statements: Statement[] | undefined;
const original = node.original;
if (original && hasAssociatedEndOfDeclarationMarker(original)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportStatement(deferredExports[id], factory.createIdentifier("default"), visitNode(node.expression, visitor, isExpression), /*location*/ node, /*allowComments*/ true);
}
else {
statements = appendExportStatement(statements, factory.createIdentifier("default"), visitNode(node.expression, visitor, isExpression), /*location*/ node, /*allowComments*/ true);
}
return singleOrMany(statements);
return createExportStatement(factory.createIdentifier("default"), visitNode(node.expression, visitor, isExpression), /*location*/ node, /*allowComments*/ true);
}
/**
@@ -1421,15 +1686,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile
statements = append(statements, visitEachChild(node, visitor, context));
}
if (hasAssociatedEndOfDeclarationMarker(node)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node);
}
else {
statements = appendExportsOfHoistedDeclaration(statements, node);
}
statements = appendExportsOfHoistedDeclaration(statements, node);
return singleOrMany(statements);
}
@@ -1461,15 +1718,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile
statements = append(statements, visitEachChild(node, visitor, context));
}
if (hasAssociatedEndOfDeclarationMarker(node)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node);
}
else {
statements = appendExportsOfHoistedDeclaration(statements, node);
}
statements = appendExportsOfHoistedDeclaration(statements, node);
return singleOrMany(statements);
}
@@ -1560,15 +1809,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile
statements = append(statements, visitEachChild(node, visitor, context));
}
if (hasAssociatedEndOfDeclarationMarker(node)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportsOfVariableStatement(deferredExports[id], node);
}
else {
statements = appendExportsOfVariableStatement(statements, node);
}
statements = appendExportsOfVariableStatement(statements, node);
return singleOrMany(statements);
}
@@ -1618,57 +1859,6 @@ export function transformModule(context: TransformationContext): (x: SourceFile
}
}
/**
* Visits a MergeDeclarationMarker used as a placeholder for the beginning of a merged
* and transformed declaration.
*
* @param node The node to visit.
*/
function visitMergeDeclarationMarker(node: MergeDeclarationMarker): VisitResult<Statement> {
// For an EnumDeclaration or ModuleDeclaration that merges with a preceeding
// declaration we do not emit a leading variable declaration. To preserve the
// begin/end semantics of the declararation and to properly handle exports
// we wrapped the leading variable declaration in a `MergeDeclarationMarker`.
//
// To balance the declaration, add the exports of the elided variable
// statement.
if (hasAssociatedEndOfDeclarationMarker(node) && node.original!.kind === SyntaxKind.VariableStatement) {
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportsOfVariableStatement(deferredExports[id], node.original as VariableStatement);
}
return node;
}
/**
* Determines whether a node has an associated EndOfDeclarationMarker.
*
* @param node The node to test.
*/
function hasAssociatedEndOfDeclarationMarker(node: Node) {
return (getEmitFlags(node) & EmitFlags.HasEndOfDeclarationMarker) !== 0;
}
/**
* Visits a DeclarationMarker used as a placeholder for the end of a transformed
* declaration.
*
* @param node The node to visit.
*/
function visitEndOfDeclarationMarker(node: EndOfDeclarationMarker): VisitResult<Statement> {
// For some transformations we emit an `EndOfDeclarationMarker` to mark the actual
// end of the transformed declaration. We use this marker to emit any deferred exports
// of the declaration.
const id = getOriginalNodeId(node);
const statements = deferredExports[id];
if (statements) {
delete deferredExports[id];
return append(statements, node);
}
return node;
}
/**
* Appends the exports of an ImportDeclaration to a statement list, returning the
* statement list.
@@ -1738,12 +1928,25 @@ export function transformModule(context: TransformationContext): (x: SourceFile
* @param node The VariableStatement whose exports are to be recorded.
*/
function appendExportsOfVariableStatement(statements: Statement[] | undefined, node: VariableStatement): Statement[] | undefined {
return appendExportsOfVariableDeclarationList(statements, node.declarationList, /*isForInOrOfInitializer*/ false);
}
/**
* Appends the exports of a VariableDeclarationList to a statement list, returning the statement
* list.
*
* @param statements A statement list to which the down-level export statements are to be
* appended. If `statements` is `undefined`, a new array is allocated if statements are
* appended.
* @param node The VariableDeclarationList whose exports are to be recorded.
*/
function appendExportsOfVariableDeclarationList(statements: Statement[] | undefined, node: VariableDeclarationList, isForInOrOfInitializer: boolean): Statement[] | undefined {
if (currentModuleInfo.exportEquals) {
return statements;
}
for (const decl of node.declarationList.declarations) {
statements = appendExportsOfBindingElement(statements, decl);
for (const decl of node.declarations) {
statements = appendExportsOfBindingElement(statements, decl, isForInOrOfInitializer);
}
return statements;
@@ -1758,7 +1961,7 @@ export function transformModule(context: TransformationContext): (x: SourceFile
* appended.
* @param decl The declaration whose exports are to be recorded.
*/
function appendExportsOfBindingElement(statements: Statement[] | undefined, decl: VariableDeclaration | BindingElement): Statement[] | undefined {
function appendExportsOfBindingElement(statements: Statement[] | undefined, decl: VariableDeclaration | BindingElement, isForInOrOfInitializer: boolean): Statement[] | undefined {
if (currentModuleInfo.exportEquals) {
return statements;
}
@@ -1766,11 +1969,11 @@ export function transformModule(context: TransformationContext): (x: SourceFile
if (isBindingPattern(decl.name)) {
for (const element of decl.name.elements) {
if (!isOmittedExpression(element)) {
statements = appendExportsOfBindingElement(statements, element);
statements = appendExportsOfBindingElement(statements, element, isForInOrOfInitializer);
}
}
}
else if (!isGeneratedIdentifier(decl.name)) {
else if (!isGeneratedIdentifier(decl.name) && (!isVariableDeclaration(decl) || decl.initializer || isForInOrOfInitializer)) {
statements = appendExportsOfDeclaration(statements, decl);
}
@@ -2140,14 +2343,11 @@ export function transformModule(context: TransformationContext): (x: SourceFile
//
// - We do not substitute generated identifiers for any reason.
// - We do not substitute identifiers tagged with the LocalName flag.
// - We do not substitute identifiers that were originally the name of an enum or
// namespace due to how they are transformed in TypeScript.
// - We only substitute identifiers that are exported at the top level.
if (isAssignmentOperator(node.operatorToken.kind)
&& isIdentifier(node.left)
&& !isGeneratedIdentifier(node.left)
&& !isLocalName(node.left)
&& !isDeclarationNameOfEnumOrNamespace(node.left)) {
&& !isLocalName(node.left)) {
const exportedNames = getExports(node.left);
if (exportedNames) {
// For each additional export of the declaration, apply an export assignment.
@@ -2172,11 +2372,27 @@ export function transformModule(context: TransformationContext): (x: SourceFile
*/
function getExports(name: Identifier): Identifier[] | undefined {
if (!isGeneratedIdentifier(name)) {
const valueDeclaration = resolver.getReferencedImportDeclaration(name)
|| resolver.getReferencedValueDeclaration(name);
if (valueDeclaration) {
return currentModuleInfo
&& currentModuleInfo.exportedBindings[getOriginalNodeId(valueDeclaration)];
const importDeclaration = resolver.getReferencedImportDeclaration(name);
if (importDeclaration) {
return currentModuleInfo?.exportedBindings[getOriginalNodeId(importDeclaration)];
}
// An exported namespace or enum may merge with an ambient declaration, which won't show up in .js emit, so
// we analyze all value exports of a symbol.
const bindingsSet = new Set<Identifier>();
const declarations = resolver.getReferencedValueDeclarations(name);
if (declarations) {
for (const declaration of declarations) {
const bindings = currentModuleInfo?.exportedBindings[getOriginalNodeId(declaration)];
if (bindings) {
for (const binding of bindings) {
bindingsSet.add(binding);
}
}
}
if (bindingsSet.size) {
return arrayFrom(bindingsSet);
}
}
}
}
+54 -144
View File
@@ -19,7 +19,6 @@ import {
DoStatement,
EmitFlags,
EmitHint,
EndOfDeclarationMarker,
ExportAssignment,
ExportDeclaration,
Expression,
@@ -46,6 +45,7 @@ import {
hasSyntacticModifier,
Identifier,
idText,
IfStatement,
ImportCall,
ImportDeclaration,
ImportEqualsDeclaration,
@@ -74,7 +74,6 @@ import {
isImportSpecifier,
isLocalName,
isModifierLike,
isModuleOrEnumDeclaration,
isNamedExports,
isObjectLiteralExpression,
isOmittedExpression,
@@ -88,7 +87,6 @@ import {
isVariableDeclarationList,
LabeledStatement,
map,
MergeDeclarationMarker,
MetaProperty,
ModifierFlags,
moveEmitHelpers,
@@ -158,7 +156,6 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
context.enableEmitNotification(SyntaxKind.SourceFile); // Restore state when substituting nodes in a file.
const moduleInfoMap: ExternalModuleInfo[] = []; // The ExternalModuleInfo for each file.
const deferredExports: (Statement[] | undefined)[] = []; // Exports to defer until an EndOfDeclarationMarker is found.
const exportFunctionsMap: Identifier[] = []; // The export function associated with a source file.
const noSubstitutionMap: boolean[][] = []; // Set of nodes for which substitution rules should be ignored for each file.
const contextObjectMap: Identifier[] = []; // The context object associated with a source file.
@@ -737,17 +734,7 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
if (node.importClause) {
hoistVariableDeclaration(getLocalNameForExternalImport(factory, node, currentSourceFile)!); // TODO: GH#18217
}
if (hasAssociatedEndOfDeclarationMarker(node)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportsOfImportDeclaration(deferredExports[id], node);
}
else {
statements = appendExportsOfImportDeclaration(statements, node);
}
return singleOrMany(statements);
return singleOrMany(appendExportsOfImportDeclaration(statements, node));
}
function visitExportDeclaration(node: ExportDeclaration): VisitResult<Statement | undefined> {
@@ -765,17 +752,7 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
let statements: Statement[] | undefined;
hoistVariableDeclaration(getLocalNameForExternalImport(factory, node, currentSourceFile)!); // TODO: GH#18217
if (hasAssociatedEndOfDeclarationMarker(node)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportsOfImportEqualsDeclaration(deferredExports[id], node);
}
else {
statements = appendExportsOfImportEqualsDeclaration(statements, node);
}
return singleOrMany(statements);
return singleOrMany(appendExportsOfImportEqualsDeclaration(statements, node));
}
/**
@@ -790,15 +767,7 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
}
const expression = visitNode(node.expression, visitor, isExpression);
const original = node.original;
if (original && hasAssociatedEndOfDeclarationMarker(original)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportStatement(deferredExports[id], factory.createIdentifier("default"), expression, /*allowComments*/ true);
}
else {
return createExportStatement(factory.createIdentifier("default"), expression, /*allowComments*/ true);
}
return createExportStatement(factory.createIdentifier("default"), expression, /*allowComments*/ true);
}
/**
@@ -823,15 +792,7 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
hoistedStatements = append(hoistedStatements, visitEachChild(node, visitor, context));
}
if (hasAssociatedEndOfDeclarationMarker(node)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node);
}
else {
hoistedStatements = appendExportsOfHoistedDeclaration(hoistedStatements, node);
}
hoistedStatements = appendExportsOfHoistedDeclaration(hoistedStatements, node);
return undefined;
}
@@ -869,15 +830,7 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
)
);
if (hasAssociatedEndOfDeclarationMarker(node)) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportsOfHoistedDeclaration(deferredExports[id], node);
}
else {
statements = appendExportsOfHoistedDeclaration(statements, node);
}
statements = appendExportsOfHoistedDeclaration(statements, node);
return singleOrMany(statements);
}
@@ -894,10 +847,9 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
let expressions: Expression[] | undefined;
const isExportedDeclaration = hasSyntacticModifier(node, ModifierFlags.Export);
const isMarkedDeclaration = hasAssociatedEndOfDeclarationMarker(node);
for (const variable of node.declarationList.declarations) {
if (variable.initializer) {
expressions = append(expressions, transformInitializedVariable(variable, isExportedDeclaration && !isMarkedDeclaration));
expressions = append(expressions, transformInitializedVariable(variable, isExportedDeclaration));
}
else {
hoistBindingElement(variable);
@@ -909,15 +861,7 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
statements = append(statements, setTextRange(factory.createExpressionStatement(factory.inlineExpressions(expressions)), node));
}
if (isMarkedDeclaration) {
// Defer exports until we encounter an EndOfDeclarationMarker node
const id = getOriginalNodeId(node);
deferredExports[id] = appendExportsOfVariableStatement(deferredExports[id], node, isExportedDeclaration);
}
else {
statements = appendExportsOfVariableStatement(statements, node, /*exportSelf*/ false);
}
statements = appendExportsOfVariableStatement(statements, node, /*exportSelf*/ false);
return singleOrMany(statements);
}
@@ -1008,64 +952,6 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
: preventSubstitution(setTextRange(factory.createAssignment(name, value), location));
}
/**
* Visits a MergeDeclarationMarker used as a placeholder for the beginning of a merged
* and transformed declaration.
*
* @param node The node to visit.
*/
function visitMergeDeclarationMarker(node: MergeDeclarationMarker): VisitResult<Statement> {
// For an EnumDeclaration or ModuleDeclaration that merges with a preceeding
// declaration we do not emit a leading variable declaration. To preserve the
// begin/end semantics of the declararation and to properly handle exports
// we wrapped the leading variable declaration in a `MergeDeclarationMarker`.
//
// To balance the declaration, we defer the exports of the elided variable
// statement until we visit this declaration's `EndOfDeclarationMarker`.
if (hasAssociatedEndOfDeclarationMarker(node) && node.original!.kind === SyntaxKind.VariableStatement) {
const id = getOriginalNodeId(node);
const isExportedDeclaration = hasSyntacticModifier(node.original!, ModifierFlags.Export);
deferredExports[id] = appendExportsOfVariableStatement(deferredExports[id], node.original as VariableStatement, isExportedDeclaration);
}
return node;
}
/**
* Determines whether a node has an associated EndOfDeclarationMarker.
*
* @param node The node to test.
*/
function hasAssociatedEndOfDeclarationMarker(node: Node) {
return (getEmitFlags(node) & EmitFlags.HasEndOfDeclarationMarker) !== 0;
}
/**
* Visits a DeclarationMarker used as a placeholder for the end of a transformed
* declaration.
*
* @param node The node to visit.
*/
function visitEndOfDeclarationMarker(node: EndOfDeclarationMarker): VisitResult<Statement> {
// For some transformations we emit an `EndOfDeclarationMarker` to mark the actual
// end of the transformed declaration. We use this marker to emit any deferred exports
// of the declaration.
const id = getOriginalNodeId(node);
const statements = deferredExports[id];
if (statements) {
delete deferredExports[id];
return append(statements, node);
}
else {
const original = getOriginalNode(node);
if (isModuleOrEnumDeclaration(original)) {
return append(appendExportsOfDeclaration(statements, original), node);
}
}
return node;
}
/**
* Appends the exports of an ImportDeclaration to a statement list, returning the
* statement list.
@@ -1325,6 +1211,9 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
case SyntaxKind.WithStatement:
return visitWithStatement(node as WithStatement);
case SyntaxKind.IfStatement:
return visitIfStatement(node as IfStatement);
case SyntaxKind.SwitchStatement:
return visitSwitchStatement(node as SwitchStatement);
@@ -1346,12 +1235,6 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
case SyntaxKind.Block:
return visitBlock(node as Block);
case SyntaxKind.MergeDeclarationMarker:
return visitMergeDeclarationMarker(node as MergeDeclarationMarker);
case SyntaxKind.EndOfDeclarationMarker:
return visitEndOfDeclarationMarker(node as EndOfDeclarationMarker);
default:
return visitor(node);
}
@@ -1504,6 +1387,20 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
);
}
/**
* Visits the body of a IfStatement to hoist declarations.
*
* @param node The node to visit.
*/
function visitIfStatement(node: IfStatement): VisitResult<Statement> {
return factory.updateIfStatement(
node,
visitNode(node.expression, visitor, isExpression),
Debug.checkDefined(visitNode(node.thenStatement, topLevelNestedVisitor, isStatement, factory.liftToBlock)),
visitNode(node.elseStatement, topLevelNestedVisitor, isStatement, factory.liftToBlock)
);
}
/**
* Visits the body of a SwitchStatement to hoist declarations.
*
@@ -1999,14 +1896,11 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
//
// - We do not substitute generated identifiers for any reason.
// - We do not substitute identifiers tagged with the LocalName flag.
// - We do not substitute identifiers that were originally the name of an enum or
// namespace due to how they are transformed in TypeScript.
// - We only substitute identifiers that are exported at the top level.
if (isAssignmentOperator(node.operatorToken.kind)
&& isIdentifier(node.left)
&& !isGeneratedIdentifier(node.left)
&& !isLocalName(node.left)
&& !isDeclarationNameOfEnumOrNamespace(node.left)) {
&& !isLocalName(node.left)) {
const exportedNames = getExports(node.left);
if (exportedNames) {
// For each additional export of the declaration, apply an export assignment.
@@ -2036,23 +1930,39 @@ export function transformSystemModule(context: TransformationContext): (x: Sourc
*/
function getExports(name: Identifier) {
let exportedNames: Identifier[] | undefined;
if (!isGeneratedIdentifier(name)) {
const valueDeclaration = resolver.getReferencedImportDeclaration(name)
|| resolver.getReferencedValueDeclaration(name);
if (valueDeclaration) {
const exportContainer = resolver.getReferencedExportContainer(name, /*prefixLocals*/ false);
if (exportContainer && exportContainer.kind === SyntaxKind.SourceFile) {
exportedNames = append(exportedNames, factory.getDeclarationName(valueDeclaration));
}
exportedNames = addRange(exportedNames, moduleInfo && moduleInfo.exportedBindings[getOriginalNodeId(valueDeclaration)]);
const valueDeclaration = getReferencedDeclaration(name);
if (valueDeclaration) {
const exportContainer = resolver.getReferencedExportContainer(name, /*prefixLocals*/ false);
if (exportContainer && exportContainer.kind === SyntaxKind.SourceFile) {
exportedNames = append(exportedNames, factory.getDeclarationName(valueDeclaration));
}
}
exportedNames = addRange(exportedNames, moduleInfo?.exportedBindings[getOriginalNodeId(valueDeclaration)]);
}
return exportedNames;
}
function getReferencedDeclaration(name: Identifier) {
if (!isGeneratedIdentifier(name)) {
const importDeclaration = resolver.getReferencedImportDeclaration(name);
if (importDeclaration) return importDeclaration;
const valueDeclaration = resolver.getReferencedValueDeclaration(name);
if (valueDeclaration && moduleInfo?.exportedBindings[getOriginalNodeId(valueDeclaration)]) return valueDeclaration;
// An exported namespace or enum may merge with an ambient declaration, which won't show up in
// .js emit. When that happens, try to find bindings associated with a non-ambient declaration.
const declarations = resolver.getReferencedValueDeclarations(name);
if (declarations) {
for (const declaration of declarations) {
if (declaration !== valueDeclaration && moduleInfo?.exportedBindings[getOriginalNodeId(declaration)]) return declaration;
}
}
return valueDeclaration;
}
}
/**
* Prevent substitution of a node for this transformer.
*
+28 -60
View File
@@ -846,9 +846,7 @@ export function transformTypeScript(context: TransformationContext) {
const moveModifiers =
promoteToIIFE ||
facts & ClassFacts.IsExportOfNamespace ||
facts & ClassFacts.HasClassOrConstructorParameterDecorators && legacyDecorators ||
facts & ClassFacts.HasStaticInitializedProperties;
facts & ClassFacts.IsExportOfNamespace;
// elide modifiers on the declaration if we are emitting an IIFE or the class is
// a namespace export
@@ -954,34 +952,28 @@ export function transformTypeScript(context: TransformationContext) {
if (moveModifiers) {
if (facts & ClassFacts.IsExportOfNamespace) {
return demarcateMultiStatementExport(
return [
statement,
createExportMemberAssignmentStatement(node));
createExportMemberAssignmentStatement(node)
];
}
if (facts & ClassFacts.IsDefaultExternalExport) {
return demarcateMultiStatementExport(
return [
statement,
factory.createExportDefault(factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true)));
factory.createExportDefault(factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true))
];
}
if (facts & ClassFacts.IsNamedExternalExport && !promoteToIIFE) {
return demarcateMultiStatementExport(
return [
statement,
factory.createExternalModuleExport(factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true)));
factory.createExternalModuleExport(factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true))
];
}
}
return statement;
}
function demarcateMultiStatementExport(declarationStatement: Statement, exportStatement: Statement) {
addEmitFlags(declarationStatement, EmitFlags.HasEndOfDeclarationMarker);
return [
declarationStatement,
exportStatement,
factory.createEndOfDeclarationMarker(declarationStatement)
];
}
function visitClassExpression(node: ClassExpression): Expression {
let modifiers = visitNodes(node.modifiers, modifierElidingVisitor, isModifierLike);
if (classOrConstructorParameterIsDecorated(legacyDecorators, node)) {
@@ -1762,9 +1754,9 @@ export function transformTypeScript(context: TransformationContext) {
const containerName = getNamespaceContainerName(node);
// `exportName` is the expression used within this node's container for any exported references.
const exportName = hasSyntacticModifier(node, ModifierFlags.Export)
const exportName = isExportOfNamespace(node)
? factory.getExternalModuleOrNamespaceExportName(currentNamespaceContainerName, node, /*allowComments*/ false, /*allowSourceMaps*/ true)
: factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
: factory.getDeclarationName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
// x || (x = {})
// exports.x || (exports.x = {})
@@ -1777,7 +1769,7 @@ export function transformTypeScript(context: TransformationContext) {
)
);
if (hasNamespaceQualifiedExportName(node)) {
if (isExportOfNamespace(node)) {
// `localName` is the expression used within this node's containing scope for any local references.
const localName = factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
@@ -1815,9 +1807,6 @@ export function transformTypeScript(context: TransformationContext) {
addEmitFlags(enumStatement, emitFlags);
statements.push(enumStatement);
// Add a DeclarationMarker for the enum to preserve trailing comments and mark
// the end of the declaration.
statements.push(factory.createEndOfDeclarationMarker(node));
return statements;
}
@@ -1916,20 +1905,6 @@ export function transformTypeScript(context: TransformationContext) {
return isInstantiatedModule(node, shouldPreserveConstEnums(compilerOptions));
}
/**
* Determines whether an exported declaration will have a qualified export name (e.g. `f.x`
* or `exports.x`).
*/
function hasNamespaceQualifiedExportName(node: Node) {
return isExportOfNamespace(node)
|| (isExternalModuleExport(node)
&& moduleKind !== ModuleKind.ES2015
&& moduleKind !== ModuleKind.ES2020
&& moduleKind !== ModuleKind.ES2022
&& moduleKind !== ModuleKind.ESNext
&& moduleKind !== ModuleKind.System);
}
/**
* Records that a declaration was emitted in the current scope, if it was the first
* declaration for the provided symbol.
@@ -1969,15 +1944,17 @@ export function transformTypeScript(context: TransformationContext) {
// Emit a variable statement for the module. We emit top-level enums as a `var`
// declaration to avoid static errors in global scripts scripts due to redeclaration.
// enums in any other scope are emitted as a `let` declaration.
const varDecl = factory.createVariableDeclaration(factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true));
const varFlags = currentLexicalScope.kind === SyntaxKind.SourceFile ? NodeFlags.None : NodeFlags.Let;
const statement = factory.createVariableStatement(
visitNodes(node.modifiers, modifierVisitor, isModifier),
factory.createVariableDeclarationList([
factory.createVariableDeclaration(
factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true)
)
], currentLexicalScope.kind === SyntaxKind.SourceFile ? NodeFlags.None : NodeFlags.Let)
factory.createVariableDeclarationList([varDecl], varFlags)
);
setOriginalNode(varDecl, node);
setSyntheticLeadingComments(varDecl, undefined);
setSyntheticTrailingComments(varDecl, undefined);
setOriginalNode(statement, node);
recordEmittedDeclarationInScope(node);
@@ -2009,20 +1986,14 @@ export function transformTypeScript(context: TransformationContext) {
// })(m1 || (m1 = {})); // trailing comment module
//
setCommentRange(statement, node);
addEmitFlags(statement, EmitFlags.NoTrailingComments | EmitFlags.HasEndOfDeclarationMarker);
addEmitFlags(statement, EmitFlags.NoTrailingComments);
statements.push(statement);
return true;
}
else {
// For an EnumDeclaration or ModuleDeclaration that merges with a preceeding
// declaration we do not emit a leading variable declaration. To preserve the
// begin/end semantics of the declararation and to properly handle exports
// we wrap the leading variable declaration in a `MergeDeclarationMarker`.
const mergeMarker = factory.createMergeDeclarationMarker(statement);
setEmitFlags(mergeMarker, EmitFlags.NoComments | EmitFlags.HasEndOfDeclarationMarker);
statements.push(mergeMarker);
return false;
}
// For an EnumDeclaration or ModuleDeclaration that merges with a preceeding
// declaration we do not emit a leading variable declaration.
return false;
}
/**
@@ -2064,9 +2035,9 @@ export function transformTypeScript(context: TransformationContext) {
const containerName = getNamespaceContainerName(node);
// `exportName` is the expression used within this node's container for any exported references.
const exportName = hasSyntacticModifier(node, ModifierFlags.Export)
const exportName = isExportOfNamespace(node)
? factory.getExternalModuleOrNamespaceExportName(currentNamespaceContainerName, node, /*allowComments*/ false, /*allowSourceMaps*/ true)
: factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
: factory.getDeclarationName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
// x || (x = {})
// exports.x || (exports.x = {})
@@ -2079,7 +2050,7 @@ export function transformTypeScript(context: TransformationContext) {
)
);
if (hasNamespaceQualifiedExportName(node)) {
if (isExportOfNamespace(node)) {
// `localName` is the expression used within this node's containing scope for any local references.
const localName = factory.getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true);
@@ -2116,9 +2087,6 @@ export function transformTypeScript(context: TransformationContext) {
addEmitFlags(moduleStatement, emitFlags);
statements.push(moduleStatement);
// Add a DeclarationMarker for the namespace to preserve trailing comments and mark
// the end of the declaration.
statements.push(factory.createEndOfDeclarationMarker(node));
return statements;
}
+7 -3
View File
@@ -52,6 +52,7 @@ import {
isGeneratedPrivateIdentifier,
isIdentifier,
isKeyword,
isLocalName,
isMethodOrAccessor,
isNamedExports,
isNamedImports,
@@ -236,7 +237,7 @@ export function collectExternalModuleInfo(context: TransformationContext, source
case SyntaxKind.VariableStatement:
if (hasSyntacticModifier(node, ModifierFlags.Export)) {
for (const decl of (node as VariableStatement).declarationList.declarations) {
exportedNames = collectExportedVariableInfo(decl, uniqueExports, exportedNames);
exportedNames = collectExportedVariableInfo(decl, uniqueExports, exportedNames, exportedBindings);
}
}
break;
@@ -314,11 +315,11 @@ export function collectExternalModuleInfo(context: TransformationContext, source
}
}
function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: Map<string, boolean>, exportedNames: Identifier[] | undefined) {
function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement, uniqueExports: Map<string, boolean>, exportedNames: Identifier[] | undefined, exportedBindings: Identifier[][]) {
if (isBindingPattern(decl.name)) {
for (const element of decl.name.elements) {
if (!isOmittedExpression(element)) {
exportedNames = collectExportedVariableInfo(element, uniqueExports, exportedNames);
exportedNames = collectExportedVariableInfo(element, uniqueExports, exportedNames, exportedBindings);
}
}
}
@@ -327,6 +328,9 @@ function collectExportedVariableInfo(decl: VariableDeclaration | BindingElement,
if (!uniqueExports.get(text)) {
uniqueExports.set(text, true);
exportedNames = append(exportedNames, decl.name);
if (isLocalName(decl.name)) {
multiMapSparseArrayAdd(exportedBindings, getOriginalNodeId(decl), decl.name);
}
}
}
return exportedNames;
+17 -1
View File
@@ -99,6 +99,7 @@ import {
ReadBuildProgramHost,
resolveConfigFileProjectName,
ResolvedConfigFileName,
resolveLibrary,
resolvePath,
resolveProjectReferencePath,
returnUndefined,
@@ -394,6 +395,7 @@ interface SolutionBuilderState<T extends BuilderProgram> extends WatchFactory<Wa
readonly compilerHost: CompilerHost & ReadBuildProgramHost;
readonly moduleResolutionCache: ModuleResolutionCache | undefined;
readonly typeReferenceDirectiveResolutionCache: TypeReferenceDirectiveResolutionCache | undefined;
readonly libraryResolutionCache: ModuleResolutionCache | undefined;
// Mutable state
buildOrder: AnyBuildOrder | undefined;
@@ -432,6 +434,7 @@ function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, ho
compilerHost.getParsedCommandLine = fileName => parseConfigFile(state, fileName as ResolvedConfigFileName, toResolvedConfigFilePath(state, fileName as ResolvedConfigFileName));
compilerHost.resolveModuleNameLiterals = maybeBind(host, host.resolveModuleNameLiterals);
compilerHost.resolveTypeReferenceDirectiveReferences = maybeBind(host, host.resolveTypeReferenceDirectiveReferences);
compilerHost.resolveLibrary = maybeBind(host, host.resolveLibrary);
compilerHost.resolveModuleNames = maybeBind(host, host.resolveModuleNames);
compilerHost.resolveTypeReferenceDirectives = maybeBind(host, host.resolveTypeReferenceDirectives);
compilerHost.getModuleResolutionCache = maybeBind(host, host.getModuleResolutionCache);
@@ -465,6 +468,17 @@ function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, ho
createTypeReferenceResolutionLoader,
);
}
let libraryResolutionCache: ModuleResolutionCache | undefined;
if (!compilerHost.resolveLibrary) {
libraryResolutionCache = createModuleResolutionCache(compilerHost.getCurrentDirectory(), compilerHost.getCanonicalFileName, /*options*/ undefined, moduleResolutionCache?.getPackageJsonInfoCache());
compilerHost.resolveLibrary = (libraryName, resolveFrom, options) => resolveLibrary(
libraryName,
resolveFrom,
options,
host,
libraryResolutionCache,
);
}
compilerHost.getBuildInfo = (fileName, configFilePath) => getBuildInfo(state, fileName, toResolvedConfigFilePath(state, configFilePath as ResolvedConfigFileName), /*modifiedTime*/ undefined);
const { watchFile, watchDirectory, writeLog } = createWatchFactory<ResolvedConfigFileName>(hostWithWatch, options);
@@ -496,6 +510,7 @@ function createSolutionBuilderState<T extends BuilderProgram>(watch: boolean, ho
compilerHost,
moduleResolutionCache,
typeReferenceDirectiveResolutionCache,
libraryResolutionCache,
// Mutable state
buildOrder: undefined,
@@ -747,7 +762,7 @@ function enableCache<T extends BuilderProgram>(state: SolutionBuilderState<T>) {
function disableCache<T extends BuilderProgram>(state: SolutionBuilderState<T>) {
if (!state.cache) return;
const { cache, host, compilerHost, extendedConfigCache, moduleResolutionCache, typeReferenceDirectiveResolutionCache } = state;
const { cache, host, compilerHost, extendedConfigCache, moduleResolutionCache, typeReferenceDirectiveResolutionCache, libraryResolutionCache } = state;
host.readFile = cache.originalReadFile;
host.fileExists = cache.originalFileExists;
@@ -759,6 +774,7 @@ function disableCache<T extends BuilderProgram>(state: SolutionBuilderState<T>)
extendedConfigCache.clear();
moduleResolutionCache?.clear();
typeReferenceDirectiveResolutionCache?.clear();
libraryResolutionCache?.clear();
state.cache = undefined;
}
+56 -36
View File
@@ -451,8 +451,6 @@ export const enum SyntaxKind {
NotEmittedStatement,
PartiallyEmittedExpression,
CommaListExpression,
MergeDeclarationMarker,
EndOfDeclarationMarker,
SyntheticReferenceExpression,
// Enum value count
@@ -1221,6 +1219,7 @@ export type HasJSDoc =
| PropertyDeclaration
| PropertySignature
| ReturnStatement
| SemicolonClassElement
| ShorthandPropertyAssignment
| SpreadAssignment
| SwitchStatement
@@ -1723,11 +1722,9 @@ export type PropertyName = Identifier | StringLiteral | NumericLiteral | Compute
export type MemberName = Identifier | PrivateIdentifier;
export type DeclarationName =
| Identifier
| PrivateIdentifier
| PropertyName
| JsxAttributeName
| StringLiteralLike
| NumericLiteral
| ComputedPropertyName
| ElementAccessExpression
| BindingPattern
| EntityNameExpression;
@@ -2103,7 +2100,7 @@ export interface ConstructorDeclaration extends FunctionLikeDeclarationBase, Cla
}
/** For when we encounter a semicolon in a class declaration. ES6 allows these as class elements. */
export interface SemicolonClassElement extends ClassElement {
export interface SemicolonClassElement extends ClassElement, JSDocContainer {
readonly kind: SyntaxKind.SemicolonClassElement;
readonly parent: ClassLikeDeclaration;
}
@@ -2333,7 +2330,7 @@ export interface LiteralTypeNode extends TypeNode {
export interface StringLiteral extends LiteralExpression, Declaration {
readonly kind: SyntaxKind.StringLiteral;
/** @internal */ readonly textSourceNode?: Identifier | StringLiteralLike | NumericLiteral | PrivateIdentifier; // Allows a StringLiteral to get its text from another node (used by transforms).
/** @internal */ readonly textSourceNode?: Identifier | StringLiteralLike | NumericLiteral | PrivateIdentifier | JsxNamespacedName; // Allows a StringLiteral to get its text from another node (used by transforms).
/**
* Note: this is only set when synthesizing a node, not during parsing.
*
@@ -2343,7 +2340,7 @@ export interface StringLiteral extends LiteralExpression, Declaration {
}
export type StringLiteralLike = StringLiteral | NoSubstitutionTemplateLiteral;
export type PropertyNameLiteral = Identifier | StringLiteralLike | NumericLiteral;
export type PropertyNameLiteral = Identifier | StringLiteralLike | NumericLiteral | JsxNamespacedName;
export interface TemplateLiteralTypeNode extends TypeNode {
kind: SyntaxKind.TemplateLiteralType,
@@ -3192,7 +3189,7 @@ export type JsxTagNameExpression =
;
export interface JsxTagNamePropertyAccess extends PropertyAccessExpression {
readonly expression: JsxTagNameExpression;
readonly expression: Identifier | ThisExpression | JsxTagNamePropertyAccess;
}
export interface JsxAttributes extends PrimaryExpression, Declaration {
@@ -3201,7 +3198,7 @@ export interface JsxAttributes extends PrimaryExpression, Declaration {
readonly parent: JsxOpeningLikeElement;
}
export interface JsxNamespacedName extends PrimaryExpression {
export interface JsxNamespacedName extends Node {
readonly kind: SyntaxKind.JsxNamespacedName;
readonly name: Identifier;
readonly namespace: Identifier;
@@ -3303,15 +3300,6 @@ export interface NotEmittedStatement extends Statement {
readonly kind: SyntaxKind.NotEmittedStatement;
}
/**
* Marks the end of transformed declaration to properly emit exports.
*
* @internal
*/
export interface EndOfDeclarationMarker extends Statement {
readonly kind: SyntaxKind.EndOfDeclarationMarker;
}
/**
* A list of comma-separated expressions. This node is only created by transformations.
*/
@@ -3320,14 +3308,6 @@ export interface CommaListExpression extends Expression {
readonly elements: NodeArray<Expression>;
}
/**
* Marks the beginning of a merged transformed declaration.
*
* @internal
*/
export interface MergeDeclarationMarker extends Statement {
readonly kind: SyntaxKind.MergeDeclarationMarker;
}
/** @internal */
export interface SyntheticReferenceExpression extends LeftHandSideExpression {
@@ -4239,7 +4219,6 @@ export interface SourceFileLike {
getPositionOfLineAndCharacter?(line: number, character: number, allowEdits?: true): number;
}
/** @internal */
export interface RedirectInfo {
/** Source file this redirects to. */
@@ -4725,6 +4704,12 @@ export const enum EmitOnly{
Js,
Dts,
}
/** @internal */
export interface LibResolution<T extends ResolvedModuleWithFailedLookupLocations = ResolvedModuleWithFailedLookupLocations> {
resolution: T;
actual: string;
}
export interface Program extends ScriptReferenceHost {
getCurrentDirectory(): string;
/**
@@ -4825,6 +4810,12 @@ export interface Program extends ScriptReferenceHost {
* @internal
*/
readonly usesUriStyleNodeCoreModules: boolean;
/**
* Map from libFileName to actual resolved location of the lib
* @internal
*/
resolvedLibReferences: Map<string, LibResolution> | undefined;
/** @internal */ getCurrentPackagesMap(): Map<string, boolean> | undefined;
/**
* Is the file emitted file
*
@@ -4960,6 +4951,9 @@ export interface TypeCheckerHost extends ModuleSpecifierResolutionHost {
isSourceOfProjectReferenceRedirect(fileName: string): boolean;
readonly redirectTargetsMap: RedirectTargetsMap;
typesPackageExists(packageName: string): boolean;
packageBundlesTypes(packageName: string): boolean;
}
export interface TypeChecker {
@@ -5702,6 +5696,7 @@ export interface EmitResolver {
// Returns the constant value this property access resolves to, or 'undefined' for a non-constant
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined;
getReferencedValueDeclaration(reference: Identifier): Declaration | undefined;
getReferencedValueDeclarations(reference: Identifier): Declaration[] | undefined;
getTypeReferenceSerializationKind(typeName: EntityName, location?: Node): TypeReferenceSerializationKind;
isOptionalParameter(node: ParameterDeclaration): boolean;
moduleExportsSomeValue(moduleReferenceExpression: Expression): boolean;
@@ -6065,6 +6060,7 @@ export interface NodeLinks {
spreadIndices?: { first: number | undefined, last: number | undefined }; // Indices of first and last spread elements in array literal
parameterInitializerContainsUndefined?: boolean; // True if this is a parameter declaration whose type annotation contains "undefined".
fakeScopeForSignatureDeclaration?: boolean; // True if this is a fake scope injected into an enclosing declaration chain.
assertionExpressionType?: Type; // Cached type of the expression of a type assertion
}
/** @internal */
@@ -6932,6 +6928,16 @@ export interface DiagnosticMessage {
elidedInCompatabilityPyramid?: boolean;
}
/** @internal */
export interface RepopulateModuleNotFoundDiagnosticChain {
moduleReference: string;
mode: ResolutionMode;
packageName: string | undefined;
}
/** @internal */
export type RepopulateDiagnosticChainInfo = RepopulateModuleNotFoundDiagnosticChain;
/**
* A linked list of formatted diagnostic messages to be used as part of a multiline message.
* It is built from the bottom up, leaving the head to be the "main" diagnostic.
@@ -6943,6 +6949,8 @@ export interface DiagnosticMessageChain {
category: DiagnosticCategory;
code: number;
next?: DiagnosticMessageChain[];
/** @internal */
repopulateInfo?: () => RepopulateDiagnosticChainInfo;
}
export interface Diagnostic extends DiagnosticRelatedInformation {
@@ -7360,7 +7368,7 @@ export interface ConfigFileSpecs {
}
/** @internal */
export type RequireResult<T = {}> =
export type ModuleImportResult<T = {}> =
| { module: T, modulePath?: string, error: undefined }
| { module: undefined, modulePath?: undefined, error: { stack?: string, message?: string } };
@@ -7741,6 +7749,8 @@ export interface ResolvedTypeReferenceDirectiveWithFailedLookupLocations {
/** @internal */
export type HasInvalidatedResolutions = (sourceFile: Path) => boolean;
/** @internal */
export type HasInvalidatedLibResolutions = (libFileName: string) => boolean;
/** @internal */
export type HasChangedAutomaticTypeDirectiveNames = () => boolean;
export interface CompilerHost extends ModuleResolutionHost {
@@ -7791,6 +7801,18 @@ export interface CompilerHost extends ModuleResolutionHost {
containingSourceFile: SourceFile | undefined,
reusedNames: readonly T[] | undefined
): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[];
/** @internal */
resolveLibrary?(
libraryName: string,
resolveFrom: string,
options: CompilerOptions,
libFileName: string,
): ResolvedModuleWithFailedLookupLocations;
/**
* If provided along with custom resolveLibrary, used to determine if we should redo library resolutions
* @internal
*/
hasInvalidatedLibResolutions?(libFileName: string): boolean;
getEnvironmentVariable?(name: string): string | undefined;
/** @internal */ onReleaseOldSourceFile?(oldSourceFile: SourceFile, oldOptions: CompilerOptions, hasSourceFileByPath: boolean): void;
/** @internal */ onReleaseParsedCommandLine?(configFileName: string, oldResolvedRef: ResolvedProjectReference | undefined, optionOptions: CompilerOptions): void;
@@ -7995,9 +8017,8 @@ export const enum EmitFlags {
ReuseTempVariableScope = 1 << 20, // Reuse the existing temp variable scope during emit.
CustomPrologue = 1 << 21, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed).
NoHoisting = 1 << 22, // Do not hoist this declaration in --module system
HasEndOfDeclarationMarker = 1 << 23, // Declaration has an associated NotEmittedStatement to mark the end of the declaration
Iterator = 1 << 24, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable.
NoAsciiEscaping = 1 << 25, // When synthesizing nodes that lack an original node or textSourceNode, we want to write the text on the node with ASCII escaping substitutions.
Iterator = 1 << 23, // The expression to a `yield*` should be treated as an Iterator when down-leveling, not an Iterable.
NoAsciiEscaping = 1 << 24, // When synthesizing nodes that lack an original node or textSourceNode, we want to write the text on the node with ASCII escaping substitutions.
}
/** @internal */
@@ -8799,8 +8820,6 @@ export interface NodeFactory {
//
createNotEmittedStatement(original: Node): NotEmittedStatement;
/** @internal */ createEndOfDeclarationMarker(original: Node): EndOfDeclarationMarker;
/** @internal */ createMergeDeclarationMarker(original: Node): MergeDeclarationMarker;
createPartiallyEmittedExpression(expression: Expression, original?: Node): PartiallyEmittedExpression;
updatePartiallyEmittedExpression(node: PartiallyEmittedExpression, expression: Expression): PartiallyEmittedExpression;
/** @internal */ createSyntheticReferenceExpression(expression: Expression, thisArg: Expression): SyntheticReferenceExpression;
@@ -8920,10 +8939,11 @@ export interface NodeFactory {
* @param node The declaration.
* @param allowComments A value indicating whether comments may be emitted for the name.
* @param allowSourceMaps A value indicating whether source maps may be emitted for the name.
* @param ignoreAssignedName Indicates that the assigned name of a declaration shouldn't be considered.
*
* @internal
*/
getLocalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean): Identifier;
getLocalName(node: Declaration, allowComments?: boolean, allowSourceMaps?: boolean, ignoreAssignedName?: boolean): Identifier;
/**
* Gets the export name of a declaration. This is primarily used for declarations that can be
* referred to by name in the declaration's immediate scope (classes, enums, namespaces). An
+74 -11
View File
@@ -293,6 +293,7 @@ import {
isJSDocTypeTag,
isJsxChild,
isJsxFragment,
isJsxNamespacedName,
isJsxOpeningLikeElement,
isJsxText,
isLeftHandSideExpression,
@@ -378,6 +379,7 @@ import {
LiteralLikeNode,
LogicalOperator,
LogicalOrCoalescingAssignmentOperator,
mangleScopedPackageName,
map,
mapDefined,
MapLike,
@@ -525,6 +527,7 @@ import {
TypeAliasDeclaration,
TypeAssertion,
TypeChecker,
TypeCheckerHost,
TypeElement,
TypeFlags,
TypeLiteralNode,
@@ -780,7 +783,37 @@ export function moduleResolutionIsEqualTo(oldResolution: ResolvedModuleWithFaile
oldResolution.resolvedModule.extension === newResolution.resolvedModule.extension &&
oldResolution.resolvedModule.resolvedFileName === newResolution.resolvedModule.resolvedFileName &&
oldResolution.resolvedModule.originalPath === newResolution.resolvedModule.originalPath &&
packageIdIsEqual(oldResolution.resolvedModule.packageId, newResolution.resolvedModule.packageId);
packageIdIsEqual(oldResolution.resolvedModule.packageId, newResolution.resolvedModule.packageId) &&
oldResolution.node10Result === newResolution.node10Result;
}
/** @internal */
export function createModuleNotFoundChain(sourceFile: SourceFile, host: TypeCheckerHost, moduleReference: string, mode: ResolutionMode, packageName: string) {
const node10Result = sourceFile.resolvedModules?.get(moduleReference, mode)?.node10Result;
const result = node10Result
? chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.There_are_types_at_0_but_this_result_could_not_be_resolved_when_respecting_package_json_exports_The_1_library_may_need_to_update_its_package_json_or_typings,
node10Result,
node10Result.indexOf(nodeModulesPathPart + "@types/") > -1 ? `@types/${mangleScopedPackageName(packageName)}` : packageName)
: host.typesPackageExists(packageName)
? chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.If_the_0_package_actually_exposes_this_module_consider_sending_a_pull_request_to_amend_https_Colon_Slash_Slashgithub_com_SlashDefinitelyTyped_SlashDefinitelyTyped_Slashtree_Slashmaster_Slashtypes_Slash_1,
packageName, mangleScopedPackageName(packageName))
: host.packageBundlesTypes(packageName)
? chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.If_the_0_package_actually_exposes_this_module_try_adding_a_new_declaration_d_ts_file_containing_declare_module_1,
packageName,
moduleReference)
: chainDiagnosticMessages(
/*details*/ undefined,
Diagnostics.Try_npm_i_save_dev_types_Slash_1_if_it_exists_or_add_a_new_declaration_d_ts_file_containing_declare_module_0,
moduleReference,
mangleScopedPackageName(packageName));
if (result) result.repopulateInfo = () => ({ moduleReference, mode, packageName: packageName === moduleReference ? undefined : packageName });
return result;
}
function packageIdIsEqual(a: PackageId | undefined, b: PackageId | undefined): boolean {
@@ -1824,7 +1857,7 @@ function isCommonJSContainingModuleKind(kind: ModuleKind) {
/** @internal */
export function isEffectiveExternalModule(node: SourceFile, compilerOptions: CompilerOptions) {
return isExternalModule(node) || getIsolatedModules(compilerOptions) || (isCommonJSContainingModuleKind(getEmitModuleKind(compilerOptions)) && !!node.commonJsModuleIndicator);
return isExternalModule(node) || (isCommonJSContainingModuleKind(getEmitModuleKind(compilerOptions)) && !!node.commonJsModuleIndicator);
}
/**
@@ -2024,7 +2057,7 @@ export function isComputedNonLiteralName(name: PropertyName): boolean {
}
/** @internal */
export function tryGetTextOfPropertyName(name: PropertyName | NoSubstitutionTemplateLiteral): __String | undefined {
export function tryGetTextOfPropertyName(name: PropertyName | NoSubstitutionTemplateLiteral | JsxAttributeName): __String | undefined {
switch (name.kind) {
case SyntaxKind.Identifier:
case SyntaxKind.PrivateIdentifier:
@@ -2036,13 +2069,15 @@ export function tryGetTextOfPropertyName(name: PropertyName | NoSubstitutionTemp
case SyntaxKind.ComputedPropertyName:
if (isStringOrNumericLiteralLike(name.expression)) return escapeLeadingUnderscores(name.expression.text);
return undefined;
case SyntaxKind.JsxNamespacedName:
return getEscapedTextOfJsxNamespacedName(name);
default:
return Debug.assertNever(name);
}
}
/** @internal */
export function getTextOfPropertyName(name: PropertyName | NoSubstitutionTemplateLiteral): __String {
export function getTextOfPropertyName(name: PropertyName | NoSubstitutionTemplateLiteral | JsxAttributeName): __String {
return Debug.checkDefined(tryGetTextOfPropertyName(name));
}
@@ -3074,7 +3109,7 @@ export function getEntityNameFromTypeNode(node: TypeNode): EntityNameOrEntityNam
}
/** @internal */
export function getInvokedExpression(node: CallLikeExpression): Expression {
export function getInvokedExpression(node: CallLikeExpression): Expression | JsxTagNameExpression {
switch (node.kind) {
case SyntaxKind.TaggedTemplateExpression:
return node.tag;
@@ -4180,6 +4215,7 @@ export function canHaveJSDoc(node: Node): node is HasJSDoc {
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
case SyntaxKind.ReturnStatement:
case SyntaxKind.SemicolonClassElement:
case SyntaxKind.SetAccessor:
case SyntaxKind.ShorthandPropertyAssignment:
case SyntaxKind.SpreadAssignment:
@@ -4198,7 +4234,29 @@ export function canHaveJSDoc(node: Node): node is HasJSDoc {
}
}
/** @internal */
/**
* This function checks multiple locations for JSDoc comments that apply to a host node.
* At each location, the whole comment may apply to the node, or only a specific tag in
* the comment. In the first case, location adds the entire {@link JSDoc} object. In the
* second case, it adds the applicable {@link JSDocTag}.
*
* For example, a JSDoc comment before a parameter adds the entire {@link JSDoc}. But a
* `@param` tag on the parent function only adds the {@link JSDocTag} for the `@param`.
*
* ```ts
* /** JSDoc will be returned for `a` *\/
* const a = 0
* /**
* * Entire JSDoc will be returned for `b`
* * @param c JSDocTag will be returned for `c`
* *\/
* function b(/** JSDoc will be returned for `c` *\/ c) {}
* ```
*/
export function getJSDocCommentsAndTags(hostNode: Node): readonly (JSDoc | JSDocTag)[];
/** @internal separate signature so that stripInternal can remove noCache from the public API */
// eslint-disable-next-line @typescript-eslint/unified-signatures
export function getJSDocCommentsAndTags(hostNode: Node, noCache?: boolean): readonly (JSDoc | JSDocTag)[];
export function getJSDocCommentsAndTags(hostNode: Node, noCache?: boolean): readonly (JSDoc | JSDocTag)[] {
let result: (JSDoc | JSDocTag)[] | undefined;
// Pull parameter comments from declaring function as well
@@ -4931,7 +4989,7 @@ export function isDynamicName(name: DeclarationName): boolean {
}
/** @internal */
export function getPropertyNameForPropertyNameNode(name: PropertyName): __String | undefined {
export function getPropertyNameForPropertyNameNode(name: PropertyName | JsxAttributeName): __String | undefined {
switch (name.kind) {
case SyntaxKind.Identifier:
case SyntaxKind.PrivateIdentifier:
@@ -4951,6 +5009,8 @@ export function getPropertyNameForPropertyNameNode(name: PropertyName): __String
return nameExpression.operand.text as __String;
}
return undefined;
case SyntaxKind.JsxNamespacedName:
return getEscapedTextOfJsxNamespacedName(name);
default:
return Debug.assertNever(name);
}
@@ -4970,12 +5030,12 @@ export function isPropertyNameLiteral(node: Node): node is PropertyNameLiteral {
}
/** @internal */
export function getTextOfIdentifierOrLiteral(node: PropertyNameLiteral | PrivateIdentifier): string {
return isMemberName(node) ? idText(node) : node.text;
return isMemberName(node) ? idText(node) : isJsxNamespacedName(node) ? getTextOfJsxNamespacedName(node) : node.text;
}
/** @internal */
export function getEscapedTextOfIdentifierOrLiteral(node: PropertyNameLiteral): __String {
return isMemberName(node) ? node.escapedText : escapeLeadingUnderscores(node.text);
return isMemberName(node) ? node.escapedText : isJsxNamespacedName(node) ? getEscapedTextOfJsxNamespacedName(node) : escapeLeadingUnderscores(node.text);
}
/** @internal */
@@ -7002,7 +7062,7 @@ export function isPropertyAccessEntityNameExpression(node: Node): node is Proper
}
/** @internal */
export function tryGetPropertyAccessOrIdentifierToString(expr: Expression): string | undefined {
export function tryGetPropertyAccessOrIdentifierToString(expr: Expression | JsxTagNameExpression): string | undefined {
if (isPropertyAccessExpression(expr)) {
const baseStr = tryGetPropertyAccessOrIdentifierToString(expr.expression);
if (baseStr !== undefined) {
@@ -7018,6 +7078,9 @@ export function tryGetPropertyAccessOrIdentifierToString(expr: Expression): stri
else if (isIdentifier(expr)) {
return unescapeLeadingUnderscores(expr.escapedText);
}
else if (isJsxNamespacedName(expr)) {
return getTextOfJsxNamespacedName(expr);
}
return undefined;
}
@@ -9100,7 +9163,7 @@ export const supportedTSExtensions: readonly Extension[][] = [[Extension.Ts, Ext
export const supportedTSExtensionsFlat: readonly Extension[] = flatten(supportedTSExtensions);
const supportedTSExtensionsWithJson: readonly Extension[][] = [...supportedTSExtensions, [Extension.Json]];
/** Must have ".d.ts" first because if ".ts" goes first, that will be detected as the extension instead of ".d.ts". */
const supportedTSExtensionsForExtractExtension: readonly Extension[] = [Extension.Dts, Extension.Dcts, Extension.Dmts, Extension.Cts, Extension.Mts, Extension.Ts, Extension.Tsx, Extension.Cts, Extension.Mts];
const supportedTSExtensionsForExtractExtension: readonly Extension[] = [Extension.Dts, Extension.Dcts, Extension.Dmts, Extension.Cts, Extension.Mts, Extension.Ts, Extension.Tsx];
/** @internal */
export const supportedJSExtensions: readonly Extension[][] = [[Extension.Js, Extension.Jsx], [Extension.Mjs], [Extension.Cjs]];
/** @internal */
+1 -4
View File
@@ -1941,7 +1941,6 @@ function isLeftHandSideExpressionKind(kind: SyntaxKind): boolean {
case SyntaxKind.JsxElement:
case SyntaxKind.JsxSelfClosingElement:
case SyntaxKind.JsxFragment:
case SyntaxKind.JsxNamespacedName:
case SyntaxKind.TaggedTemplateExpression:
case SyntaxKind.ArrayLiteralExpression:
case SyntaxKind.ParenthesizedExpression:
@@ -2334,9 +2333,7 @@ function isStatementKindButNotDeclarationKind(kind: SyntaxKind) {
|| kind === SyntaxKind.VariableStatement
|| kind === SyntaxKind.WhileStatement
|| kind === SyntaxKind.WithStatement
|| kind === SyntaxKind.NotEmittedStatement
|| kind === SyntaxKind.EndOfDeclarationMarker
|| kind === SyntaxKind.MergeDeclarationMarker;
|| kind === SyntaxKind.NotEmittedStatement;
}
/** @internal */
+4
View File
@@ -692,6 +692,8 @@ export const WatchType: WatchTypeRegistry = {
NoopConfigFileForInferredRoot: "Noop Config file for the inferred project root",
MissingGeneratedFile: "Missing generated file",
NodeModulesForModuleSpecifierCache: "node_modules for module specifier cache invalidation",
TypingInstallerLocationFile: "File location for typing installer",
TypingInstallerLocationDirectory: "Directory location for typing installer",
};
/** @internal */
@@ -717,6 +719,8 @@ export interface WatchTypeRegistry {
NoopConfigFileForInferredRoot: "Noop Config file for the inferred project root",
MissingGeneratedFile: "Missing generated file",
NodeModulesForModuleSpecifierCache: "node_modules for module specifier cache invalidation",
TypingInstallerLocationFile: "File location for typing installer",
TypingInstallerLocationDirectory: "Directory location for typing installer",
}
/** @internal */
+27 -5
View File
@@ -48,6 +48,7 @@ import {
getParsedCommandLineOfConfigFile,
getSourceFileVersionAsHashFromText,
getTsBuildInfoEmitOutputFilePath,
HasInvalidatedLibResolutions,
HasInvalidatedResolutions,
isArray,
isIgnoredFileFromWildCardWatching,
@@ -229,6 +230,19 @@ export interface ProgramHost<T extends BuilderProgram> {
containingSourceFile: SourceFile | undefined,
reusedNames: readonly T[] | undefined
): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[];
/** @internal */
resolveLibrary?(
libraryName: string,
resolveFrom: string,
options: CompilerOptions,
libFileName: string,
): ResolvedModuleWithFailedLookupLocations;
/**
* If provided along with custom resolveLibrary, used to determine if we should redo library resolutions
* @internal
*/
hasInvalidatedLibResolutions?(libFileName: string): boolean;
/** If provided along with custom resolveModuleNames or resolveTypeReferenceDirectives, used to determine if unchanged file path needs to re-resolve modules/type reference directives */
hasInvalidatedResolutions?(filePath: Path): boolean;
/**
@@ -503,6 +517,9 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
if (!compilerHost.resolveTypeReferenceDirectiveReferences && !compilerHost.resolveTypeReferenceDirectives) {
compilerHost.resolveTypeReferenceDirectiveReferences = resolutionCache.resolveTypeReferenceDirectiveReferences.bind(resolutionCache);
}
compilerHost.resolveLibrary = !host.resolveLibrary ?
resolutionCache.resolveLibrary.bind(resolutionCache) :
host.resolveLibrary.bind(host);
compilerHost.getModuleResolutionCache = host.resolveModuleNameLiterals || host.resolveModuleNames ?
maybeBind(host, host.getModuleResolutionCache) :
(() => resolutionCache.getModuleResolutionCache());
@@ -512,6 +529,9 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
const customHasInvalidatedResolutions = userProvidedResolution ?
maybeBind(host, host.hasInvalidatedResolutions) || returnTrue :
returnFalse;
const customHasInvalidLibResolutions = host.resolveLibrary ?
maybeBind(host, host.hasInvalidatedLibResolutions) || returnTrue :
returnFalse;
builderProgram = readBuilderProgram(compilerOptions, compilerHost) as any as T;
synchronizeProgram();
@@ -584,16 +604,17 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
if (hasChangedCompilerOptions) {
newLine = updateNewLine();
if (program && changesAffectModuleResolution(program.getCompilerOptions(), compilerOptions)) {
resolutionCache.clear();
debugger;
resolutionCache.onChangesAffectModuleResolution();
}
}
const hasInvalidatedResolutions = resolutionCache.createHasInvalidatedResolutions(customHasInvalidatedResolutions);
const { hasInvalidatedResolutions, hasInvalidatedLibResolutions } = resolutionCache.createHasInvalidatedResolutions(customHasInvalidatedResolutions, customHasInvalidLibResolutions);
const {
originalReadFile, originalFileExists, originalDirectoryExists,
originalCreateDirectory, originalWriteFile, readFileWithCache
} = changeCompilerHostLikeToUseCache(compilerHost, toPath);
if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, path => getSourceVersion(path, readFileWithCache), fileName => compilerHost.fileExists(fileName), hasInvalidatedResolutions, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) {
if (isProgramUptoDate(getCurrentProgram(), rootFileNames, compilerOptions, path => getSourceVersion(path, readFileWithCache), fileName => compilerHost.fileExists(fileName), hasInvalidatedResolutions, hasInvalidatedLibResolutions, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) {
if (hasChangedConfigFileParsingErrors) {
if (reportFileChangeDetectedOnCreateProgram) {
reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation);
@@ -606,7 +627,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
if (reportFileChangeDetectedOnCreateProgram) {
reportWatchDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation);
}
createNewProgram(hasInvalidatedResolutions);
createNewProgram(hasInvalidatedResolutions, hasInvalidatedLibResolutions);
}
reportFileChangeDetectedOnCreateProgram = false;
@@ -623,7 +644,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
return builderProgram;
}
function createNewProgram(hasInvalidatedResolutions: HasInvalidatedResolutions) {
function createNewProgram(hasInvalidatedResolutions: HasInvalidatedResolutions, hasInvalidatedLibResolutions: HasInvalidatedLibResolutions) {
// Compile the program
writeLog("CreatingProgramWith::");
writeLog(` roots: ${JSON.stringify(rootFileNames)}`);
@@ -635,6 +656,7 @@ export function createWatchProgram<T extends BuilderProgram>(host: WatchCompiler
hasChangedConfigFileParsingErrors = false;
resolutionCache.startCachingPerDirectoryResolution();
compilerHost.hasInvalidatedResolutions = hasInvalidatedResolutions;
compilerHost.hasInvalidatedLibResolutions = hasInvalidatedLibResolutions;
compilerHost.hasChangedAutomaticTypeDirectiveNames = hasChangedAutomaticTypeDirectiveNames;
const oldProgram = getCurrentProgram();
builderProgram = createProgram(rootFileNames, compilerOptions, compilerHost, builderProgram, configFileParsingDiagnostics, projectReferences);
+14 -4
View File
@@ -568,15 +568,17 @@ export class SessionClient implements LanguageService {
return notImplemented();
}
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] {
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, preferences: UserPreferences | boolean | undefined): RenameLocation[] {
if (!this.lastRenameEntry ||
this.lastRenameEntry.inputs.fileName !== fileName ||
this.lastRenameEntry.inputs.position !== position ||
this.lastRenameEntry.inputs.findInStrings !== findInStrings ||
this.lastRenameEntry.inputs.findInComments !== findInComments) {
if (providePrefixAndSuffixTextForRename !== undefined) {
const providePrefixAndSuffixTextForRename = typeof preferences === "boolean" ? preferences : preferences?.providePrefixAndSuffixTextForRename;
const quotePreference = typeof preferences === "boolean" ? undefined : preferences?.quotePreference;
if (providePrefixAndSuffixTextForRename !== undefined || quotePreference !== undefined) {
// User preferences have to be set through the `Configure` command
this.configure({ providePrefixAndSuffixTextForRename });
this.configure({ providePrefixAndSuffixTextForRename, quotePreference });
// Options argument is not used, so don't pass in options
this.getRenameInfo(fileName, position, /*preferences*/{}, findInStrings, findInComments);
// Restore previous user preferences
@@ -793,6 +795,14 @@ export class SessionClient implements LanguageService {
return response.body!; // TODO: GH#18217
}
getMoveToRefactoringFileSuggestions(fileName: string, positionOrRange: number | TextRange): { newFileName: string; files: string[]; } {
const args = this.createFileLocationOrRangeRequestArgs(positionOrRange, fileName);
const request = this.processRequest<protocol.GetMoveToRefactoringFileSuggestionsRequest>(protocol.CommandTypes.GetMoveToRefactoringFileSuggestions, args);
const response = this.processResponse<protocol.GetMoveToRefactoringFileSuggestions>(request);
return { newFileName: response.body?.newFileName, files:response.body?.files }!;
}
getEditsForRefactor(
fileName: string,
_formatOptions: FormatCodeSettings,
@@ -816,7 +826,7 @@ export class SessionClient implements LanguageService {
const renameFilename: string | undefined = response.body.renameFilename;
let renameLocation: number | undefined;
if (renameFilename !== undefined) {
renameLocation = this.lineOffsetToPosition(renameFilename, response.body.renameLocation!); // TODO: GH#18217
renameLocation = this.lineOffsetToPosition(renameFilename, response.body.renameLocation!);
}
return {
+25 -8
View File
@@ -1802,13 +1802,18 @@ export class TestState {
isMarker(markerOrRange) ?
markerOrRange :
{ fileName: markerOrRange.fileName, position: markerOrRange.pos };
const { findInStrings = false, findInComments = false, providePrefixAndSuffixTextForRename = true } = options || {};
const {
findInStrings = false,
findInComments = false,
providePrefixAndSuffixTextForRename = true,
quotePreference = "double"
} = options || {};
const locations = this.languageService.findRenameLocations(
fileName,
position,
findInStrings,
findInComments,
providePrefixAndSuffixTextForRename,
{ providePrefixAndSuffixTextForRename, quotePreference },
);
if (!locations) {
@@ -1818,7 +1823,8 @@ export class TestState {
const renameOptions = options ?
(options.findInStrings !== undefined ? `// @findInStrings: ${findInStrings}\n` : "") +
(options.findInComments !== undefined ? `// @findInComments: ${findInComments}\n` : "") +
(options.providePrefixAndSuffixTextForRename !== undefined ? `// @providePrefixAndSuffixTextForRename: ${providePrefixAndSuffixTextForRename}\n` : "") :
(options.providePrefixAndSuffixTextForRename !== undefined ? `// @providePrefixAndSuffixTextForRename: ${providePrefixAndSuffixTextForRename}\n` : "") +
(options.quotePreference !== undefined ? `// @quotePreference: ${quotePreference}\n` : "") :
"";
return renameOptions + (renameOptions ? "\n" : "") + this.getBaselineForDocumentSpansWithFileContents(
@@ -3293,7 +3299,6 @@ export class TestState {
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);
this.verifyTextMatches(newContent, /*includeWhitespace*/ true, expectedNewContent);
}
@@ -3906,6 +3911,18 @@ export class TestState {
this.verifyNewContent({ newFileContent: options.newFileContents }, editInfo.edits);
}
public moveToFile(options: FourSlashInterface.MoveToFileOptions): void {
assert(this.getRanges().length === 1, "Must have exactly one fourslash range (source enclosed between '[|' and '|]' delimiters) in the source file");
const range = this.getRanges()[0];
const refactor = ts.find(this.getApplicableRefactors(range, { allowTextChangesInNewFiles: true }, /*triggerReason*/ undefined, /*kind*/ undefined, /*includeInteractiveActions*/ true), r => r.name === "Move to file")!;
assert(refactor.actions.length === 1);
const action = ts.first(refactor.actions);
assert(action.name === "Move to file" && action.description === "Move to file");
const editInfo = this.languageService.getEditsForRefactor(range.fileName, this.formatCodeSettings, range, refactor.name, action.name, options.preferences || ts.emptyOptions, options.interactiveRefactorArguments)!;
this.verifyNewContent({ newFileContent: options.newFileContents }, editInfo.edits);
}
private testNewFileContents(edits: readonly ts.FileTextChanges[], newFileContents: { [fileName: string]: string }, description: string): void {
for (const { fileName, textChanges } of edits) {
const newContent = newFileContents[fileName];
@@ -4211,11 +4228,11 @@ export class TestState {
private getApplicableRefactorsAtSelection(triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string, preferences = ts.emptyOptions) {
return this.getApplicableRefactorsWorker(this.getSelection(), this.activeFile.fileName, preferences, triggerReason, kind);
}
private getApplicableRefactors(rangeOrMarker: Range | Marker, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string): readonly ts.ApplicableRefactorInfo[] {
return this.getApplicableRefactorsWorker("position" in rangeOrMarker ? rangeOrMarker.position : rangeOrMarker, rangeOrMarker.fileName, preferences, triggerReason, kind); // eslint-disable-line local/no-in-operator
private getApplicableRefactors(rangeOrMarker: Range | Marker, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason = "implicit", kind?: string, includeInteractiveActions?: boolean): readonly ts.ApplicableRefactorInfo[] {
return this.getApplicableRefactorsWorker("position" in rangeOrMarker ? rangeOrMarker.position : rangeOrMarker, rangeOrMarker.fileName, preferences, triggerReason, kind, includeInteractiveActions); // eslint-disable-line local/no-in-operator
}
private getApplicableRefactorsWorker(positionOrRange: number | ts.TextRange, fileName: string, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason, kind?: string): readonly ts.ApplicableRefactorInfo[] {
return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences, triggerReason, kind) || ts.emptyArray;
private getApplicableRefactorsWorker(positionOrRange: number | ts.TextRange, fileName: string, preferences = ts.emptyOptions, triggerReason: ts.RefactorTriggerReason, kind?: string, includeInteractiveActions?: boolean): readonly ts.ApplicableRefactorInfo[] {
return this.languageService.getApplicableRefactors(fileName, positionOrRange, preferences, triggerReason, kind, includeInteractiveActions) || ts.emptyArray;
}
public configurePlugin(pluginName: string, configuration: any): void {
+11
View File
@@ -610,6 +610,10 @@ export class Verify extends VerifyNegatable {
this.state.moveToNewFile(options);
}
public moveToFile(options: MoveToFileOptions): void {
this.state.moveToFile(options);
}
public noMoveToNewFile(): void {
this.state.noMoveToNewFile();
}
@@ -1896,6 +1900,12 @@ export interface MoveToNewFileOptions {
readonly preferences?: ts.UserPreferences;
}
export interface MoveToFileOptions {
readonly newFileContents: { readonly [fileName: string]: string };
readonly interactiveRefactorArguments: ts.InteractiveRefactorArguments;
readonly preferences?: ts.UserPreferences;
}
export type RenameLocationsOptions = readonly RenameLocationOptions[] | {
readonly findInStrings?: boolean;
readonly findInComments?: boolean;
@@ -1910,6 +1920,7 @@ export interface RenameOptions {
readonly findInStrings?: boolean;
readonly findInComments?: boolean;
readonly providePrefixAndSuffixTextForRename?: boolean;
readonly quotePreference?: "auto" | "double" | "single";
}
export type BaselineCommandWithMarkerOrRange = {
type: "findAllReferences" | "goToDefinition" | "getDefinitionAtPosition" | "goToSourceDefinition" | "goToType" | "goToImplementation";
+6 -3
View File
@@ -527,8 +527,8 @@ class LanguageServiceShimProxy implements ts.LanguageService {
getSmartSelectionRange(fileName: string, position: number): ts.SelectionRange {
return unwrapJSONCallResult(this.shim.getSmartSelectionRange(fileName, position));
}
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): ts.RenameLocation[] {
return unwrapJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename));
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, preferences?: ts.UserPreferences | boolean): ts.RenameLocation[] {
return unwrapJSONCallResult(this.shim.findRenameLocations(fileName, position, findInStrings, findInComments, preferences));
}
getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] {
return unwrapJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position));
@@ -616,6 +616,9 @@ class LanguageServiceShimProxy implements ts.LanguageService {
getApplicableRefactors(): ts.ApplicableRefactorInfo[] {
throw new Error("Not supported on the shim.");
}
getMoveToRefactoringFileSuggestions(): { newFileName: string, files: string[] } {
throw new Error("Not supported on the shim.");
}
organizeImports(_args: ts.OrganizeImportsArgs, _formatOptions: ts.FormatCodeSettings): readonly ts.FileTextChanges[] {
throw new Error("Not supported on the shim.");
}
@@ -885,7 +888,7 @@ class SessionServerHost implements ts.server.ServerHost, ts.server.Logger {
return mockHash(s);
}
require(_initialDir: string, _moduleName: string): ts.RequireResult {
require(_initialDir: string, _moduleName: string): ts.ModuleImportResult {
switch (_moduleName) {
// Adds to the Quick Info a fixed string and a string from the config file
// and replaces the first display part
+3
View File
@@ -10,6 +10,7 @@ export type EventTypesRegistry = "event::typesRegistry";
export type EventBeginInstallTypes = "event::beginInstallTypes";
export type EventEndInstallTypes = "event::endInstallTypes";
export type EventInitializationFailed = "event::initializationFailed";
export type ActionWatchTypingLocations = "action::watchTypingLocations";
/** @internal */
export const ActionSet: ActionSet = "action::set";
/** @internal */
@@ -24,6 +25,8 @@ export const EventBeginInstallTypes: EventBeginInstallTypes = "event::beginInsta
export const EventEndInstallTypes: EventEndInstallTypes = "event::endInstallTypes";
/** @internal */
export const EventInitializationFailed: EventInitializationFailed = "event::initializationFailed";
/** @internal */
export const ActionWatchTypingLocations: ActionWatchTypingLocations = "action::watchTypingLocations";
/** @internal */
export namespace Arguments {
+9 -9
View File
@@ -1,19 +1,16 @@
import {
CompilerOptions,
DirectoryWatcherCallback,
FileWatcher,
FileWatcherCallback,
JsTyping,
MapLike,
Path,
SortedReadonlyArray,
TypeAcquisition,
WatchOptions,
} from "./_namespaces/ts";
import {
ActionInvalidate,
ActionPackageInstalled,
ActionSet,
ActionWatchTypingLocations,
EventBeginInstallTypes,
EventEndInstallTypes,
EventInitializationFailed,
@@ -21,7 +18,7 @@ import {
} from "./_namespaces/ts.server";
export interface TypingInstallerResponse {
readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed;
readonly kind: ActionSet | ActionInvalidate | EventTypesRegistry | ActionPackageInstalled | EventBeginInstallTypes | EventEndInstallTypes | EventInitializationFailed | ActionWatchTypingLocations;
}
export interface TypingInstallerRequestWithProjectName {
@@ -35,7 +32,6 @@ export interface DiscoverTypings extends TypingInstallerRequestWithProjectName {
readonly fileNames: string[];
readonly projectRootPath: Path;
readonly compilerOptions: CompilerOptions;
readonly watchOptions?: WatchOptions;
readonly typeAcquisition: TypeAcquisition;
readonly unresolvedImports: SortedReadonlyArray<string>;
readonly cachePath?: string;
@@ -104,8 +100,6 @@ export interface InstallTypingHost extends JsTyping.TypingResolutionHost {
writeFile(path: string, content: string): void;
createDirectory(path: string): void;
getCurrentDirectory?(): string;
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number, options?: WatchOptions): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean, options?: WatchOptions): FileWatcher;
}
export interface SetTypings extends ProjectResponse {
@@ -116,5 +110,11 @@ export interface SetTypings extends ProjectResponse {
readonly kind: ActionSet;
}
export interface WatchTypingLocations extends ProjectResponse {
/** if files is undefined, retain same set of watchers */
readonly files: readonly string[] | undefined;
readonly kind: ActionWatchTypingLocations;
}
/** @internal */
export type TypingInstallerResponseUnion = SetTypings | InvalidateCachedTypings | TypesRegistryResponse | PackageInstalledResponse | InstallTypes | InitializationFailedResponse;
export type TypingInstallerResponseUnion = SetTypings | InvalidateCachedTypings | TypesRegistryResponse | PackageInstalledResponse | InstallTypes | InitializationFailedResponse | WatchTypingLocations;
+14 -4
View File
@@ -4311,12 +4311,21 @@ interface Float64Array {
sort(compareFn?: (a: number, b: number) => number): this;
/**
* Gets a new Float64Array view of the ArrayBuffer store for this array, referencing the elements
* at begin, inclusive, up to end, exclusive.
* @param begin The index of the beginning of the array.
* @param end The index of the end of the array.
*/
subarray(begin?: number, end?: number): Float64Array;
/**
* Converts a number to a string by using the current locale.
*/
toLocaleString(): string;
/**
* Returns a string representation of an array.
*/
toString(): string;
/** Returns the primitive value of the specified object. */
@@ -4365,11 +4374,12 @@ declare var Float64Array: Float64ArrayConstructor;
declare namespace Intl {
interface CollatorOptions {
usage?: string | undefined;
localeMatcher?: string | undefined;
usage?: "sort" | "search" | undefined;
localeMatcher?: "lookup" | "best fit" | undefined;
numeric?: boolean | undefined;
caseFirst?: string | undefined;
sensitivity?: string | undefined;
caseFirst?: "upper" | "lower" | "false" | undefined;
sensitivity?: "base" | "accent" | "case" | "variant" | undefined;
collation?: "big5han" | "compat" | "dict" | "direct" | "ducet" | "emoji" | "eor" | "gb2312" | "phonebk" | "phonetic" | "pinyin" | "reformed" | "searchjl" | "stroke" | "trad" | "unihan" | "zhuyin" | undefined;
ignorePunctuation?: boolean | undefined;
}
+49 -10
View File
@@ -176,6 +176,7 @@ import {
ThrottledOperations,
toNormalizedPath,
TypingsCache,
WatchTypingLocations,
} from "./_namespaces/ts.server";
import * as protocol from "./protocol";
@@ -579,7 +580,7 @@ export interface ProjectServiceOptions {
cancellationToken: HostCancellationToken;
useSingleInferredProject: boolean;
useInferredProjectPerProjectRoot: boolean;
typingsInstaller: ITypingsInstaller;
typingsInstaller?: ITypingsInstaller;
eventHandler?: ProjectServiceEventHandler;
suppressDiagnosticEvents?: boolean;
throttleWaitMilliseconds?: number;
@@ -962,7 +963,7 @@ export class ProjectService {
public readonly globalPlugins: readonly string[];
public readonly pluginProbeLocations: readonly string[];
public readonly allowLocalPluginLoads: boolean;
private currentPluginConfigOverrides: Map<string, any> | undefined;
/** @internal */ currentPluginConfigOverrides: Map<string, any> | undefined;
public readonly typesMapLocation: string | undefined;
@@ -1153,6 +1154,11 @@ export class ProjectService {
}
}
/** @internal */
watchTypingLocations(response: WatchTypingLocations) {
this.findProject(response.projectName)?.watchTypingLocations(response.files);
}
/** @internal */
delayEnsureProjectForOpenFiles() {
if (!this.openFiles.size) return;
@@ -2189,7 +2195,6 @@ export class ProjectService {
/*lastFileExceededProgramSize*/ this.getFilenameForExceededTotalSizeLimitForNonTsFiles(projectFileName, compilerOptions, files, externalFilePropertyReader),
options.compileOnSave === undefined ? true : options.compileOnSave,
/*projectFilePath*/ undefined,
this.currentPluginConfigOverrides,
watchOptionsAndErrors?.watchOptions
);
project.setProjectErrors(watchOptionsAndErrors?.errors);
@@ -2354,7 +2359,7 @@ export class ProjectService {
project.enableLanguageService();
this.watchWildcards(configFilename, configFileExistenceInfo, project);
}
project.enablePluginsWithOptions(compilerOptions, this.currentPluginConfigOverrides);
project.enablePluginsWithOptions(compilerOptions);
const filesToAdd = parsedCommandLine.fileNames.concat(project.getExternalFiles());
this.updateRootAndOptionsOfNonInferredProject(project, filesToAdd, fileNamePropertyReader, compilerOptions, parsedCommandLine.typeAcquisition!, parsedCommandLine.compileOnSave, parsedCommandLine.watchOptions);
tracing?.pop();
@@ -2737,7 +2742,7 @@ export class ProjectService {
typeAcquisition = this.typeAcquisitionForInferredProjects;
}
watchOptionsAndErrors = watchOptionsAndErrors || undefined;
const project = new InferredProject(this, this.documentRegistry, compilerOptions, watchOptionsAndErrors?.watchOptions, projectRootPath, currentDirectory, this.currentPluginConfigOverrides, typeAcquisition);
const project = new InferredProject(this, this.documentRegistry, compilerOptions, watchOptionsAndErrors?.watchOptions, projectRootPath, currentDirectory, typeAcquisition);
project.setProjectErrors(watchOptionsAndErrors?.errors);
if (isSingleInferredProject) {
this.inferredProjects.unshift(project);
@@ -4242,8 +4247,11 @@ export class ProjectService {
return false;
}
/** @internal */
requestEnablePlugin(project: Project, pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<string, any> | undefined) {
/**
* Performs the initial steps of enabling a plugin by finding and instantiating the module for a plugin either asynchronously or synchronously
* @internal
*/
requestEnablePlugin(project: Project, pluginConfigEntry: PluginImport, searchPaths: string[]) {
if (!this.host.importPlugin && !this.host.require) {
this.logger.info("Plugins were requested but not running in environment that supports 'require'. Nothing will be loaded");
return;
@@ -4257,7 +4265,12 @@ export class ProjectService {
// If the host supports dynamic import, begin enabling the plugin asynchronously.
if (this.host.importPlugin) {
const importPromise = project.beginEnablePluginAsync(pluginConfigEntry, searchPaths, pluginConfigOverrides);
const importPromise = Project.importServicePluginAsync(
pluginConfigEntry,
searchPaths,
this.host,
s => this.logger.info(s),
) as Promise<BeginEnablePluginResult>;
this.pendingPluginEnablements ??= new Map();
let promises = this.pendingPluginEnablements.get(project);
if (!promises) this.pendingPluginEnablements.set(project, promises = []);
@@ -4266,7 +4279,33 @@ export class ProjectService {
}
// Otherwise, load the plugin using `require`
project.endEnablePlugin(project.beginEnablePluginSync(pluginConfigEntry, searchPaths, pluginConfigOverrides));
this.endEnablePlugin(project, Project.importServicePluginSync(
pluginConfigEntry,
searchPaths,
this.host,
s => this.logger.info(s),
));
}
/**
* Performs the remaining steps of enabling a plugin after its module has been instantiated.
* @internal
*/
private endEnablePlugin(project: Project, { pluginConfigEntry, resolvedModule, errorLogs }: BeginEnablePluginResult) {
if (resolvedModule) {
const configurationOverride = this.currentPluginConfigOverrides?.get(pluginConfigEntry.name);
if (configurationOverride) {
// Preserve the name property since it's immutable
const pluginName = pluginConfigEntry.name;
pluginConfigEntry = configurationOverride;
pluginConfigEntry.name = pluginName;
}
project.enableProxy(resolvedModule, pluginConfigEntry);
}
else {
forEach(errorLogs, message => this.logger.info(message));
this.logger.info(`Couldn't find ${pluginConfigEntry.name}`);
}
}
/** @internal */
@@ -4345,7 +4384,7 @@ export class ProjectService {
}
for (const result of results) {
project.endEnablePlugin(result);
this.endEnablePlugin(project, result);
}
// Plugins may have modified external files, so mark the project as dirty.
+205 -109
View File
@@ -13,16 +13,19 @@ import {
closeFileWatcher,
closeFileWatcherOf,
combinePaths,
comparePaths,
CompilerHost,
CompilerOptions,
concatenate,
ConfigFileProgramReloadLevel,
containsPath,
createCacheableExportInfoMap,
createLanguageService,
createResolutionCache,
createSymlinkCache,
Debug,
Diagnostic,
directorySeparator,
DirectoryStructureHost,
DirectoryWatcherCallback,
DocumentPositionMapper,
@@ -38,7 +41,6 @@ import {
FileWatcherCallback,
FileWatcherEventKind,
filter,
firstDefined,
flatMap,
forEach,
forEachEntry,
@@ -46,6 +48,7 @@ import {
generateDjb2Hash,
getAllowJSCompilerOption,
getAutomaticTypeDirectiveNames,
getBaseFileName,
GetCanonicalFileName,
getDeclarationEmitOutputFilePathWorker,
getDefaultCompilerOptions,
@@ -58,6 +61,7 @@ import {
getNormalizedAbsolutePath,
getOrUpdate,
getStringComparer,
HasInvalidatedLibResolutions,
HasInvalidatedResolutions,
HostCancellationToken,
inferredTypesContainingFile,
@@ -126,6 +130,7 @@ import {
WatchType,
} from "./_namespaces/ts";
import {
ActionInvalidate,
asNormalizedPath,
createModuleSpecifierCache,
emptyArray,
@@ -252,13 +257,15 @@ export interface PluginModuleWithName {
export type PluginModuleFactory = (mod: { typescript: typeof ts }) => PluginModule;
/** @internal */
export interface BeginEnablePluginResult {
export interface PluginImportResult<T> {
pluginConfigEntry: PluginImport;
pluginConfigOverrides: Map<string, any> | undefined;
resolvedModule: PluginModuleFactory | undefined;
resolvedModule: T | undefined;
errorLogs: string[] | undefined;
}
/** @internal */
export type BeginEnablePluginResult = PluginImportResult<PluginModuleFactory>;
/**
* The project root can be script info - if root is present,
* or it could be just normalized path if root wasn't present on the host(only for non inferred project)
@@ -285,6 +292,14 @@ export interface EmitResult {
diagnostics: readonly Diagnostic[];
}
const enum TypingWatcherType {
FileWatcher = "FileWatcher",
DirectoryWatcher = "DirectoryWatcher"
}
type TypingWatchers = Map<Path, FileWatcher> & { isInvoked?: boolean; };
export abstract class Project implements LanguageServiceHost, ModuleResolutionHost {
private rootFiles: ScriptInfo[] = [];
private rootFilesMap = new Map<string, ProjectRootFile>();
@@ -326,6 +341,9 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
/** @internal */
hasInvalidatedResolutions: HasInvalidatedResolutions | undefined;
/** @internal */
hasInvalidatedLibResolutions: HasInvalidatedLibResolutions | undefined;
/** @internal */
resolutionCache: ResolutionCache;
@@ -365,6 +383,9 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
/** @internal */
typingFiles: SortedReadonlyArray<string> = emptyArray;
/** @internal */
private typingWatchers: TypingWatchers | undefined;
/** @internal */
originalConfiguredProjects: Set<NormalizedPath> | undefined;
@@ -394,36 +415,62 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
return hasOneOrMoreJsAndNoTsFiles(this);
}
public static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void, logErrors?: (message: string) => void): {} | undefined {
const resolvedPath = normalizeSlashes(host.resolvePath(combinePaths(initialDir, "node_modules")));
log(`Loading ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`);
const result = host.require!(resolvedPath, moduleName); // TODO: GH#18217
if (result.error) {
const err = result.error.stack || result.error.message || JSON.stringify(result.error);
(logErrors || log)(`Failed to load module '${moduleName}' from ${resolvedPath}: ${err}`);
return undefined;
}
return result.module;
public static resolveModule(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void): {} | undefined {
return Project.importServicePluginSync({ name: moduleName }, [initialDir], host, log).resolvedModule;
}
/** @internal */
public static async importServicePluginAsync(moduleName: string, initialDir: string, host: ServerHost, log: (message: string) => void, logErrors?: (message: string) => void): Promise<{} | undefined> {
Debug.assertIsDefined(host.importPlugin);
const resolvedPath = combinePaths(initialDir, "node_modules");
log(`Dynamically importing ${moduleName} from ${initialDir} (resolved to ${resolvedPath})`);
let result: ModuleImportResult;
try {
result = await host.importPlugin(resolvedPath, moduleName);
}
catch (e) {
result = { module: undefined, error: e };
}
if (result.error) {
public static importServicePluginSync<T = {}>(
pluginConfigEntry: PluginImport,
searchPaths: string[],
host: ServerHost,
log: (message: string) => void,
): PluginImportResult<T> {
Debug.assertIsDefined(host.require);
let errorLogs: string[] | undefined;
let resolvedModule: T | undefined;
for (const initialDir of searchPaths) {
const resolvedPath = normalizeSlashes(host.resolvePath(combinePaths(initialDir, "node_modules")));
log(`Loading ${pluginConfigEntry.name} from ${initialDir} (resolved to ${resolvedPath})`);
const result = host.require(resolvedPath, pluginConfigEntry.name); // TODO: GH#18217
if (!result.error) {
resolvedModule = result.module as T;
break;
}
const err = result.error.stack || result.error.message || JSON.stringify(result.error);
(logErrors || log)(`Failed to dynamically import module '${moduleName}' from ${resolvedPath}: ${err}`);
return undefined;
(errorLogs ??= []).push(`Failed to load module '${pluginConfigEntry.name}' from ${resolvedPath}: ${err}`);
}
return result.module;
return { pluginConfigEntry, resolvedModule, errorLogs };
}
/** @internal */
public static async importServicePluginAsync<T = {}>(
pluginConfigEntry: PluginImport,
searchPaths: string[],
host: ServerHost,
log: (message: string) => void,
): Promise<PluginImportResult<T>> {
Debug.assertIsDefined(host.importPlugin);
let errorLogs: string[] | undefined;
let resolvedModule: T | undefined;
for (const initialDir of searchPaths) {
const resolvedPath = combinePaths(initialDir, "node_modules");
log(`Dynamically importing ${pluginConfigEntry.name} from ${initialDir} (resolved to ${resolvedPath})`);
let result: ModuleImportResult;
try {
result = await host.importPlugin(resolvedPath, pluginConfigEntry.name);
}
catch (e) {
result = { module: undefined, error: e };
}
if (!result.error) {
resolvedModule = result.module as T;
break;
}
const err = result.error.stack || result.error.message || JSON.stringify(result.error);
(errorLogs ??= []).push(`Failed to dynamically import module '${pluginConfigEntry.name}' from ${resolvedPath}: ${err}`);
}
return { pluginConfigEntry, resolvedModule, errorLogs };
}
/** @internal */
@@ -684,6 +731,11 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
);
}
/** @internal */
resolveLibrary(libraryName: string, resolveFrom: string, options: CompilerOptions, libFileName: string): ResolvedModuleWithFailedLookupLocations {
return this.resolutionCache.resolveLibrary(libraryName, resolveFrom, options, libFileName);
}
directoryExists(path: string): boolean {
return this.directoryStructureHost.directoryExists!(path); // TODO: GH#18217
}
@@ -976,6 +1028,8 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
}
close() {
this.projectService.typingsCache.onProjectClosed(this);
this.closeWatchingTypingLocations();
if (this.program) {
// if we have a program - release all files that are enlisted in program but arent root
// The releasing of the roots happens later
@@ -1324,6 +1378,108 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
}
}
/** @internal */
private closeWatchingTypingLocations() {
if (this.typingWatchers) clearMap(this.typingWatchers, closeFileWatcher);
this.typingWatchers = undefined;
}
/** @internal */
private onTypingInstallerWatchInvoke() {
this.typingWatchers!.isInvoked = true;
this.projectService.updateTypingsForProject({ projectName: this.getProjectName(), kind: ActionInvalidate });
}
/** @internal */
watchTypingLocations(files: readonly string[] | undefined) {
if (!files) {
this.typingWatchers!.isInvoked = false;
return;
}
if (!files.length) {
// shut down existing watchers
this.closeWatchingTypingLocations();
return;
}
const toRemove = new Map(this.typingWatchers);
if (!this.typingWatchers) this.typingWatchers = new Map();
// handler should be invoked once for the entire set of files since it will trigger full rediscovery of typings
this.typingWatchers.isInvoked = false;
const createProjectWatcher = (path: string, typingsWatcherType: TypingWatcherType) => {
const canonicalPath = this.toPath(path);
toRemove.delete(canonicalPath);
if (!this.typingWatchers!.has(canonicalPath)) {
this.typingWatchers!.set(canonicalPath, typingsWatcherType === TypingWatcherType.FileWatcher ?
this.projectService.watchFactory.watchFile(
path,
() => !this.typingWatchers!.isInvoked ?
this.onTypingInstallerWatchInvoke() :
this.writeLog(`TypingWatchers already invoked`),
PollingInterval.High,
this.projectService.getWatchOptions(this),
WatchType.TypingInstallerLocationFile,
this,
) :
this.projectService.watchFactory.watchDirectory(
path,
f => {
if (this.typingWatchers!.isInvoked) return this.writeLog(`TypingWatchers already invoked`);
if (!fileExtensionIs(f, Extension.Json)) return this.writeLog(`Ignoring files that are not *.json`);
if (comparePaths(f, combinePaths(this.projectService.typingsInstaller.globalTypingsCacheLocation!, "package.json"), !this.useCaseSensitiveFileNames())) return this.writeLog(`Ignoring package.json change at global typings location`);
this.onTypingInstallerWatchInvoke();
},
WatchDirectoryFlags.Recursive,
this.projectService.getWatchOptions(this),
WatchType.TypingInstallerLocationDirectory,
this,
)
);
}
};
// Create watches from list of files
for (const file of files) {
const basename = getBaseFileName(file);
if (basename === "package.json" || basename === "bower.json") {
// package.json or bower.json exists, watch the file to detect changes and update typings
createProjectWatcher(file, TypingWatcherType.FileWatcher);
continue;
}
// path in projectRoot, watch project root
if (containsPath(this.currentDirectory, file, this.currentDirectory, !this.useCaseSensitiveFileNames())) {
const subDirectory = file.indexOf(directorySeparator, this.currentDirectory.length + 1);
if (subDirectory !== -1) {
// Watch subDirectory
createProjectWatcher(file.substr(0, subDirectory), TypingWatcherType.DirectoryWatcher);
}
else {
// Watch the directory itself
createProjectWatcher(file, TypingWatcherType.DirectoryWatcher);
}
continue;
}
// path in global cache, watch global cache
if (containsPath(this.projectService.typingsInstaller.globalTypingsCacheLocation!, file, this.currentDirectory, !this.useCaseSensitiveFileNames())) {
createProjectWatcher(this.projectService.typingsInstaller.globalTypingsCacheLocation!, TypingWatcherType.DirectoryWatcher);
continue;
}
// watch node_modules or bower_components
createProjectWatcher(file, TypingWatcherType.DirectoryWatcher);
}
// Remove unused watches
toRemove.forEach((watch, path) => {
watch.close();
this.typingWatchers!.delete(path);
});
}
/** @internal */
getCurrentProgram(): Program | undefined {
return this.program;
@@ -1339,7 +1495,9 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
Debug.assert(!this.isClosed(), "Called update graph worker of closed project");
this.writeLog(`Starting updateGraphWorker: Project: ${this.getProjectName()}`);
const start = timestamp();
this.hasInvalidatedResolutions = this.resolutionCache.createHasInvalidatedResolutions(returnFalse);
const { hasInvalidatedResolutions, hasInvalidatedLibResolutions } = this.resolutionCache.createHasInvalidatedResolutions(returnFalse, returnFalse);
this.hasInvalidatedResolutions = hasInvalidatedResolutions;
this.hasInvalidatedLibResolutions = hasInvalidatedLibResolutions;
this.resolutionCache.startCachingPerDirectoryResolution();
this.program = this.languageService.getProgram(); // TODO: GH#18217
this.dirty = false;
@@ -1643,7 +1801,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
// reset cached unresolved imports if changes in compiler options affected module resolution
this.cachedUnresolvedImportsPerFile.clear();
this.lastCachedUnresolvedImportsList = undefined;
this.resolutionCache.clear();
this.resolutionCache.onChangesAffectModuleResolution();
this.moduleSpecifierCache.clear();
}
this.markAsDirty();
@@ -1796,7 +1954,7 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
];
}
protected enableGlobalPlugins(options: CompilerOptions, pluginConfigOverrides: Map<string, any> | undefined): void {
protected enableGlobalPlugins(options: CompilerOptions): void {
if (!this.projectService.globalPlugins.length) return;
const host = this.projectService.host;
@@ -1817,80 +1975,16 @@ export abstract class Project implements LanguageServiceHost, ModuleResolutionHo
// Provide global: true so plugins can detect why they can't find their config
this.projectService.logger.info(`Loading global plugin ${globalPluginName}`);
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths, pluginConfigOverrides);
this.enablePlugin({ name: globalPluginName, global: true } as PluginImport, searchPaths);
}
}
/**
* Performs the initial steps of enabling a plugin by finding and instantiating the module for a plugin synchronously using 'require'.
*
* @internal
*/
beginEnablePluginSync(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<string, any> | undefined): BeginEnablePluginResult {
Debug.assertIsDefined(this.projectService.host.require);
let errorLogs: string[] | undefined;
const log = (message: string) => this.projectService.logger.info(message);
const logError = (message: string) => {
(errorLogs ??= []).push(message);
};
const resolvedModule = firstDefined(searchPaths, searchPath =>
Project.resolveModule(pluginConfigEntry.name, searchPath, this.projectService.host, log, logError) as PluginModuleFactory | undefined);
return { pluginConfigEntry, pluginConfigOverrides, resolvedModule, errorLogs };
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[]): void {
this.projectService.requestEnablePlugin(this, pluginConfigEntry, searchPaths);
}
/**
* Performs the initial steps of enabling a plugin by finding and instantiating the module for a plugin asynchronously using dynamic `import`.
*
* @internal
*/
async beginEnablePluginAsync(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<string, any> | undefined): Promise<BeginEnablePluginResult> {
Debug.assertIsDefined(this.projectService.host.importPlugin);
let errorLogs: string[] | undefined;
const log = (message: string) => this.projectService.logger.info(message);
const logError = (message: string) => {
(errorLogs ??= []).push(message);
};
let resolvedModule: PluginModuleFactory | undefined;
for (const searchPath of searchPaths) {
resolvedModule = await Project.importServicePluginAsync(pluginConfigEntry.name, searchPath, this.projectService.host, log, logError) as PluginModuleFactory | undefined;
if (resolvedModule !== undefined) {
break;
}
}
return { pluginConfigEntry, pluginConfigOverrides, resolvedModule, errorLogs };
}
/**
* Performs the remaining steps of enabling a plugin after its module has been instantiated.
*
* @internal
*/
endEnablePlugin({ pluginConfigEntry, pluginConfigOverrides, resolvedModule, errorLogs }: BeginEnablePluginResult) {
if (resolvedModule) {
const configurationOverride = pluginConfigOverrides && pluginConfigOverrides.get(pluginConfigEntry.name);
if (configurationOverride) {
// Preserve the name property since it's immutable
const pluginName = pluginConfigEntry.name;
pluginConfigEntry = configurationOverride;
pluginConfigEntry.name = pluginName;
}
this.enableProxy(resolvedModule, pluginConfigEntry);
}
else {
forEach(errorLogs, message => this.projectService.logger.info(message));
this.projectService.logger.info(`Couldn't find ${pluginConfigEntry.name}`);
}
}
protected enablePlugin(pluginConfigEntry: PluginImport, searchPaths: string[], pluginConfigOverrides: Map<string, any> | undefined): void {
this.projectService.requestEnablePlugin(this, pluginConfigEntry, searchPaths, pluginConfigOverrides);
}
private enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) {
/** @internal */
enableProxy(pluginModuleFactory: PluginModuleFactory, configEntry: PluginImport) {
try {
if (typeof pluginModuleFactory !== "function") {
this.projectService.logger.info(`Skipped loading plugin ${configEntry.name} because it did not expose a proper factory function`);
@@ -2177,7 +2271,6 @@ export class InferredProject extends Project {
watchOptions: WatchOptions | undefined,
projectRootPath: NormalizedPath | undefined,
currentDirectory: string,
pluginConfigOverrides: Map<string, any> | undefined,
typeAcquisition: TypeAcquisition | undefined) {
super(projectService.newInferredProjectName(),
ProjectKind.Inferred,
@@ -2196,7 +2289,7 @@ export class InferredProject extends Project {
if (!projectRootPath && !projectService.useSingleInferredProject) {
this.canonicalCurrentDirectory = projectService.toCanonicalFileName(this.currentDirectory);
}
this.enableGlobalPlugins(this.getCompilerOptions(), pluginConfigOverrides);
this.enableGlobalPlugins(this.getCompilerOptions());
}
override addRoot(info: ScriptInfo) {
@@ -2205,13 +2298,17 @@ export class InferredProject extends Project {
if (!this._isJsInferredProject && info.isJavaScript()) {
this.toggleJsInferredProject(/*isJsInferredProject*/ true);
}
else if (this.isOrphan() && this._isJsInferredProject && !info.isJavaScript()) {
this.toggleJsInferredProject(/*isJsInferredProject*/ false);
}
super.addRoot(info);
}
override removeRoot(info: ScriptInfo) {
this.projectService.stopWatchingConfigFilesForInferredProjectRoot(info);
super.removeRoot(info);
if (this._isJsInferredProject && info.isJavaScript()) {
// Delay toggling to isJsInferredProject = false till we actually need it again
if (!this.isOrphan() && this._isJsInferredProject && info.isJavaScript()) {
if (every(this.getRootScriptInfos(), rootInfo => !rootInfo.isJavaScript())) {
this.toggleJsInferredProject(/*isJsInferredProject*/ false);
}
@@ -2714,7 +2811,7 @@ export class ConfiguredProject extends Project {
}
/** @internal */
enablePluginsWithOptions(options: CompilerOptions, pluginConfigOverrides: Map<string, any> | undefined): void {
enablePluginsWithOptions(options: CompilerOptions): void {
this.plugins.length = 0;
if (!options.plugins?.length && !this.projectService.globalPlugins.length) return;
const host = this.projectService.host;
@@ -2733,11 +2830,11 @@ export class ConfiguredProject extends Project {
// Enable tsconfig-specified plugins
if (options.plugins) {
for (const pluginConfigEntry of options.plugins) {
this.enablePlugin(pluginConfigEntry, searchPaths, pluginConfigOverrides);
this.enablePlugin(pluginConfigEntry, searchPaths);
}
}
return this.enableGlobalPlugins(options, pluginConfigOverrides);
return this.enableGlobalPlugins(options);
}
/**
@@ -2869,7 +2966,6 @@ export class ExternalProject extends Project {
lastFileExceededProgramSize: string | undefined,
public override compileOnSaveEnabled: boolean,
projectFilePath?: string,
pluginConfigOverrides?: Map<string, any>,
watchOptions?: WatchOptions) {
super(externalProjectName,
ProjectKind.External,
@@ -2882,7 +2978,7 @@ export class ExternalProject extends Project {
watchOptions,
projectService.host,
getDirectoryPath(projectFilePath || normalizeSlashes(externalProjectName)));
this.enableGlobalPlugins(this.getCompilerOptions(), pluginConfigOverrides);
this.enableGlobalPlugins(this.getCompilerOptions());
}
override updateGraph() {
+39
View File
@@ -4,6 +4,7 @@ import type {
EndOfLineState,
FileExtensionInfo,
HighlightSpanKind,
InteractiveRefactorArguments,
MapLike,
OutliningSpanKind,
OutputFile,
@@ -142,6 +143,7 @@ export const enum CommandTypes {
GetApplicableRefactors = "getApplicableRefactors",
GetEditsForRefactor = "getEditsForRefactor",
GetMoveToRefactoringFileSuggestions = "getMoveToRefactoringFileSuggestions",
/** @internal */
GetEditsForRefactorFull = "getEditsForRefactor-full",
@@ -586,6 +588,14 @@ export interface GetApplicableRefactorsRequest extends Request {
export type GetApplicableRefactorsRequestArgs = FileLocationOrRangeRequestArgs & {
triggerReason?: RefactorTriggerReason;
kind?: string;
/**
* Include refactor actions that require additional arguments to be passed when
* calling 'GetEditsForRefactor'. When true, clients should inspect the
* `isInteractive` property of each returned `RefactorActionInfo`
* and ensure they are able to collect the appropriate arguments for any
* interactive refactor before offering it.
*/
includeInteractiveActions?: boolean;
};
export type RefactorTriggerReason = "implicit" | "invoked";
@@ -598,6 +608,27 @@ export interface GetApplicableRefactorsResponse extends Response {
body?: ApplicableRefactorInfo[];
}
/**
* Request refactorings at a given position or selection area to move to an existing file.
*/
export interface GetMoveToRefactoringFileSuggestionsRequest extends Request {
command: CommandTypes.GetMoveToRefactoringFileSuggestions;
arguments: GetMoveToRefactoringFileSuggestionsRequestArgs;
}
export type GetMoveToRefactoringFileSuggestionsRequestArgs = FileLocationOrRangeRequestArgs & {
kind?: string;
};
/**
* Response is a list of available files.
* Each refactoring exposes one or more "Actions"; a user selects one action to invoke a refactoring
*/
export interface GetMoveToRefactoringFileSuggestions extends Response {
body: {
newFileName: string;
files: string[];
};
}
/**
* A set of one or more available refactoring actions, grouped under a parent refactoring.
*/
@@ -650,6 +681,12 @@ export interface RefactorActionInfo {
* The hierarchical dotted name of the refactor action.
*/
kind?: string;
/**
* Indicates that the action requires additional arguments to be passed
* when calling 'GetEditsForRefactor'.
*/
isInteractive?: boolean;
}
export interface GetEditsForRefactorRequest extends Request {
@@ -666,6 +703,8 @@ export type GetEditsForRefactorRequestArgs = FileLocationOrRangeRequestArgs & {
refactor: string;
/* The 'name' property from the refactoring action */
action: string;
/* Arguments for interactive action */
interactiveRefactorArguments?: InteractiveRefactorArguments;
};
+24 -11
View File
@@ -136,7 +136,6 @@ import {
toFileNameLowerCase,
tracing,
unmangleScopedPackageName,
UserPreferences,
version,
WithMetadata,
} from "./_namespaces/ts";
@@ -161,6 +160,7 @@ import {
LogLevel,
Msg,
NormalizedPath,
nullTypingsInstaller,
Project,
ProjectInfoTelemetryEvent,
ProjectKind,
@@ -319,7 +319,7 @@ export function formatMessage<T extends protocol.Message>(msg: T, logger: Logger
const json = JSON.stringify(msg);
if (verboseLogging) {
logger.info(`${msg.type}:${indent(json)}`);
logger.info(`${msg.type}:${indent(JSON.stringify(msg, undefined, " "))}`);
}
const len = byteLength(json, "utf8");
@@ -496,14 +496,14 @@ function getRenameLocationsWorker(
initialLocation: DocumentPosition,
findInStrings: boolean,
findInComments: boolean,
{ providePrefixAndSuffixTextForRename }: UserPreferences
preferences: protocol.UserPreferences
): readonly RenameLocation[] {
const perProjectResults = getPerProjectReferences(
projects,
defaultProject,
initialLocation,
/*isForRename*/ true,
(project, position) => project.getLanguageService().findRenameLocations(position.fileName, position.pos, findInStrings, findInComments, providePrefixAndSuffixTextForRename),
(project, position) => project.getLanguageService().findRenameLocations(position.fileName, position.pos, findInStrings, findInComments, preferences),
(renameLocation, cb) => cb(documentSpanLocation(renameLocation)),
);
@@ -881,6 +881,7 @@ const invalidPartialSemanticModeCommands: readonly protocol.CommandTypes[] = [
protocol.CommandTypes.ApplyCodeActionCommand,
protocol.CommandTypes.GetSupportedCodeFixes,
protocol.CommandTypes.GetApplicableRefactors,
protocol.CommandTypes.GetMoveToRefactoringFileSuggestions,
protocol.CommandTypes.GetEditsForRefactor,
protocol.CommandTypes.GetEditsForRefactorFull,
protocol.CommandTypes.OrganizeImports,
@@ -926,7 +927,7 @@ export interface SessionOptions {
cancellationToken: ServerCancellationToken;
useSingleInferredProject: boolean;
useInferredProjectPerProjectRoot: boolean;
typingsInstaller: ITypingsInstaller;
typingsInstaller?: ITypingsInstaller;
byteLength: (buf: string, encoding?: BufferEncoding) => number;
hrtime: (start?: [number, number]) => [number, number];
logger: Logger;
@@ -972,7 +973,7 @@ export class Session<TMessage = string> implements EventSender {
constructor(opts: SessionOptions) {
this.host = opts.host;
this.cancellationToken = opts.cancellationToken;
this.typingsInstaller = opts.typingsInstaller;
this.typingsInstaller = opts.typingsInstaller || nullTypingsInstaller;
this.byteLength = opts.byteLength;
this.hrtime = opts.hrtime;
this.logger = opts.logger;
@@ -2674,7 +2675,7 @@ export class Session<TMessage = string> implements EventSender {
private getApplicableRefactors(args: protocol.GetApplicableRefactorsRequestArgs): protocol.ApplicableRefactorInfo[] {
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file)!;
return project.getLanguageService().getApplicableRefactors(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file), args.triggerReason, args.kind);
return project.getLanguageService().getApplicableRefactors(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file), args.triggerReason, args.kind, args.includeInteractiveActions);
}
private getEditsForRefactor(args: protocol.GetEditsForRefactorRequestArgs, simplifiedResult: boolean): RefactorEditInfo | protocol.RefactorEditInfo {
@@ -2687,6 +2688,7 @@ export class Session<TMessage = string> implements EventSender {
args.refactor,
args.action,
this.getPreferences(file),
args.interactiveRefactorArguments
);
if (result === undefined) {
@@ -2702,11 +2704,19 @@ export class Session<TMessage = string> implements EventSender {
const renameScriptInfo = project.getScriptInfoForNormalizedPath(toNormalizedPath(renameFilename))!;
mappedRenameLocation = getLocationInNewDocument(getSnapshotText(renameScriptInfo.getSnapshot()), renameFilename, renameLocation, edits);
}
return { renameLocation: mappedRenameLocation, renameFilename, edits: this.mapTextChangesToCodeEdits(edits) };
}
else {
return result;
return {
renameLocation: mappedRenameLocation,
renameFilename,
edits: this.mapTextChangesToCodeEdits(edits)
};
}
return result;
}
private getMoveToRefactoringFileSuggestions(args: protocol.GetMoveToRefactoringFileSuggestionsRequestArgs): { newFileName: string, files: string[] }{
const { file, project } = this.getFileAndProject(args);
const scriptInfo = project.getScriptInfoForNormalizedPath(file)!;
return project.getLanguageService().getMoveToRefactoringFileSuggestions(file, this.extractPositionOrRange(args, scriptInfo), this.getPreferences(file));
}
private organizeImports(args: protocol.OrganizeImportsRequestArgs, simplifiedResult: boolean): readonly protocol.FileCodeEdits[] | readonly FileTextChanges[] {
@@ -3429,6 +3439,9 @@ export class Session<TMessage = string> implements EventSender {
[protocol.CommandTypes.GetEditsForRefactor]: (request: protocol.GetEditsForRefactorRequest) => {
return this.requiredResponse(this.getEditsForRefactor(request.arguments, /*simplifiedResult*/ true));
},
[protocol.CommandTypes.GetMoveToRefactoringFileSuggestions]: (request: protocol.GetMoveToRefactoringFileSuggestionsRequest) => {
return this.requiredResponse(this.getMoveToRefactoringFileSuggestions(request.arguments));
},
[protocol.CommandTypes.GetEditsForRefactorFull]: (request: protocol.GetEditsForRefactorRequest) => {
return this.requiredResponse(this.getEditsForRefactor(request.arguments, /*simplifiedResult*/ false));
},
-1
View File
@@ -46,7 +46,6 @@ export function createInstallTypingsRequest(project: Project, typeAcquisition: T
projectName: project.getProjectName(),
fileNames: project.getFileNames(/*excludeFilesFromExternalLibraries*/ true, /*excludeConfigFiles*/ true).concat(project.getExcludedFiles() as NormalizedPath[]),
compilerOptions: project.getCompilationSettings(),
watchOptions: project.projectService.getWatchOptions(project),
typeAcquisition,
unresolvedImports,
projectRootPath: project.getCurrentDirectory() as Path,
+1
View File
@@ -6,6 +6,7 @@ export * from "../refactors/convertImport";
export * from "../refactors/extractType";
export * from "../refactors/helpers";
export * from "../refactors/moveToNewFile";
export * from "../refactors/moveToFile";
import * as addOrRemoveBracesToArrowFunction from "./ts.refactor.addOrRemoveBracesToArrowFunction";
export { addOrRemoveBracesToArrowFunction };
import * as convertArrowFunctionOrFunctionExpression from "./ts.refactor.convertArrowFunctionOrFunctionExpression";
+4 -4
View File
@@ -2938,7 +2938,7 @@ function getContextualType(previousToken: Node, position: number, sourceFile: So
isEqualityOperatorKind(previousToken.kind) && isBinaryExpression(parent) && isEqualityOperatorKind(parent.operatorToken.kind) ?
// completion at `x ===/**/` should be for the right side
checker.getTypeAtLocation(parent.left) :
checker.getContextualType(previousToken as Expression);
checker.getContextualType(previousToken as Expression, ContextFlags.Completions) || checker.getContextualType(previousToken as Expression);
}
}
@@ -4607,9 +4607,9 @@ function getCompletionData(
return isDeclarationName(contextToken)
&& !isShorthandPropertyAssignment(contextToken.parent)
&& !isJsxAttribute(contextToken.parent)
// Don't block completions if we're in `class C /**/` or `interface I /**/`, because we're *past* the end of the identifier and might want to complete `extends`.
// If `contextToken !== previousToken`, this is `class C ex/**/ or `interface I ex/**/``.
&& !((isClassLike(contextToken.parent) || isInterfaceDeclaration(contextToken.parent)) && (contextToken !== previousToken || position > previousToken.end));
// Don't block completions if we're in `class C /**/`, `interface I /**/` or `<T /**/>` , because we're *past* the end of the identifier and might want to complete `extends`.
// If `contextToken !== previousToken`, this is `class C ex/**/`, `interface I ex/**/` or `<T ex/**/>`.
&& !((isClassLike(contextToken.parent) || isInterfaceDeclaration(contextToken.parent) || isTypeParameterDeclaration(contextToken.parent)) && (contextToken !== previousToken || position > previousToken.end));
}
function isPreviousPropertyDeclarationTerminated(contextToken: Node, position: number) {
+12 -3
View File
@@ -72,6 +72,7 @@ import {
getNodeKind,
getPropertySymbolFromBindingElement,
getPropertySymbolsFromContextualType,
getQuoteFromPreference,
getReferencedFileLocation,
getSuperContainer,
getSymbolId,
@@ -151,6 +152,7 @@ import {
isNamespaceExportDeclaration,
isNewExpressionTarget,
isNoSubstitutionTemplateLiteral,
isNumericLiteral,
isObjectBindingElementWithoutPropertyName,
isObjectLiteralExpression,
isObjectLiteralMethod,
@@ -205,6 +207,7 @@ import {
PropertyAssignment,
PropertyDeclaration,
punctuationPart,
QuotePreference,
rangeIsOnSingleLine,
ReferencedSymbol,
ReferencedSymbolDefinitionInfo,
@@ -673,8 +676,8 @@ function getDefinitionKindAndDisplayParts(symbol: Symbol, checker: TypeChecker,
}
/** @internal */
export function toRenameLocation(entry: Entry, originalNode: Node, checker: TypeChecker, providePrefixAndSuffixText: boolean): RenameLocation {
return { ...entryToDocumentSpan(entry), ...(providePrefixAndSuffixText && getPrefixAndSuffixText(entry, originalNode, checker)) };
export function toRenameLocation(entry: Entry, originalNode: Node, checker: TypeChecker, providePrefixAndSuffixText: boolean, quotePreference: QuotePreference): RenameLocation {
return { ...entryToDocumentSpan(entry), ...(providePrefixAndSuffixText && getPrefixAndSuffixText(entry, originalNode, checker, quotePreference)) };
}
function toReferencedSymbolEntry(entry: Entry, symbol: Symbol | undefined): ReferencedSymbolEntry {
@@ -716,7 +719,7 @@ function entryToDocumentSpan(entry: Entry): DocumentSpan {
}
interface PrefixAndSuffix { readonly prefixText?: string; readonly suffixText?: string; }
function getPrefixAndSuffixText(entry: Entry, originalNode: Node, checker: TypeChecker): PrefixAndSuffix {
function getPrefixAndSuffixText(entry: Entry, originalNode: Node, checker: TypeChecker, quotePreference: QuotePreference): PrefixAndSuffix {
if (entry.kind !== EntryKind.Span && isIdentifier(originalNode)) {
const { node, kind } = entry;
const parent = node.parent;
@@ -760,6 +763,12 @@ function getPrefixAndSuffixText(entry: Entry, originalNode: Node, checker: TypeC
}
}
// If the node is a numerical indexing literal, then add quotes around the property access.
if (entry.kind !== EntryKind.Span && isNumericLiteral(entry.node) && isAccessExpression(entry.node.parent)) {
const quote = getQuoteFromPreference(quotePreference);
return { prefixText: quote, suffixText: quote };
}
return emptyOptions;
}
+5 -4
View File
@@ -2,6 +2,7 @@ import {
ApplicableRefactorInfo,
arrayFrom,
flatMapIterator,
InteractiveRefactorArguments,
Refactor,
RefactorContext,
RefactorEditInfo,
@@ -22,15 +23,15 @@ export function registerRefactor(name: string, refactor: Refactor) {
}
/** @internal */
export function getApplicableRefactors(context: RefactorContext): ApplicableRefactorInfo[] {
export function getApplicableRefactors(context: RefactorContext, includeInteractiveActions?: boolean): ApplicableRefactorInfo[] {
return arrayFrom(flatMapIterator(refactors.values(), refactor =>
context.cancellationToken && context.cancellationToken.isCancellationRequested() ||
!refactor.kinds?.some(kind => refactorKindBeginsWith(kind, context.kind)) ? undefined :
refactor.getAvailableActions(context)));
refactor.getAvailableActions(context, includeInteractiveActions)));
}
/** @internal */
export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string): RefactorEditInfo | undefined {
export function getEditsForRefactor(context: RefactorContext, refactorName: string, actionName: string, interactiveRefactorArguments?: InteractiveRefactorArguments): RefactorEditInfo | undefined {
const refactor = refactors.get(refactorName);
return refactor && refactor.getEditsForAction(context, actionName);
return refactor && refactor.getEditsForAction(context, actionName, interactiveRefactorArguments);
}
File diff suppressed because it is too large Load Diff
+34 -870
View File
@@ -1,135 +1,56 @@
import { getModuleSpecifier } from "../../compiler/moduleSpecifiers";
import {
AnyImportOrRequireStatement,
append,
ApplicableRefactorInfo,
AssignmentDeclarationKind,
BinaryExpression,
BindingElement,
BindingName,
CallExpression,
canHaveDecorators,
canHaveModifiers,
canHaveSymbol, cast,
ClassDeclaration,
codefix,
combinePaths,
concatenate,
contains,
copyEntries,
createModuleSpecifierResolutionHost,
createTextRangeFromSpan,
Debug,
Declaration,
DeclarationStatement,
Diagnostics,
emptyArray,
EnumDeclaration,
escapeLeadingUnderscores,
Expression,
ExpressionStatement,
extensionFromPath,
ExternalModuleReference,
factory,
find,
FindAllReferences,
findIndex,
firstDefined,
flatMap,
forEachEntry,
FunctionDeclaration,
getAssignmentDeclarationKind,
fileShouldUseJavaScriptRequire,
getBaseFileName,
GetCanonicalFileName,
getDecorators,
getDirectoryPath,
getLocaleSpecificMessage,
getModifiers,
getPropertySymbolFromBindingElement,
getQuotePreference,
getRangesWhere,
getRefactorContextSpan,
getRelativePathFromFile,
getSymbolId,
getUniqueName,
hasSyntacticModifier,
hostGetCanonicalFileName,
Identifier,
ImportDeclaration,
ImportEqualsDeclaration,
insertImports,
InterfaceDeclaration,
InternalSymbolName,
isArrayLiteralExpression,
isBinaryExpression,
isBindingElement,
isDeclarationName,
isExpressionStatement,
isExternalModuleReference,
isIdentifier,
isImportDeclaration,
isImportEqualsDeclaration,
isNamedDeclaration,
isObjectLiteralExpression,
isOmittedExpression,
isPrologueDirective,
isPropertyAccessExpression,
isPropertyAssignment,
isRequireCall,
isSourceFile,
isStringLiteral,
isStringLiteralLike,
isVariableDeclaration,
isVariableDeclarationList,
isVariableStatement,
LanguageServiceHost,
last,
length,
makeImportIfNecessary,
makeStringLiteral,
mapDefined,
ModifierFlags,
ModifierLike,
ModuleDeclaration,
NamedImportBindings,
Node,
NodeFlags,
nodeSeenTracker,
normalizePath,
ObjectBindingElementWithoutPropertyName,
Program,
PropertyAccessExpression,
PropertyAssignment,
QuotePreference,
rangeContainsRange,
RefactorContext,
RefactorEditInfo,
RequireOrImportCall,
RequireVariableStatement,
resolvePath,
ScriptTarget,
skipAlias,
some,
SourceFile,
Statement,
StringLiteralLike,
Symbol,
SymbolFlags,
symbolNameNoDefault,
SyntaxKind,
takeWhile,
textChanges,
TransformFlags,
tryCast,
TypeAliasDeclaration,
TypeChecker,
TypeNode,
UserPreferences,
VariableDeclaration,
VariableDeclarationList,
VariableStatement,
} from "../_namespaces/ts";
import { registerRefactor } from "../_namespaces/ts.refactor";
import {
addExports,
addExportToChanges,
addNewFileToTsconfig,
createNewFileName,
createOldFileImportsFromTargetFile,
deleteMovedStatements,
deleteUnusedOldImports,
filterImport,
forEachImportInStatement,
getStatementsToMove,
getTopLevelDeclarationStatement,
getUsageInfo,
isTopLevelDeclaration,
makeImportOrRequire,
moduleSpecifierFromImport,
nameOfTopLevelDeclaration,
registerRefactor,
SupportedImportStatement,
ToMove,
updateImportsInOtherFiles,
UsageInfo
} from "../_namespaces/ts.refactor";
const refactorName = "Move to a new file";
const description = getLocaleSpecificMessage(Diagnostics.Move_to_a_new_file);
@@ -156,55 +77,16 @@ registerRefactor(refactorName, {
getEditsForAction: function getRefactorEditsToMoveToNewFile(context, actionName): RefactorEditInfo {
Debug.assert(actionName === refactorName, "Wrong refactor invoked");
const statements = Debug.checkDefined(getStatementsToMove(context));
const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, statements, t, context.host, context.preferences));
const edits = textChanges.ChangeTracker.with(context, t => doChange(context.file, context.program, statements, t, context.host, context.preferences, context));
return { edits, renameFilename: undefined, renameLocation: undefined };
}
});
interface RangeToMove { readonly toMove: readonly Statement[]; readonly afterLast: Statement | undefined; }
function getRangeToMove(context: RefactorContext): RangeToMove | undefined {
const { file } = context;
const range = createTextRangeFromSpan(getRefactorContextSpan(context));
const { statements } = file;
const startNodeIndex = findIndex(statements, s => s.end > range.pos);
if (startNodeIndex === -1) return undefined;
const startStatement = statements[startNodeIndex];
if (isNamedDeclaration(startStatement) && startStatement.name && rangeContainsRange(startStatement.name, range)) {
return { toMove: [statements[startNodeIndex]], afterLast: statements[startNodeIndex + 1] };
}
// Can't only partially include the start node or be partially into the next node
if (range.pos > startStatement.getStart(file)) return undefined;
const afterEndNodeIndex = findIndex(statements, s => s.end > range.end, startNodeIndex);
// Can't be partially into the next node
if (afterEndNodeIndex !== -1 && (afterEndNodeIndex === 0 || statements[afterEndNodeIndex].getStart(file) < range.end)) return undefined;
return {
toMove: statements.slice(startNodeIndex, afterEndNodeIndex === -1 ? statements.length : afterEndNodeIndex),
afterLast: afterEndNodeIndex === -1 ? undefined : statements[afterEndNodeIndex],
};
}
function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes: textChanges.ChangeTracker, host: LanguageServiceHost, preferences: UserPreferences): void {
function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes: textChanges.ChangeTracker, host: LanguageServiceHost, preferences: UserPreferences, context: RefactorContext): void {
const checker = program.getTypeChecker();
const usage = getUsageInfo(oldFile, toMove.all, checker);
const currentDirectory = getDirectoryPath(oldFile.fileName);
const extension = extensionFromPath(oldFile.fileName);
const newFilename = combinePaths(
// new file is always placed in the same directory as the old file
currentDirectory,
// ensures the filename computed below isn't already taken
makeUniqueFilename(
// infers a name for the new file from the symbols being moved
inferNewFilename(usage.oldFileImportsFromNewFile, usage.movedSymbols),
extension,
currentDirectory,
host))
// new file has same extension as old file
+ extension;
const newFilename = createNewFileName(oldFile, program, context, host);
// If previous file was global, this is easy.
changes.createNewFile(oldFile, newFilename, getNewStatementsAndRemoveFromOldFile(oldFile, usage, changes, toMove, program, host, newFilename, preferences));
@@ -212,76 +94,19 @@ function doChange(oldFile: SourceFile, program: Program, toMove: ToMove, changes
addNewFileToTsconfig(program, changes, oldFile.fileName, newFilename, hostGetCanonicalFileName(host));
}
interface StatementRange {
readonly first: Statement;
readonly afterLast: Statement | undefined;
}
interface ToMove {
readonly all: readonly Statement[];
readonly ranges: readonly StatementRange[];
}
function getStatementsToMove(context: RefactorContext): ToMove | undefined {
const rangeToMove = getRangeToMove(context);
if (rangeToMove === undefined) return undefined;
const all: Statement[] = [];
const ranges: StatementRange[] = [];
const { toMove, afterLast } = rangeToMove;
getRangesWhere(toMove, isAllowedStatementToMove, (start, afterEndIndex) => {
for (let i = start; i < afterEndIndex; i++) all.push(toMove[i]);
ranges.push({ first: toMove[start], afterLast });
});
return all.length === 0 ? undefined : { all, ranges };
}
function isAllowedStatementToMove(statement: Statement): boolean {
// Filters imports and prologue directives out of the range of statements to move.
// Imports will be copied to the new file anyway, and may still be needed in the old file.
// Prologue directives will be copied to the new file and should be left in the old file.
return !isPureImport(statement) && !isPrologueDirective(statement);
}
function isPureImport(node: Node): boolean {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
return true;
case SyntaxKind.ImportEqualsDeclaration:
return !hasSyntacticModifier(node, ModifierFlags.Export);
case SyntaxKind.VariableStatement:
return (node as VariableStatement).declarationList.declarations.every(d => !!d.initializer && isRequireCall(d.initializer, /*requireStringLiteralLikeArgument*/ true));
default:
return false;
}
}
function addNewFileToTsconfig(program: Program, changes: textChanges.ChangeTracker, oldFileName: string, newFileNameWithExtension: string, getCanonicalFileName: GetCanonicalFileName): void {
const cfg = program.getCompilerOptions().configFile;
if (!cfg) return;
const newFileAbsolutePath = normalizePath(combinePaths(oldFileName, "..", newFileNameWithExtension));
const newFilePath = getRelativePathFromFile(cfg.fileName, newFileAbsolutePath, getCanonicalFileName);
const cfgObject = cfg.statements[0] && tryCast(cfg.statements[0].expression, isObjectLiteralExpression);
const filesProp = cfgObject && find(cfgObject.properties, (prop): prop is PropertyAssignment =>
isPropertyAssignment(prop) && isStringLiteral(prop.name) && prop.name.text === "files");
if (filesProp && isArrayLiteralExpression(filesProp.initializer)) {
changes.insertNodeInListAfter(cfg, last(filesProp.initializer.elements), factory.createStringLiteral(newFilePath), filesProp.initializer.elements);
}
}
function getNewStatementsAndRemoveFromOldFile(
oldFile: SourceFile, usage: UsageInfo, changes: textChanges.ChangeTracker, toMove: ToMove, program: Program, host: LanguageServiceHost, newFilename: string, preferences: UserPreferences,
) {
const checker = program.getTypeChecker();
const prologueDirectives = takeWhile(oldFile.statements, isPrologueDirective);
if (oldFile.externalModuleIndicator === undefined && oldFile.commonJsModuleIndicator === undefined && usage.oldImportsNeededByNewFile.size() === 0) {
if (oldFile.externalModuleIndicator === undefined && oldFile.commonJsModuleIndicator === undefined && usage.oldImportsNeededByTargetFile.size === 0) {
deleteMovedStatements(oldFile, toMove.ranges, changes);
return [...prologueDirectives, ...toMove.all];
}
const useEsModuleSyntax = !!oldFile.externalModuleIndicator;
const useEsModuleSyntax = !fileShouldUseJavaScriptRequire(newFilename, program, host, !!oldFile.commonJsModuleIndicator);
const quotePreference = getQuotePreference(oldFile, preferences);
const importsFromNewFile = createOldFileImportsFromNewFile(oldFile, usage.oldFileImportsFromNewFile, newFilename, program, host, useEsModuleSyntax, quotePreference);
const importsFromNewFile = createOldFileImportsFromTargetFile(oldFile, usage.oldFileImportsFromTargetFile, newFilename, program, host, useEsModuleSyntax, quotePreference);
if (importsFromNewFile) {
insertImports(changes, oldFile, importsFromNewFile, /*blankLineBetween*/ true, preferences);
}
@@ -290,8 +115,8 @@ function getNewStatementsAndRemoveFromOldFile(
deleteMovedStatements(oldFile, toMove.ranges, changes);
updateImportsInOtherFiles(changes, program, host, oldFile, usage.movedSymbols, newFilename, quotePreference);
const imports = getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByNewFile, usage.newFileImportsFromOldFile, changes, checker, program, host, useEsModuleSyntax, quotePreference);
const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromNewFile, useEsModuleSyntax);
const imports = getNewFileImportsAndAddExportInOldFile(oldFile, usage.oldImportsNeededByTargetFile, usage.targetFileImportsFromOldFile, changes, checker, program, host, useEsModuleSyntax, quotePreference);
const body = addExports(oldFile, toMove.all, usage.oldFileImportsFromTargetFile, useEsModuleSyntax);
if (imports.length && body.length) {
return [
...prologueDirectives,
@@ -308,295 +133,10 @@ function getNewStatementsAndRemoveFromOldFile(
];
}
function deleteMovedStatements(sourceFile: SourceFile, moved: readonly StatementRange[], changes: textChanges.ChangeTracker) {
for (const { first, afterLast } of moved) {
changes.deleteNodeRangeExcludingEnd(sourceFile, first, afterLast);
}
}
function deleteUnusedOldImports(oldFile: SourceFile, toMove: readonly Statement[], changes: textChanges.ChangeTracker, toDelete: ReadonlySymbolSet, checker: TypeChecker) {
for (const statement of oldFile.statements) {
if (contains(toMove, statement)) continue;
forEachImportInStatement(statement, i => deleteUnusedImports(oldFile, i, changes, name => toDelete.has(checker.getSymbolAtLocation(name)!)));
}
}
function updateImportsInOtherFiles(
changes: textChanges.ChangeTracker, program: Program, host: LanguageServiceHost, oldFile: SourceFile, movedSymbols: ReadonlySymbolSet, newFilename: string, quotePreference: QuotePreference
): void {
const checker = program.getTypeChecker();
for (const sourceFile of program.getSourceFiles()) {
if (sourceFile === oldFile) continue;
for (const statement of sourceFile.statements) {
forEachImportInStatement(statement, importNode => {
if (checker.getSymbolAtLocation(moduleSpecifierFromImport(importNode)) !== oldFile.symbol) return;
const shouldMove = (name: Identifier): boolean => {
const symbol = isBindingElement(name.parent)
? getPropertySymbolFromBindingElement(checker, name.parent as ObjectBindingElementWithoutPropertyName)
: skipAlias(checker.getSymbolAtLocation(name)!, checker); // TODO: GH#18217
return !!symbol && movedSymbols.has(symbol);
};
deleteUnusedImports(sourceFile, importNode, changes, shouldMove); // These will be changed to imports from the new file
const pathToNewFileWithExtension = resolvePath(getDirectoryPath(oldFile.path), newFilename);
const newModuleSpecifier = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, pathToNewFileWithExtension, createModuleSpecifierResolutionHost(program, host));
const newImportDeclaration = filterImport(importNode, makeStringLiteral(newModuleSpecifier, quotePreference), shouldMove);
if (newImportDeclaration) changes.insertNodeAfter(sourceFile, statement, newImportDeclaration);
const ns = getNamespaceLikeImport(importNode);
if (ns) updateNamespaceLikeImport(changes, sourceFile, checker, movedSymbols, newModuleSpecifier, ns, importNode, quotePreference);
});
}
}
}
function getNamespaceLikeImport(node: SupportedImport): Identifier | undefined {
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
return node.importClause && node.importClause.namedBindings && node.importClause.namedBindings.kind === SyntaxKind.NamespaceImport ?
node.importClause.namedBindings.name : undefined;
case SyntaxKind.ImportEqualsDeclaration:
return node.name;
case SyntaxKind.VariableDeclaration:
return tryCast(node.name, isIdentifier);
default:
return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`);
}
}
function updateNamespaceLikeImport(
changes: textChanges.ChangeTracker,
sourceFile: SourceFile,
checker: TypeChecker,
movedSymbols: ReadonlySymbolSet,
newModuleSpecifier: string,
oldImportId: Identifier,
oldImportNode: SupportedImport,
quotePreference: QuotePreference
): void {
const preferredNewNamespaceName = codefix.moduleSpecifierToValidIdentifier(newModuleSpecifier, ScriptTarget.ESNext);
let needUniqueName = false;
const toChange: Identifier[] = [];
FindAllReferences.Core.eachSymbolReferenceInFile(oldImportId, checker, sourceFile, ref => {
if (!isPropertyAccessExpression(ref.parent)) return;
needUniqueName = needUniqueName || !!checker.resolveName(preferredNewNamespaceName, ref, SymbolFlags.All, /*excludeGlobals*/ true);
if (movedSymbols.has(checker.getSymbolAtLocation(ref.parent.name)!)) {
toChange.push(ref);
}
});
if (toChange.length) {
const newNamespaceName = needUniqueName ? getUniqueName(preferredNewNamespaceName, sourceFile) : preferredNewNamespaceName;
for (const ref of toChange) {
changes.replaceNode(sourceFile, ref, factory.createIdentifier(newNamespaceName));
}
changes.insertNodeAfter(sourceFile, oldImportNode, updateNamespaceLikeImportNode(oldImportNode, preferredNewNamespaceName, newModuleSpecifier, quotePreference));
}
}
function updateNamespaceLikeImportNode(node: SupportedImport, newNamespaceName: string, newModuleSpecifier: string, quotePreference: QuotePreference): Node {
const newNamespaceId = factory.createIdentifier(newNamespaceName);
const newModuleString = makeStringLiteral(newModuleSpecifier, quotePreference);
switch (node.kind) {
case SyntaxKind.ImportDeclaration:
return factory.createImportDeclaration(
/*modifiers*/ undefined,
factory.createImportClause(/*isTypeOnly*/ false, /*name*/ undefined, factory.createNamespaceImport(newNamespaceId)),
newModuleString,
/*assertClause*/ undefined);
case SyntaxKind.ImportEqualsDeclaration:
return factory.createImportEqualsDeclaration(/*modifiers*/ undefined, /*isTypeOnly*/ false, newNamespaceId, factory.createExternalModuleReference(newModuleString));
case SyntaxKind.VariableDeclaration:
return factory.createVariableDeclaration(newNamespaceId, /*exclamationToken*/ undefined, /*type*/ undefined, createRequireCall(newModuleString));
default:
return Debug.assertNever(node, `Unexpected node kind ${(node as SupportedImport).kind}`);
}
}
function moduleSpecifierFromImport(i: SupportedImport): StringLiteralLike {
return (i.kind === SyntaxKind.ImportDeclaration ? i.moduleSpecifier
: i.kind === SyntaxKind.ImportEqualsDeclaration ? i.moduleReference.expression
: i.initializer.arguments[0]);
}
function forEachImportInStatement(statement: Statement, cb: (importNode: SupportedImport) => void): void {
if (isImportDeclaration(statement)) {
if (isStringLiteral(statement.moduleSpecifier)) cb(statement as SupportedImport);
}
else if (isImportEqualsDeclaration(statement)) {
if (isExternalModuleReference(statement.moduleReference) && isStringLiteralLike(statement.moduleReference.expression)) {
cb(statement as SupportedImport);
}
}
else if (isVariableStatement(statement)) {
for (const decl of statement.declarationList.declarations) {
if (decl.initializer && isRequireCall(decl.initializer, /*requireStringLiteralLikeArgument*/ true)) {
cb(decl as SupportedImport);
}
}
}
}
type SupportedImport =
| ImportDeclaration & { moduleSpecifier: StringLiteralLike }
| ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteralLike } }
| VariableDeclaration & { initializer: RequireOrImportCall };
type SupportedImportStatement =
| ImportDeclaration
| ImportEqualsDeclaration
| VariableStatement;
function createOldFileImportsFromNewFile(
sourceFile: SourceFile,
newFileNeedExport: ReadonlySymbolSet,
newFileNameWithExtension: string,
program: Program,
host: LanguageServiceHost,
useEs6Imports: boolean,
quotePreference: QuotePreference
): AnyImportOrRequireStatement | undefined {
let defaultImport: Identifier | undefined;
const imports: string[] = [];
newFileNeedExport.forEach(symbol => {
if (symbol.escapedName === InternalSymbolName.Default) {
defaultImport = factory.createIdentifier(symbolNameNoDefault(symbol)!); // TODO: GH#18217
}
else {
imports.push(symbol.name);
}
});
return makeImportOrRequire(sourceFile, defaultImport, imports, newFileNameWithExtension, program, host, useEs6Imports, quotePreference);
}
function makeImportOrRequire(
sourceFile: SourceFile,
defaultImport: Identifier | undefined,
imports: readonly string[],
newFileNameWithExtension: string,
program: Program,
host: LanguageServiceHost,
useEs6Imports: boolean,
quotePreference: QuotePreference
): AnyImportOrRequireStatement | undefined {
const pathToNewFile = resolvePath(getDirectoryPath(sourceFile.path), newFileNameWithExtension);
const pathToNewFileWithCorrectExtension = getModuleSpecifier(program.getCompilerOptions(), sourceFile, sourceFile.path, pathToNewFile, createModuleSpecifierResolutionHost(program, host));
if (useEs6Imports) {
const specifiers = imports.map(i => factory.createImportSpecifier(/*isTypeOnly*/ false, /*propertyName*/ undefined, factory.createIdentifier(i)));
return makeImportIfNecessary(defaultImport, specifiers, pathToNewFileWithCorrectExtension, quotePreference);
}
else {
Debug.assert(!defaultImport, "No default import should exist"); // If there's a default export, it should have been an es6 module.
const bindingElements = imports.map(i => factory.createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, i));
return bindingElements.length
? makeVariableStatement(factory.createObjectBindingPattern(bindingElements), /*type*/ undefined, createRequireCall(makeStringLiteral(pathToNewFileWithCorrectExtension, quotePreference))) as RequireVariableStatement
: undefined;
}
}
function makeVariableStatement(name: BindingName, type: TypeNode | undefined, initializer: Expression | undefined, flags: NodeFlags = NodeFlags.Const) {
return factory.createVariableStatement(/*modifiers*/ undefined, factory.createVariableDeclarationList([factory.createVariableDeclaration(name, /*exclamationToken*/ undefined, type, initializer)], flags));
}
function createRequireCall(moduleSpecifier: StringLiteralLike): CallExpression {
return factory.createCallExpression(factory.createIdentifier("require"), /*typeArguments*/ undefined, [moduleSpecifier]);
}
function addExports(sourceFile: SourceFile, toMove: readonly Statement[], needExport: ReadonlySymbolSet, useEs6Exports: boolean): readonly Statement[] {
return flatMap(toMove, statement => {
if (isTopLevelDeclarationStatement(statement) &&
!isExported(sourceFile, statement, useEs6Exports) &&
forEachTopLevelDeclaration(statement, d => needExport.has(Debug.checkDefined(tryCast(d, canHaveSymbol)?.symbol)))) {
const exports = addExport(statement, useEs6Exports);
if (exports) return exports;
}
return statement;
});
}
function deleteUnusedImports(sourceFile: SourceFile, importDecl: SupportedImport, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void {
switch (importDecl.kind) {
case SyntaxKind.ImportDeclaration:
deleteUnusedImportsInDeclaration(sourceFile, importDecl, changes, isUnused);
break;
case SyntaxKind.ImportEqualsDeclaration:
if (isUnused(importDecl.name)) {
changes.delete(sourceFile, importDecl);
}
break;
case SyntaxKind.VariableDeclaration:
deleteUnusedImportsInVariableDeclaration(sourceFile, importDecl, changes, isUnused);
break;
default:
Debug.assertNever(importDecl, `Unexpected import decl kind ${(importDecl as SupportedImport).kind}`);
}
}
function deleteUnusedImportsInDeclaration(sourceFile: SourceFile, importDecl: ImportDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean): void {
if (!importDecl.importClause) return;
const { name, namedBindings } = importDecl.importClause;
const defaultUnused = !name || isUnused(name);
const namedBindingsUnused = !namedBindings ||
(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);
}
else {
if (name && defaultUnused) {
changes.delete(sourceFile, name);
}
if (namedBindings) {
if (namedBindingsUnused) {
changes.replaceNode(
sourceFile,
importDecl.importClause,
factory.updateImportClause(importDecl.importClause, importDecl.importClause.isTypeOnly, name, /*namedBindings*/ undefined)
);
}
else if (namedBindings.kind === SyntaxKind.NamedImports) {
for (const element of namedBindings.elements) {
if (isUnused(element.name)) changes.delete(sourceFile, element);
}
}
}
}
}
function deleteUnusedImportsInVariableDeclaration(sourceFile: SourceFile, varDecl: VariableDeclaration, changes: textChanges.ChangeTracker, isUnused: (name: Identifier) => boolean) {
const { name } = varDecl;
switch (name.kind) {
case SyntaxKind.Identifier:
if (isUnused(name)) {
if (varDecl.initializer && isRequireCall(varDecl.initializer, /*requireStringLiteralLikeArgument*/ true)) {
changes.delete(sourceFile,
isVariableDeclarationList(varDecl.parent) && length(varDecl.parent.declarations) === 1 ? varDecl.parent.parent : varDecl);
}
else {
changes.delete(sourceFile, name);
}
}
break;
case SyntaxKind.ArrayBindingPattern:
break;
case SyntaxKind.ObjectBindingPattern:
if (name.elements.every(e => isIdentifier(e.name) && isUnused(e.name))) {
changes.delete(sourceFile,
isVariableDeclarationList(varDecl.parent) && varDecl.parent.declarations.length === 1 ? varDecl.parent.parent : varDecl);
}
else {
for (const element of name.elements) {
if (isIdentifier(element.name) && isUnused(element.name)) {
changes.delete(sourceFile, element.name);
}
}
}
break;
}
}
function getNewFileImportsAndAddExportInOldFile(
oldFile: SourceFile,
importsToCopy: ReadonlySymbolSet,
newFileImportsFromOldFile: ReadonlySymbolSet,
importsToCopy: Map<Symbol, boolean>,
newFileImportsFromOldFile: Set<Symbol>,
changes: textChanges.ChangeTracker,
checker: TypeChecker,
program: Program,
@@ -640,379 +180,3 @@ function getNewFileImportsAndAddExportInOldFile(
append(copiedOldImports, makeImportOrRequire(oldFile, oldFileDefault, oldFileNamedImports, getBaseFileName(oldFile.fileName), program, host, useEsModuleSyntax, quotePreference));
return copiedOldImports;
}
function makeUniqueFilename(proposedFilename: string, extension: string, inDirectory: string, host: LanguageServiceHost): string {
let newFilename = proposedFilename;
for (let i = 1; ; i++) {
const name = combinePaths(inDirectory, newFilename + extension);
if (!host.fileExists(name)) return newFilename;
newFilename = `${proposedFilename}.${i}`;
}
}
function inferNewFilename(importsFromNewFile: ReadonlySymbolSet, movedSymbols: ReadonlySymbolSet): string {
return importsFromNewFile.forEachEntry(symbolNameNoDefault) || movedSymbols.forEachEntry(symbolNameNoDefault) || "newFile";
}
interface UsageInfo {
// Symbols whose declarations are moved from the old file to the new file.
readonly movedSymbols: ReadonlySymbolSet;
// Symbols declared in the old file that must be imported by the new file. (May not already be exported.)
readonly newFileImportsFromOldFile: ReadonlySymbolSet;
// Subset of movedSymbols that are still used elsewhere in the old file and must be imported back.
readonly oldFileImportsFromNewFile: ReadonlySymbolSet;
readonly oldImportsNeededByNewFile: ReadonlySymbolSet;
// Subset of oldImportsNeededByNewFile that are will no longer be used in the old file.
readonly unusedImportsFromOldFile: ReadonlySymbolSet;
}
function getUsageInfo(oldFile: SourceFile, toMove: readonly Statement[], checker: TypeChecker): UsageInfo {
const movedSymbols = new SymbolSet();
const oldImportsNeededByNewFile = new SymbolSet();
const newFileImportsFromOldFile = new SymbolSet();
const containsJsx = find(toMove, statement => !!(statement.transformFlags & TransformFlags.ContainsJsx));
const jsxNamespaceSymbol = getJsxNamespaceSymbol(containsJsx);
if (jsxNamespaceSymbol) { // Might not exist (e.g. in non-compiling code)
oldImportsNeededByNewFile.add(jsxNamespaceSymbol);
}
for (const statement of toMove) {
forEachTopLevelDeclaration(statement, decl => {
movedSymbols.add(Debug.checkDefined(isExpressionStatement(decl) ? checker.getSymbolAtLocation(decl.expression.left) : decl.symbol, "Need a symbol here"));
});
}
for (const statement of toMove) {
forEachReference(statement, checker, symbol => {
if (!symbol.declarations) return;
for (const decl of symbol.declarations) {
if (isInImport(decl)) {
oldImportsNeededByNewFile.add(symbol);
}
else if (isTopLevelDeclaration(decl) && sourceFileOfTopLevelDeclaration(decl) === oldFile && !movedSymbols.has(symbol)) {
newFileImportsFromOldFile.add(symbol);
}
}
});
}
const unusedImportsFromOldFile = oldImportsNeededByNewFile.clone();
const oldFileImportsFromNewFile = new SymbolSet();
for (const statement of oldFile.statements) {
if (contains(toMove, statement)) continue;
// jsxNamespaceSymbol will only be set iff it is in oldImportsNeededByNewFile.
if (jsxNamespaceSymbol && !!(statement.transformFlags & TransformFlags.ContainsJsx)) {
unusedImportsFromOldFile.delete(jsxNamespaceSymbol);
}
forEachReference(statement, checker, symbol => {
if (movedSymbols.has(symbol)) oldFileImportsFromNewFile.add(symbol);
unusedImportsFromOldFile.delete(symbol);
});
}
return { movedSymbols, newFileImportsFromOldFile, oldFileImportsFromNewFile, oldImportsNeededByNewFile, unusedImportsFromOldFile };
function getJsxNamespaceSymbol(containsJsx: Node | undefined) {
if (containsJsx === undefined) {
return undefined;
}
const jsxNamespace = checker.getJsxNamespace(containsJsx);
// Strictly speaking, this could resolve to a symbol other than the JSX namespace.
// This will produce erroneous output (probably, an incorrectly copied import) but
// is expected to be very rare and easily reversible.
const jsxNamespaceSymbol = checker.resolveName(jsxNamespace, containsJsx, SymbolFlags.Namespace, /*excludeGlobals*/ true);
return !!jsxNamespaceSymbol && some(jsxNamespaceSymbol.declarations, isInImport)
? jsxNamespaceSymbol
: undefined;
}
}
// Below should all be utilities
function isInImport(decl: Declaration) {
switch (decl.kind) {
case SyntaxKind.ImportEqualsDeclaration:
case SyntaxKind.ImportSpecifier:
case SyntaxKind.ImportClause:
case SyntaxKind.NamespaceImport:
return true;
case SyntaxKind.VariableDeclaration:
return isVariableDeclarationInImport(decl as VariableDeclaration);
case SyntaxKind.BindingElement:
return isVariableDeclaration(decl.parent.parent) && isVariableDeclarationInImport(decl.parent.parent);
default:
return false;
}
}
function isVariableDeclarationInImport(decl: VariableDeclaration) {
return isSourceFile(decl.parent.parent.parent) &&
!!decl.initializer && isRequireCall(decl.initializer, /*requireStringLiteralLikeArgument*/ true);
}
function filterImport(i: SupportedImport, moduleSpecifier: StringLiteralLike, keep: (name: Identifier) => boolean): SupportedImportStatement | undefined {
switch (i.kind) {
case SyntaxKind.ImportDeclaration: {
const clause = i.importClause;
if (!clause) return undefined;
const defaultImport = clause.name && keep(clause.name) ? clause.name : undefined;
const namedBindings = clause.namedBindings && filterNamedBindings(clause.namedBindings, keep);
return defaultImport || namedBindings
? factory.createImportDeclaration(/*modifiers*/ undefined, factory.createImportClause(clause.isTypeOnly, defaultImport, namedBindings), moduleSpecifier, /*assertClause*/ undefined)
: undefined;
}
case SyntaxKind.ImportEqualsDeclaration:
return keep(i.name) ? i : undefined;
case SyntaxKind.VariableDeclaration: {
const name = filterBindingName(i.name, keep);
return name ? makeVariableStatement(name, i.type, createRequireCall(moduleSpecifier), i.parent.flags) : undefined;
}
default:
return Debug.assertNever(i, `Unexpected import kind ${(i as SupportedImport).kind}`);
}
}
function filterNamedBindings(namedBindings: NamedImportBindings, keep: (name: Identifier) => boolean): NamedImportBindings | undefined {
if (namedBindings.kind === SyntaxKind.NamespaceImport) {
return keep(namedBindings.name) ? namedBindings : undefined;
}
else {
const newElements = namedBindings.elements.filter(e => keep(e.name));
return newElements.length ? factory.createNamedImports(newElements) : undefined;
}
}
function filterBindingName(name: BindingName, keep: (name: Identifier) => boolean): BindingName | undefined {
switch (name.kind) {
case SyntaxKind.Identifier:
return keep(name) ? name : undefined;
case SyntaxKind.ArrayBindingPattern:
return name;
case SyntaxKind.ObjectBindingPattern: {
// We can't handle nested destructurings or property names well here, so just copy them all.
const newElements = name.elements.filter(prop => prop.propertyName || !isIdentifier(prop.name) || keep(prop.name));
return newElements.length ? factory.createObjectBindingPattern(newElements) : undefined;
}
}
}
function forEachReference(node: Node, checker: TypeChecker, onReference: (s: Symbol) => void) {
node.forEachChild(function cb(node) {
if (isIdentifier(node) && !isDeclarationName(node)) {
const sym = checker.getSymbolAtLocation(node);
if (sym) onReference(sym);
}
else {
node.forEachChild(cb);
}
});
}
interface ReadonlySymbolSet {
size(): number;
has(symbol: Symbol): boolean;
forEach(cb: (symbol: Symbol) => void): void;
forEachEntry<T>(cb: (symbol: Symbol) => T | undefined): T | undefined;
}
class SymbolSet implements ReadonlySymbolSet {
private map = new Map<string, Symbol>();
add(symbol: Symbol): void {
this.map.set(String(getSymbolId(symbol)), symbol);
}
has(symbol: Symbol): boolean {
return this.map.has(String(getSymbolId(symbol)));
}
delete(symbol: Symbol): void {
this.map.delete(String(getSymbolId(symbol)));
}
forEach(cb: (symbol: Symbol) => void): void {
this.map.forEach(cb);
}
forEachEntry<T>(cb: (symbol: Symbol) => T | undefined): T | undefined {
return forEachEntry(this.map, cb);
}
clone(): SymbolSet {
const clone = new SymbolSet();
copyEntries(this.map, clone.map);
return clone;
}
size() {
return this.map.size;
}
}
type TopLevelExpressionStatement = ExpressionStatement & { expression: BinaryExpression & { left: PropertyAccessExpression } }; // 'exports.x = ...'
type NonVariableTopLevelDeclaration =
| FunctionDeclaration
| ClassDeclaration
| EnumDeclaration
| TypeAliasDeclaration
| InterfaceDeclaration
| ModuleDeclaration
| TopLevelExpressionStatement
| ImportEqualsDeclaration;
type TopLevelDeclarationStatement = NonVariableTopLevelDeclaration | VariableStatement;
interface TopLevelVariableDeclaration extends VariableDeclaration { parent: VariableDeclarationList & { parent: VariableStatement; }; }
type TopLevelDeclaration = NonVariableTopLevelDeclaration | TopLevelVariableDeclaration | BindingElement;
function isTopLevelDeclaration(node: Node): node is TopLevelDeclaration {
return isNonVariableTopLevelDeclaration(node) && isSourceFile(node.parent) || isVariableDeclaration(node) && isSourceFile(node.parent.parent.parent);
}
function sourceFileOfTopLevelDeclaration(node: TopLevelDeclaration): Node {
return isVariableDeclaration(node) ? node.parent.parent.parent : node.parent;
}
function isTopLevelDeclarationStatement(node: Node): node is TopLevelDeclarationStatement {
Debug.assert(isSourceFile(node.parent), "Node parent should be a SourceFile");
return isNonVariableTopLevelDeclaration(node) || isVariableStatement(node);
}
function isNonVariableTopLevelDeclaration(node: Node): node is NonVariableTopLevelDeclaration {
switch (node.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.ImportEqualsDeclaration:
return true;
default:
return false;
}
}
function forEachTopLevelDeclaration<T>(statement: Statement, cb: (node: TopLevelDeclaration) => T): T | undefined {
switch (statement.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.ImportEqualsDeclaration:
return cb(statement as FunctionDeclaration | ClassDeclaration | EnumDeclaration | ModuleDeclaration | TypeAliasDeclaration | InterfaceDeclaration | ImportEqualsDeclaration);
case SyntaxKind.VariableStatement:
return firstDefined((statement as VariableStatement).declarationList.declarations, decl => forEachTopLevelDeclarationInBindingName(decl.name, cb));
case SyntaxKind.ExpressionStatement: {
const { expression } = statement as ExpressionStatement;
return isBinaryExpression(expression) && getAssignmentDeclarationKind(expression) === AssignmentDeclarationKind.ExportsProperty
? cb(statement as TopLevelExpressionStatement)
: undefined;
}
}
}
function forEachTopLevelDeclarationInBindingName<T>(name: BindingName, cb: (node: TopLevelDeclaration) => T): T | undefined {
switch (name.kind) {
case SyntaxKind.Identifier:
return cb(cast(name.parent, (x): x is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(x) || isBindingElement(x)));
case SyntaxKind.ArrayBindingPattern:
case SyntaxKind.ObjectBindingPattern:
return firstDefined(name.elements, em => isOmittedExpression(em) ? undefined : forEachTopLevelDeclarationInBindingName(em.name, cb));
default:
return Debug.assertNever(name, `Unexpected name kind ${(name as BindingName).kind}`);
}
}
function nameOfTopLevelDeclaration(d: TopLevelDeclaration): Identifier | undefined {
return isExpressionStatement(d) ? tryCast(d.expression.left.name, isIdentifier) : tryCast(d.name, isIdentifier);
}
function getTopLevelDeclarationStatement(d: TopLevelDeclaration): TopLevelDeclarationStatement {
switch (d.kind) {
case SyntaxKind.VariableDeclaration:
return d.parent.parent;
case SyntaxKind.BindingElement:
return getTopLevelDeclarationStatement(
cast(d.parent.parent, (p): p is TopLevelVariableDeclaration | BindingElement => isVariableDeclaration(p) || isBindingElement(p)));
default:
return d;
}
}
function addExportToChanges(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, name: Identifier, changes: textChanges.ChangeTracker, useEs6Exports: boolean): void {
if (isExported(sourceFile, decl, useEs6Exports, name)) return;
if (useEs6Exports) {
if (!isExpressionStatement(decl)) changes.insertExportModifier(sourceFile, decl);
}
else {
const names = getNamesToExportInCommonJS(decl);
if (names.length !== 0) changes.insertNodesAfter(sourceFile, decl, names.map(createExportAssignment));
}
}
function isExported(sourceFile: SourceFile, decl: TopLevelDeclarationStatement, useEs6Exports: boolean, name?: Identifier): boolean {
if (useEs6Exports) {
return !isExpressionStatement(decl) && hasSyntacticModifier(decl, ModifierFlags.Export) || !!(name && sourceFile.symbol.exports?.has(name.escapedText));
}
return !!sourceFile.symbol && !!sourceFile.symbol.exports &&
getNamesToExportInCommonJS(decl).some(name => sourceFile.symbol.exports!.has(escapeLeadingUnderscores(name)));
}
function addExport(decl: TopLevelDeclarationStatement, useEs6Exports: boolean): readonly Statement[] | undefined {
return useEs6Exports ? [addEs6Export(decl)] : addCommonjsExport(decl);
}
function addEs6Export(d: TopLevelDeclarationStatement): TopLevelDeclarationStatement {
const modifiers = canHaveModifiers(d) ? concatenate([factory.createModifier(SyntaxKind.ExportKeyword)], getModifiers(d)) : undefined;
switch (d.kind) {
case SyntaxKind.FunctionDeclaration:
return factory.updateFunctionDeclaration(d, modifiers, d.asteriskToken, d.name, d.typeParameters, d.parameters, d.type, d.body);
case SyntaxKind.ClassDeclaration:
const decorators = canHaveDecorators(d) ? getDecorators(d) : undefined;
return factory.updateClassDeclaration(d, concatenate<ModifierLike>(decorators, modifiers), d.name, d.typeParameters, d.heritageClauses, d.members);
case SyntaxKind.VariableStatement:
return factory.updateVariableStatement(d, modifiers, d.declarationList);
case SyntaxKind.ModuleDeclaration:
return factory.updateModuleDeclaration(d, modifiers, d.name, d.body);
case SyntaxKind.EnumDeclaration:
return factory.updateEnumDeclaration(d, modifiers, d.name, d.members);
case SyntaxKind.TypeAliasDeclaration:
return factory.updateTypeAliasDeclaration(d, modifiers, d.name, d.typeParameters, d.type);
case SyntaxKind.InterfaceDeclaration:
return factory.updateInterfaceDeclaration(d, modifiers, d.name, d.typeParameters, d.heritageClauses, d.members);
case SyntaxKind.ImportEqualsDeclaration:
return factory.updateImportEqualsDeclaration(d, modifiers, d.isTypeOnly, d.name, d.moduleReference);
case SyntaxKind.ExpressionStatement:
return Debug.fail(); // Shouldn't try to add 'export' keyword to `exports.x = ...`
default:
return Debug.assertNever(d, `Unexpected declaration kind ${(d as DeclarationStatement).kind}`);
}
}
function addCommonjsExport(decl: TopLevelDeclarationStatement): readonly Statement[] | undefined {
return [decl, ...getNamesToExportInCommonJS(decl).map(createExportAssignment)];
}
function getNamesToExportInCommonJS(decl: TopLevelDeclarationStatement): readonly string[] {
switch (decl.kind) {
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.ClassDeclaration:
return [decl.name!.text]; // TODO: GH#18217
case SyntaxKind.VariableStatement:
return mapDefined(decl.declarationList.declarations, d => isIdentifier(d.name) ? d.name.text : undefined);
case SyntaxKind.ModuleDeclaration:
case SyntaxKind.EnumDeclaration:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.ImportEqualsDeclaration:
return emptyArray;
case SyntaxKind.ExpressionStatement:
return Debug.fail("Can't export an ExpressionStatement"); // Shouldn't try to add 'export' keyword to `exports.x = ...`
default:
return Debug.assertNever(decl, `Unexpected decl kind ${(decl as TopLevelDeclarationStatement).kind}`);
}
}
/** Creates `exports.x = x;` */
function createExportAssignment(name: string): Statement {
return factory.createExpressionStatement(
factory.createBinaryExpression(
factory.createPropertyAccessExpression(factory.createIdentifier("exports"), factory.createIdentifier(name)),
SyntaxKind.EqualsToken,
factory.createIdentifier(name)));
}
+41 -7
View File
@@ -65,6 +65,8 @@ import {
EntityName,
equateValues,
ExportDeclaration,
Extension,
extensionFromPath,
FileReference,
FileTextChanges,
filter,
@@ -86,6 +88,7 @@ import {
getAdjustedRenameLocation,
getAllSuperTypeNodes,
getAssignmentDeclarationKind,
getBaseFileName,
GetCompletionsAtPositionOptions,
getContainerNode,
getDefaultLibFileName,
@@ -105,6 +108,7 @@ import {
getNonAssignedNameOfDeclaration,
getNormalizedAbsolutePath,
getObjectFlags,
getQuotePreference,
getScriptKind,
getSetExternalModuleIndicator,
getSnapshotText,
@@ -134,6 +138,7 @@ import {
InlayHints,
InlayHintsContext,
insertSorted,
InteractiveRefactorArguments,
InterfaceType,
IntersectionType,
isArray,
@@ -276,6 +281,7 @@ import {
SourceFile,
SourceFileLike,
SourceMapSource,
startsWith,
Statement,
stringContains,
StringLiteral,
@@ -319,6 +325,7 @@ import {
} from "./_namespaces/ts";
import * as NavigateTo from "./_namespaces/ts.NavigateTo";
import * as NavigationBar from "./_namespaces/ts.NavigationBar";
import { createNewFileName } from "./_namespaces/ts.refactor";
import * as classifier from "./classifier";
import * as classifier2020 from "./classifier2020";
@@ -1634,6 +1641,7 @@ export function createLanguageService(
// Get a fresh cache of the host information
const newSettings = host.getCompilationSettings() || getDefaultCompilerOptions();
const hasInvalidatedResolutions: HasInvalidatedResolutions = host.hasInvalidatedResolutions || returnFalse;
const hasInvalidatedLibResolutions = maybeBind(host, host.hasInvalidatedLibResolutions) || returnFalse;
const hasChangedAutomaticTypeDirectiveNames = maybeBind(host, host.hasChangedAutomaticTypeDirectiveNames);
const projectReferences = host.getProjectReferences?.();
let parsedCommandLines: Map<Path, ParsedCommandLine | false> | undefined;
@@ -1666,6 +1674,7 @@ export function createLanguageService(
onReleaseOldSourceFile,
onReleaseParsedCommandLine,
hasInvalidatedResolutions,
hasInvalidatedLibResolutions,
hasChangedAutomaticTypeDirectiveNames,
trace: maybeBind(host, host.trace),
resolveModuleNames: maybeBind(host, host.resolveModuleNames),
@@ -1674,6 +1683,7 @@ export function createLanguageService(
resolveTypeReferenceDirectives: maybeBind(host, host.resolveTypeReferenceDirectives),
resolveModuleNameLiterals: maybeBind(host, host.resolveModuleNameLiterals),
resolveTypeReferenceDirectiveReferences: maybeBind(host, host.resolveTypeReferenceDirectiveReferences),
resolveLibrary: maybeBind(host, host.resolveLibrary),
useSourceOfProjectReferenceRedirect: maybeBind(host, host.useSourceOfProjectReferenceRedirect),
getParsedCommandLine,
};
@@ -1705,7 +1715,7 @@ export function createLanguageService(
const documentRegistryBucketKey = documentRegistry.getKeyForCompilationSettings(newSettings);
// If the program is already up-to-date, we can reuse it
if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileName => compilerHost!.fileExists(fileName), hasInvalidatedResolutions, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) {
if (isProgramUptoDate(program, rootFileNames, newSettings, (_path, fileName) => host.getScriptVersion(fileName), fileName => compilerHost!.fileExists(fileName), hasInvalidatedResolutions, hasInvalidatedLibResolutions, hasChangedAutomaticTypeDirectiveNames, getParsedCommandLine, projectReferences)) {
return;
}
@@ -2125,7 +2135,7 @@ export function createLanguageService(
return DocumentHighlights.getDocumentHighlights(program, cancellationToken, sourceFile, position, sourceFilesToSearch);
}
function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): RenameLocation[] | undefined {
function findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, preferences?: UserPreferences | boolean): RenameLocation[] | undefined {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const node = getAdjustedRenameLocation(getTouchingPropertyName(sourceFile, position));
@@ -2142,8 +2152,10 @@ export function createLanguageService(
});
}
else {
const quotePreference = getQuotePreference(sourceFile, preferences ?? emptyOptions);
const providePrefixAndSuffixTextForRename = typeof preferences === "boolean" ? preferences : preferences?.providePrefixAndSuffixTextForRename;
return getReferencesWorker(node, position, { findInStrings, findInComments, providePrefixAndSuffixTextForRename, use: FindAllReferences.FindReferencesUse.Rename },
(entry, originalNode, checker) => FindAllReferences.toRenameLocation(entry, originalNode, checker, providePrefixAndSuffixTextForRename || false));
(entry, originalNode, checker) => FindAllReferences.toRenameLocation(entry, originalNode, checker, providePrefixAndSuffixTextForRename || false, quotePreference));
}
}
@@ -2492,6 +2504,9 @@ export function createLanguageService(
const token = findPrecedingToken(position, sourceFile);
if (!token || token.parent.kind === SyntaxKind.SourceFile) return undefined;
// matches more than valid tag names to allow linked editing when typing is in progress or tag name is incomplete
const jsxTagWordPattern = "[a-zA-Z0-9:\\-\\._$]*";
if (isJsxFragment(token.parent.parent)) {
const openFragment = token.parent.parent.openingFragment;
const closeFragment = token.parent.parent.closingFragment;
@@ -2503,7 +2518,10 @@ export function createLanguageService(
// only allows linked editing right after opening bracket: <| ></| >
if ((position !== openPos) && (position !== closePos)) return undefined;
return { ranges: [{ start: openPos, length: 0 }, { start: closePos, length: 0 }] };
return {
ranges: [{ start: openPos, length: 0 }, { start: closePos, length: 0 }],
wordPattern: jsxTagWordPattern,
};
}
else {
// determines if the cursor is in an element tag
@@ -2534,6 +2552,7 @@ export function createLanguageService(
return {
ranges: [{ start: openTagStart, length: openTagEnd - openTagStart }, { start: closeTagStart, length: closeTagEnd - closeTagStart }],
wordPattern: jsxTagWordPattern,
};
}
}
@@ -2971,10 +2990,23 @@ export function createLanguageService(
return SmartSelectionRange.getSmartSelectionRange(position, syntaxTreeCache.getCurrentSourceFile(fileName));
}
function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason, kind: string): ApplicableRefactorInfo[] {
function getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions, triggerReason: RefactorTriggerReason, kind: string, includeInteractiveActions?: boolean): ApplicableRefactorInfo[] {
synchronizeHostData();
const file = getValidSourceFile(fileName);
return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind));
return refactor.getApplicableRefactors(getRefactorContext(file, positionOrRange, preferences, emptyOptions, triggerReason, kind), includeInteractiveActions);
}
function getMoveToRefactoringFileSuggestions(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences = emptyOptions): { newFileName: string, files: string[] } {
synchronizeHostData();
const sourceFile = getValidSourceFile(fileName);
const allFiles = Debug.checkDefined(program.getSourceFiles());
const extension = extensionFromPath(fileName);
const files = mapDefined(allFiles, file => !program?.isSourceFileFromExternalLibrary(sourceFile) &&
!(sourceFile === getValidSourceFile(file.fileName) || extension === Extension.Ts && extensionFromPath(file.fileName) === Extension.Dts || extension === Extension.Dts && startsWith(getBaseFileName(file.fileName), "lib.") && extensionFromPath(file.fileName) === Extension.Dts)
&& extension === extensionFromPath(file.fileName) ? file.fileName : undefined);
const newFileName = createNewFileName(sourceFile, program, getRefactorContext(sourceFile, positionOrRange, preferences, emptyOptions), host);
return { newFileName, files };
}
function getEditsForRefactor(
@@ -2984,10 +3016,11 @@ export function createLanguageService(
refactorName: string,
actionName: string,
preferences: UserPreferences = emptyOptions,
interactiveRefactorArguments?: InteractiveRefactorArguments,
): RefactorEditInfo | undefined {
synchronizeHostData();
const file = getValidSourceFile(fileName);
return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName);
return refactor.getEditsForRefactor(getRefactorContext(file, positionOrRange, preferences, formatOptions), refactorName, actionName, interactiveRefactorArguments);
}
function toLineColumnOffset(fileName: string, position: number): LineAndCharacter {
@@ -3084,6 +3117,7 @@ export function createLanguageService(
updateIsDefinitionOfReferencedSymbols,
getApplicableRefactors,
getEditsForRefactor,
getMoveToRefactoringFileSuggestions,
toLineColumnOffset,
getSourceMapper: () => sourceMapper,
clearSourceMapperCache: () => sourceMapper.clearCache(),
+4 -4
View File
@@ -256,7 +256,7 @@ export interface LanguageServiceShim extends Shim {
* Returns a JSON-encoded value of the type:
* { fileName: string, textSpan: { start: number, length: number } }[]
*/
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string;
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, preferences?: UserPreferences | boolean): string;
/**
* Returns a JSON-encoded value of the type:
@@ -952,10 +952,10 @@ class LanguageServiceShimObject extends ShimBase implements LanguageServiceShim
);
}
public findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): string {
public findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, preferences: UserPreferences): string {
return this.forwardJSONCall(
`findRenameLocations('${fileName}', ${position}, ${findInStrings}, ${findInComments}, ${providePrefixAndSuffixTextForRename})`,
() => this.languageService.findRenameLocations(fileName, position, findInStrings, findInComments, providePrefixAndSuffixTextForRename)
`findRenameLocations('${fileName}', ${position}, ${findInStrings}, ${findInComments})`,
() => this.languageService.findRenameLocations(fileName, position, findInStrings, findInComments, preferences)
);
}
+2 -1
View File
@@ -55,6 +55,7 @@ import {
isTemplateSpan,
isTemplateTail,
isTransientSymbol,
JsxTagNameExpression,
last,
lastOrUndefined,
ListFormat,
@@ -599,7 +600,7 @@ function getChildListThatStartsWithOpenerToken(parent: Node, openerToken: Node,
return children[indexOfOpenerToken + 1];
}
function getExpressionFromInvocation(invocation: CallInvocation | TypeArgsInvocation): Expression {
function getExpressionFromInvocation(invocation: CallInvocation | TypeArgsInvocation): Expression | JsxTagNameExpression {
return invocation.kind === InvocationKind.Call ? getInvokedExpression(invocation.node) : invocation.called;
}
+3 -4
View File
@@ -452,7 +452,7 @@ export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: Typ
displayParts.push(spacePart());
displayParts.push(operatorPart(SyntaxKind.EqualsToken));
displayParts.push(spacePart());
addRange(displayParts, typeToDisplayParts(typeChecker, isConstTypeReference(location.parent) ? typeChecker.getTypeAtLocation(location.parent) : typeChecker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, TypeFormatFlags.InTypeAlias));
addRange(displayParts, typeToDisplayParts(typeChecker, location.parent && isConstTypeReference(location.parent) ? typeChecker.getTypeAtLocation(location.parent) : typeChecker.getDeclaredTypeOfSymbol(symbol), enclosingDeclaration, TypeFormatFlags.InTypeAlias));
}
if (symbolFlags & SymbolFlags.Enum) {
prefixNextMeaning();
@@ -535,12 +535,12 @@ export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: Typ
// don't use symbolFlags since getAliasedSymbol requires the flag on the symbol itself
if (symbol.flags & SymbolFlags.Alias) {
prefixNextMeaning();
if (!hasAddedSymbolInfo) {
if (!hasAddedSymbolInfo || documentation.length === 0 && tags.length === 0) {
const resolvedSymbol = typeChecker.getAliasedSymbol(symbol);
if (resolvedSymbol !== symbol && resolvedSymbol.declarations && resolvedSymbol.declarations.length > 0) {
const resolvedNode = resolvedSymbol.declarations[0];
const declarationName = getNameOfDeclaration(resolvedNode);
if (declarationName) {
if (declarationName && !hasAddedSymbolInfo) {
const isExternalModuleDeclaration =
isModuleWithStringLiteralName(resolvedNode) &&
hasSyntacticModifier(resolvedNode, ModifierFlags.Ambient);
@@ -835,7 +835,6 @@ export function getSymbolDisplayPartsDocumentationAndSymbolKind(typeChecker: Typ
documentation = allSignatures[0].getDocumentationComment(typeChecker);
tags = allSignatures[0].getJsDocTags().filter(tag => tag.name !== "deprecated"); // should only include @deprecated JSDoc tag on the first overload (#49368)
}
}
function writeTypeParametersOfSymbol(symbol: Symbol, enclosingDeclaration: Node | undefined) {
+39 -24
View File
@@ -10,6 +10,7 @@ import {
concatenate,
ConstructorDeclaration,
contains,
createMultiMap,
createNodeFactory,
createPrinter,
createRange,
@@ -51,9 +52,11 @@ import {
getNewLineKind,
getNewLineOrDefaultFromHost,
getNodeId,
getOriginalNode,
getPrecedingNonSpaceCharacterPosition,
getScriptKindFromFileName,
getShebang,
getSourceFileOfNode,
getStartPositionOfLine,
getTokenAtPosition,
getTouchingToken,
@@ -116,6 +119,7 @@ import {
mapDefined,
MethodSignature,
Modifier,
MultiMap,
NamedImportBindings,
NamedImports,
NamespaceImport,
@@ -337,6 +341,11 @@ interface ChangeText extends BaseChange {
readonly text: string;
}
interface NewFileInsertion {
readonly oldFile?: SourceFile;
readonly statements: readonly (Statement | SyntaxKind.NewLineTrivia)[];
}
function getAdjustedRange(sourceFile: SourceFile, startNode: Node, endNode: Node, options: ConfigurableStartEnd): TextRange {
return { pos: getAdjustedStartPosition(sourceFile, startNode, options), end: getAdjustedEndPosition(sourceFile, endNode, options) };
}
@@ -480,7 +489,7 @@ export function isThisTypeAnnotatable(containingFunction: SignatureDeclaration):
/** @internal */
export class ChangeTracker {
private readonly changes: Change[] = [];
private readonly newFiles: { readonly oldFile: SourceFile | undefined, readonly fileName: string, readonly statements: readonly (Statement | SyntaxKind.NewLineTrivia)[] }[] = [];
private newFileChanges?: MultiMap<string, NewFileInsertion> ;
private readonly classesWithNodesInsertedAtStart = new Map<number, { readonly node: ClassLikeDeclaration | InterfaceDeclaration | ObjectLiteralExpression, readonly sourceFile: SourceFile }>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration>
private readonly deletedNodes: { readonly sourceFile: SourceFile, readonly node: Node | NodeArray<TypeParameterDeclaration> }[] = [];
@@ -622,6 +631,13 @@ export class ChangeTracker {
}
}
private insertStatementsInNewFile(fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], oldFile?: SourceFile): void {
if (!this.newFileChanges) {
this.newFileChanges = createMultiMap<string, NewFileInsertion>();
}
this.newFileChanges.add(fileName, { oldFile, statements });
}
public insertFirstParameter(sourceFile: SourceFile, parameters: NodeArray<ParameterDeclaration>, newParam: ParameterDeclaration): void {
const p0 = firstOrUndefined(parameters);
if (p0) {
@@ -1128,14 +1144,16 @@ export class ChangeTracker {
this.finishDeleteDeclarations();
this.finishClassesWithNodesInsertedAtStart();
const changes = changesToText.getTextChangesFromChanges(this.changes, this.newLineCharacter, this.formatContext, validate);
for (const { oldFile, fileName, statements } of this.newFiles) {
changes.push(changesToText.newFileChanges(oldFile, fileName, statements, this.newLineCharacter, this.formatContext));
if (this.newFileChanges) {
this.newFileChanges.forEach((insertions, fileName) => {
changes.push(changesToText.newFileChanges(fileName, insertions, this.newLineCharacter, this.formatContext));
});
}
return changes;
}
public createNewFile(oldFile: SourceFile | undefined, fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[]): void {
this.newFiles.push({ oldFile, fileName, statements });
this.insertStatementsInNewFile(fileName, statements, oldFile);
}
}
@@ -1207,11 +1225,6 @@ function getMembersOrProperties(node: ClassLikeDeclaration | InterfaceDeclaratio
/** @internal */
export type ValidateNonFormattedText = (node: Node, text: string) => void;
/** @internal */
export function getNewFileText(statements: readonly Statement[], scriptKind: ScriptKind, newLineCharacter: string, formatContext: formatting.FormatContext): string {
return changesToText.newFileChangesWorker(/*oldFile*/ undefined, scriptKind, statements, newLineCharacter, formatContext);
}
namespace changesToText {
export function getTextChangesFromChanges(changes: readonly Change[], newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): FileTextChanges[] {
return mapDefined(group(changes, c => c.sourceFile.path), changesInFile => {
@@ -1227,10 +1240,12 @@ namespace changesToText {
const textChanges = mapDefined(normalized, c => {
const span = createTextSpanFromRange(c.range);
const newText = computeNewText(c, sourceFile, newLineCharacter, formatContext, validate);
const targetSourceFile = c.kind === ChangeKind.ReplaceWithSingleNode ? getSourceFileOfNode(getOriginalNode(c.node)) ?? c.sourceFile :
c.kind === ChangeKind.ReplaceWithMultipleNodes ? getSourceFileOfNode(getOriginalNode(c.nodes[0])) ?? c.sourceFile :
c.sourceFile;
const newText = computeNewText(c, targetSourceFile, sourceFile, newLineCharacter, formatContext, validate);
// Filter out redundant changes.
if (span.length === newText.length && stringContainsAt(sourceFile.text, newText, span.start)) {
if (span.length === newText.length && stringContainsAt(targetSourceFile.text, newText, span.start)) {
return undefined;
}
@@ -1241,20 +1256,20 @@ namespace changesToText {
});
}
export function newFileChanges(oldFile: SourceFile | undefined, fileName: string, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], newLineCharacter: string, formatContext: formatting.FormatContext): FileTextChanges {
const text = newFileChangesWorker(oldFile, getScriptKindFromFileName(fileName), statements, newLineCharacter, formatContext);
export function newFileChanges(fileName: string, insertions: readonly NewFileInsertion[], newLineCharacter: string, formatContext: formatting.FormatContext): FileTextChanges {
const text = newFileChangesWorker(getScriptKindFromFileName(fileName), insertions, newLineCharacter, formatContext);
return { fileName, textChanges: [createTextChange(createTextSpan(0, 0), text)], isNewFile: true };
}
export function newFileChangesWorker(oldFile: SourceFile | undefined, scriptKind: ScriptKind, statements: readonly (Statement | SyntaxKind.NewLineTrivia)[], newLineCharacter: string, formatContext: formatting.FormatContext): string {
export function newFileChangesWorker(scriptKind: ScriptKind, insertions: readonly NewFileInsertion[], newLineCharacter: string, formatContext: formatting.FormatContext): string {
// TODO: this emits the file, parses it back, then formats it that -- may be a less roundabout way to do this
const nonFormattedText = statements.map(s => s === SyntaxKind.NewLineTrivia ? "" : getNonformattedText(s, oldFile, newLineCharacter).text).join(newLineCharacter);
const nonFormattedText = flatMap(insertions, insertion => insertion.statements.map(s => s === SyntaxKind.NewLineTrivia ? "" : getNonformattedText(s, insertion.oldFile, newLineCharacter).text)).join(newLineCharacter);
const sourceFile = createSourceFile("any file name", nonFormattedText, ScriptTarget.ESNext, /*setParentNodes*/ true, scriptKind);
const changes = formatting.formatDocument(sourceFile, formatContext);
return applyChanges(nonFormattedText, changes) + newLineCharacter;
}
function computeNewText(change: Change, sourceFile: SourceFile, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string {
function computeNewText(change: Change, targetSourceFile: SourceFile, sourceFile: SourceFile, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string {
if (change.kind === ChangeKind.Remove) {
return "";
}
@@ -1263,26 +1278,26 @@ namespace changesToText {
}
const { options = {}, range: { pos } } = change;
const format = (n: Node) => getFormattedTextOfNode(n, sourceFile, pos, options, newLineCharacter, formatContext, validate);
const format = (n: Node) => getFormattedTextOfNode(n, targetSourceFile, sourceFile, pos, options, newLineCharacter, formatContext, validate);
const text = change.kind === ChangeKind.ReplaceWithMultipleNodes
? change.nodes.map(n => removeSuffix(format(n), newLineCharacter)).join(change.options?.joiner || newLineCharacter)
: format(change.node);
// strip initial indentation (spaces or tabs) if text will be inserted in the middle of the line
const noIndent = (options.indentation !== undefined || getLineStartPositionForPosition(pos, sourceFile) === pos) ? text : text.replace(/^\s+/, "");
const noIndent = (options.indentation !== undefined || getLineStartPositionForPosition(pos, targetSourceFile) === pos) ? text : text.replace(/^\s+/, "");
return (options.prefix || "") + noIndent
+ ((!options.suffix || endsWith(noIndent, options.suffix))
? "" : options.suffix);
}
/** Note: this may mutate `nodeIn`. */
function getFormattedTextOfNode(nodeIn: Node, sourceFile: SourceFile, pos: number, { indentation, prefix, delta }: InsertNodeOptions, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string {
const { node, text } = getNonformattedText(nodeIn, sourceFile, newLineCharacter);
function getFormattedTextOfNode(nodeIn: Node, targetSourceFile: SourceFile, sourceFile: SourceFile, pos: number, { indentation, prefix, delta }: InsertNodeOptions, newLineCharacter: string, formatContext: formatting.FormatContext, validate: ValidateNonFormattedText | undefined): string {
const { node, text } = getNonformattedText(nodeIn, targetSourceFile, newLineCharacter);
if (validate) validate(node, text);
const formatOptions = getFormatCodeSettingsForWriting(formatContext, sourceFile);
const formatOptions = getFormatCodeSettingsForWriting(formatContext, targetSourceFile);
const initialIndentation =
indentation !== undefined
? indentation
: formatting.SmartIndenter.getIndentation(pos, sourceFile, formatOptions, prefix === newLineCharacter || getLineStartPositionForPosition(pos, sourceFile) === pos);
: formatting.SmartIndenter.getIndentation(pos, sourceFile, formatOptions, prefix === newLineCharacter || getLineStartPositionForPosition(pos, targetSourceFile) === pos);
if (delta === undefined) {
delta = formatting.SmartIndenter.shouldIndentChildNode(formatOptions, nodeIn) ? (formatOptions.indentSize || 0) : 0;
}
@@ -1293,7 +1308,7 @@ namespace changesToText {
return getLineAndCharacterOfPosition(this, pos);
}
};
const changes = formatting.formatNodeGivenIndentation(node, file, sourceFile.languageVariant, initialIndentation, delta, { ...formatContext, options: formatOptions });
const changes = formatting.formatNodeGivenIndentation(node, file, targetSourceFile.languageVariant, initialIndentation, delta, { ...formatContext, options: formatOptions });
return applyChanges(text, changes);
}
+36 -4
View File
@@ -378,6 +378,19 @@ export interface LanguageServiceHost extends GetEffectiveTypeRootsHost, MinimalR
containingSourceFile: SourceFile | undefined,
reusedNames: readonly T[] | undefined
): readonly ResolvedTypeReferenceDirectiveWithFailedLookupLocations[];
/** @internal */
resolveLibrary?(
libraryName: string,
resolveFrom: string,
options: CompilerOptions,
libFileName: string,
): ResolvedModuleWithFailedLookupLocations;
/**
* If provided along with custom resolveLibrary, used to determine if we should redo library resolutions
* @internal
*/
hasInvalidatedLibResolutions?(libFileName: string): boolean;
/** @internal */ hasInvalidatedResolutions?: HasInvalidatedResolutions;
/** @internal */ hasChangedAutomaticTypeDirectiveNames?: HasChangedAutomaticTypeDirectiveNames;
/** @internal */ getGlobalTypingsCacheLocation?(): string | undefined;
@@ -562,6 +575,8 @@ export interface LanguageService {
/** @deprecated Use the signature with `UserPreferences` instead. */
getRenameInfo(fileName: string, position: number, options?: RenameInfoOptions): RenameInfo;
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, preferences: UserPreferences): readonly RenameLocation[] | undefined;
/** @deprecated Pass `providePrefixAndSuffixTextForRename` as part of a `UserPreferences` parameter. */
findRenameLocations(fileName: string, position: number, findInStrings: boolean, findInComments: boolean, providePrefixAndSuffixTextForRename?: boolean): readonly RenameLocation[] | undefined;
getSmartSelectionRange(fileName: string, position: number): SelectionRange;
@@ -630,8 +645,15 @@ export interface LanguageService {
/** @deprecated `fileName` will be ignored */
applyCodeActionCommand(fileName: string, action: CodeActionCommand | CodeActionCommand[]): Promise<ApplyCodeActionCommandResult | ApplyCodeActionCommandResult[]>;
getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): ApplicableRefactorInfo[];
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined): RefactorEditInfo | undefined;
/**
* @param includeInteractiveActions Include refactor actions that require additional arguments to be
* passed when calling `getEditsForRefactor`. When true, clients should inspect the `isInteractive`
* property of each returned `RefactorActionInfo` and ensure they are able to collect the appropriate
* arguments for any interactive action before offering it.
*/
getApplicableRefactors(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string, includeInteractiveActions?: boolean): ApplicableRefactorInfo[];
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string, preferences: UserPreferences | undefined, includeInteractiveActions?: InteractiveRefactorArguments): RefactorEditInfo | undefined;
getMoveToRefactoringFileSuggestions(fileName: string, positionOrRange: number | TextRange, preferences: UserPreferences | undefined, triggerReason?: RefactorTriggerReason, kind?: string): { newFileName: string, files: string[] };
organizeImports(args: OrganizeImportsArgs, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[];
getEditsForFileRename(oldFilePath: string, newFilePath: string, formatOptions: FormatCodeSettings, preferences: UserPreferences | undefined): readonly FileTextChanges[];
@@ -963,6 +985,12 @@ export interface RefactorActionInfo {
* The hierarchical dotted name of the refactor action.
*/
kind?: string;
/**
* Indicates that the action requires additional arguments to be passed
* when calling `getEditsForRefactor`.
*/
isInteractive?: boolean;
}
/**
@@ -1266,6 +1294,10 @@ export interface DocCommentTemplateOptions {
readonly generateReturnInDocTemplate?: boolean;
}
export interface InteractiveRefactorArguments {
targetFile: string;
}
export interface SignatureHelpParameter {
name: string;
documentation: SymbolDisplayPart[];
@@ -1758,10 +1790,10 @@ export interface Refactor {
kinds?: string[];
/** Compute the associated code actions */
getEditsForAction(context: RefactorContext, actionName: string): RefactorEditInfo | undefined;
getEditsForAction(context: RefactorContext, actionName: string, interactiveRefactorArguments?: InteractiveRefactorArguments): RefactorEditInfo | undefined;
/** Compute (quickly) which actions are available here */
getAvailableActions(context: RefactorContext): readonly ApplicableRefactorInfo[];
getAvailableActions(context: RefactorContext, includeInteractive?: boolean, interactiveRefactorArguments?: InteractiveRefactorArguments): readonly ApplicableRefactorInfo[];
}
/** @internal */
+57 -2
View File
@@ -54,6 +54,7 @@ import {
ElementAccessExpression,
EmitFlags,
EmitHint,
emitModuleKindIsNonNodeESM,
emptyArray,
EndOfFileToken,
endsWith,
@@ -88,8 +89,10 @@ import {
getAssignmentDeclarationKind,
getCombinedNodeFlagsAlwaysIncludeJSDoc,
getDirectoryPath,
getEmitModuleKind,
getEmitScriptTarget,
getExternalModuleImportEqualsDeclarationExpression,
getImpliedNodeFormatForFile,
getIndentString,
getJSDocEnumTag,
getLastChild,
@@ -108,8 +111,10 @@ import {
getTextOfIdentifierOrLiteral,
getTextOfNode,
getTypesPackageName,
hasJSFileExtension,
hasSyntacticModifier,
HeritageClause,
hostGetCanonicalFileName,
Identifier,
identifierIsThisKeyword,
identity,
@@ -256,6 +261,7 @@ import {
JsTyping,
JsxEmit,
JsxOpeningLikeElement,
JsxTagNameExpression,
LabeledStatement,
LanguageServiceHost,
last,
@@ -267,6 +273,7 @@ import {
ModifierFlags,
ModuleDeclaration,
ModuleInstanceState,
ModuleKind,
ModuleResolutionKind,
ModuleSpecifierResolutionHost,
moduleSpecifiers,
@@ -350,6 +357,7 @@ import {
textSpanEnd,
Token,
tokenToString,
toPath,
tryCast,
Type,
TypeChecker,
@@ -610,7 +618,7 @@ function selectTagNameOfJsxOpeningLikeElement(node: JsxOpeningLikeElement) {
return node.tagName;
}
function isCalleeWorker<T extends CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement>(node: Node, pred: (node: Node) => node is T, calleeSelector: (node: T) => Expression, includeElementAccess: boolean, skipPastOuterExpressions: boolean) {
function isCalleeWorker<T extends CallExpression | NewExpression | TaggedTemplateExpression | Decorator | JsxOpeningLikeElement>(node: Node, pred: (node: Node) => node is T, calleeSelector: (node: T) => Expression | JsxTagNameExpression, includeElementAccess: boolean, skipPastOuterExpressions: boolean) {
let target = includeElementAccess ? climbPastPropertyOrElementAccess(node) : climbPastPropertyAccess(node);
if (skipPastOuterExpressions) {
target = skipOuterExpressions(target);
@@ -3137,7 +3145,12 @@ export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T>, in
export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T> | undefined, includeTrivia?: boolean): NodeArray<T> | undefined;
/** @internal */
export function getSynthesizedDeepClones<T extends Node>(nodes: NodeArray<T> | undefined, includeTrivia = true): NodeArray<T> | undefined {
return nodes && factory.createNodeArray(nodes.map(n => getSynthesizedDeepClone(n, includeTrivia)), nodes.hasTrailingComma);
if (nodes) {
const cloned = factory.createNodeArray(nodes.map(n => getSynthesizedDeepClone(n, includeTrivia)), nodes.hasTrailingComma);
setTextRange(cloned, nodes);
return cloned;
}
return nodes;
}
/** @internal */
@@ -4165,3 +4178,45 @@ export function newCaseClauseTracker(checker: TypeChecker, clauses: readonly (Ca
}
}
}
/** @internal */
export function fileShouldUseJavaScriptRequire(file: SourceFile | string, program: Program, host: LanguageServiceHost, preferRequire?: boolean) {
const fileName = typeof file === "string" ? file : file.fileName;
if (!hasJSFileExtension(fileName)) {
return false;
}
const compilerOptions = program.getCompilerOptions();
const moduleKind = getEmitModuleKind(compilerOptions);
const impliedNodeFormat = typeof file === "string"
? getImpliedNodeFormatForFile(toPath(file, host.getCurrentDirectory(), hostGetCanonicalFileName(host)), program.getPackageJsonInfoCache?.(), host, compilerOptions)
: file.impliedNodeFormat;
if (impliedNodeFormat === ModuleKind.ESNext) {
return false;
}
if (impliedNodeFormat === ModuleKind.CommonJS) {
// Since we're in a JS file, assume the user is writing the JS that will run
// (i.e., assume `noEmit`), so a CJS-format file should just have require
// syntax, rather than imports that will be downleveled to `require`.
return true;
}
if (compilerOptions.verbatimModuleSyntax && moduleKind === ModuleKind.CommonJS) {
// Using ESM syntax under these options would result in an error.
return true;
}
if (compilerOptions.verbatimModuleSyntax && emitModuleKindIsNonNodeESM(moduleKind)) {
return false;
}
// impliedNodeFormat is undefined and `verbatimModuleSyntax` is off (or in an invalid combo)
// Use heuristics from existing code
if (typeof file === "object") {
if (file.commonJsModuleIndicator) {
return true;
}
if (file.externalModuleIndicator) {
return false;
}
}
return preferRequire;
}
+10 -1
View File
@@ -53,7 +53,6 @@ import "./unittests/services/extract/constants";
import "./unittests/services/extract/functions";
import "./unittests/services/extract/symbolWalker";
import "./unittests/services/extract/ranges";
import "./unittests/services/findAllReferences";
import "./unittests/services/hostNewLineSupport";
import "./unittests/services/languageService";
import "./unittests/services/organizeImports";
@@ -78,6 +77,7 @@ import "./unittests/tsbuild/graphOrdering";
import "./unittests/tsbuild/inferredTypeFromTransitiveModule";
import "./unittests/tsbuild/javascriptProjectEmit";
import "./unittests/tsbuild/lateBoundSymbol";
import "./unittests/tsbuild/libraryResolution";
import "./unittests/tsbuild/moduleResolution";
import "./unittests/tsbuild/moduleSpecifiers";
import "./unittests/tsbuild/noEmit";
@@ -92,6 +92,7 @@ import "./unittests/tsbuild/sample";
import "./unittests/tsbuild/transitiveReferences";
import "./unittests/tsbuildWatch/configFileErrors";
import "./unittests/tsbuildWatch/demo";
import "./unittests/tsbuildWatch/libraryResolution";
import "./unittests/tsbuildWatch/moduleResolution";
import "./unittests/tsbuildWatch/noEmit";
import "./unittests/tsbuildWatch/noEmitOnError";
@@ -105,7 +106,9 @@ import "./unittests/tsc/composite";
import "./unittests/tsc/declarationEmit";
import "./unittests/tsc/forceConsistentCasingInFileNames";
import "./unittests/tsc/incremental";
import "./unittests/tsc/libraryResolution";
import "./unittests/tsc/listFilesOnly";
import "./unittests/tsc/moduleResolution";
import "./unittests/tsc/projectReferences";
import "./unittests/tsc/projectReferencesConfig";
import "./unittests/tsc/redirect";
@@ -116,6 +119,7 @@ import "./unittests/tscWatch/nodeNextWatch";
import "./unittests/tscWatch/emitAndErrorUpdates";
import "./unittests/tscWatch/forceConsistentCasingInFileNames";
import "./unittests/tscWatch/incremental";
import "./unittests/tscWatch/libraryResolution";
import "./unittests/tscWatch/moduleResolution";
import "./unittests/tscWatch/programUpdates";
import "./unittests/tscWatch/projectsWithReferences";
@@ -144,17 +148,20 @@ import "./unittests/tsserver/events/projectLoading";
import "./unittests/tsserver/events/projectUpdatedInBackground";
import "./unittests/tsserver/exportMapCache";
import "./unittests/tsserver/externalProjects";
import "./unittests/tsserver/findAllReferences";
import "./unittests/tsserver/forceConsistentCasingInFileNames";
import "./unittests/tsserver/formatSettings";
import "./unittests/tsserver/getApplicableRefactors";
import "./unittests/tsserver/getEditsForFileRename";
import "./unittests/tsserver/getExportReferences";
import "./unittests/tsserver/getFileReferences";
import "./unittests/tsserver/goToDefinition";
import "./unittests/tsserver/importHelpers";
import "./unittests/tsserver/inlayHints";
import "./unittests/tsserver/inferredProjects";
import "./unittests/tsserver/jsdocTag";
import "./unittests/tsserver/languageService";
import "./unittests/tsserver/libraryResolution";
import "./unittests/tsserver/maxNodeModuleJsDepth";
import "./unittests/tsserver/metadataInResponse";
import "./unittests/tsserver/moduleResolution";
@@ -165,6 +172,7 @@ import "./unittests/tsserver/openFile";
import "./unittests/tsserver/packageJsonInfo";
import "./unittests/tsserver/partialSemanticServer";
import "./unittests/tsserver/plugins";
import "./unittests/tsserver/pluginsAsync";
import "./unittests/tsserver/projectErrors";
import "./unittests/tsserver/projectReferenceCompileOnSave";
import "./unittests/tsserver/projectReferenceErrors";
@@ -194,3 +202,4 @@ import "./unittests/tsserver/versionCache";
import "./unittests/tsserver/watchEnvironment";
import "./unittests/debugDeprecation";
import "./unittests/tsserver/inconsistentErrorInEditor";
import "./unittests/tsserver/getMoveToRefactoringFileSuggestions";
@@ -106,6 +106,81 @@ describe("unittests:: evaluation:: forAwaitOfEvaluation", () => {
assert.instanceOf(result.output[2], Promise);
});
it("call return when user code return (es2015)", async () => {
const result = evaluator.evaluateTypeScript(`
let returnCalled = false;
async function f() {
const iterator = {
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
async next() {
return { value: undefined, done: false };
},
async return() {
returnCalled = true;
}
};
for await (const item of iterator) {
return;
}
}
export async function main() {
try { await f(); } catch { }
return returnCalled;
}
`, { target: ts.ScriptTarget.ES2015 });
assert.isTrue(await result.main());
});
it("call return when user code break (es2015)", async () => {
const result = evaluator.evaluateTypeScript(`
let returnCalled = false;
async function f() {
const iterator = {
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
async next() {
return { value: undefined, done: false };
},
async return() {
returnCalled = true;
}
};
for await (const item of iterator) {
break;
}
}
export async function main() {
try { await f(); } catch { }
return returnCalled;
}
`, { target: ts.ScriptTarget.ES2015 });
assert.isTrue(await result.main());
});
it("call return when user code throws (es2015)", async () => {
const result = evaluator.evaluateTypeScript(`
let returnCalled = false;
async function f() {
const iterator = {
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
async next() {
return { value: undefined, done: false };
},
async return() {
returnCalled = true;
}
};
for await (const item of iterator) {
throw new Error();
}
}
export async function main() {
try { await f(); } catch { }
return returnCalled;
}
`, { target: ts.ScriptTarget.ES2015 });
assert.isTrue(await result.main());
});
it("don't call return when non-user code throws (es2015)", async () => {
const result = evaluator.evaluateTypeScript(`
let returnCalled = false;
@@ -132,4 +207,96 @@ describe("unittests:: evaluation:: forAwaitOfEvaluation", () => {
`, { target: ts.ScriptTarget.ES2015 });
assert.isFalse(await result.main());
});
it("don't call return when user code continue (es2015)", async () => {
const result = evaluator.evaluateTypeScript(`
let returnCalled = false;
async function f() {
let i = 0;
const iterator = {
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
async next() {
i++;
if (i < 2) return { value: undefined, done: false };
throw new Error();
},
async return() {
returnCalled = true;
}
};
for await (const item of iterator) {
continue;
}
}
export async function main() {
try { await f(); } catch { }
return returnCalled;
}
`, { target: ts.ScriptTarget.ES2015 });
assert.isFalse(await result.main());
});
it("don't call return when user code continue to local label (es2015)", async () => {
const result = evaluator.evaluateTypeScript(`
let returnCalled = false;
async function f() {
let i = 0;
const iterator = {
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
async next() {
i++;
if (i < 2) return { value: undefined, done: false };
throw new Error();
},
async return() {
returnCalled = true;
}
};
outerLoop:
for (const outerItem of [1, 2, 3]) {
innerLoop:
for await (const item of iterator) {
continue innerLoop;
}
}
}
export async function main() {
try { await f(); } catch { }
return returnCalled;
}
`, { target: ts.ScriptTarget.ES2015 });
assert.isFalse(await result.main());
});
it("call return when user code continue to non-local label (es2015)", async () => {
const result = evaluator.evaluateTypeScript(`
let returnCalled = false;
async function f() {
let i = 0;
const iterator = {
[Symbol.asyncIterator](): AsyncIterableIterator<any> { return this; },
async next() {
i++;
if (i < 2) return { value: undefined, done: false };
return { value: undefined, done: true };
},
async return() {
returnCalled = true;
}
};
outerLoop:
for (const outerItem of [1, 2, 3]) {
innerLoop:
for await (const item of iterator) {
continue outerLoop;
}
}
}
export async function main() {
try { await f(); } catch { }
return returnCalled;
}
`, { target: ts.ScriptTarget.ES2015 });
assert.isTrue(await result.main());
});
});
@@ -0,0 +1,370 @@
import * as fakes from "../../_namespaces/fakes";
import * as Harness from "../../_namespaces/Harness";
import * as ts from "../../_namespaces/ts";
import { TscCompileSystem } from "./tsc";
import { TestServerHost } from "./virtualFileSystemWithWatch";
export type CommandLineProgram = [ts.Program, ts.BuilderProgram?];
export interface CommandLineCallbacks {
cb: ts.ExecuteCommandLineCallbacks;
getPrograms: () => readonly CommandLineProgram[];
}
function isAnyProgram(program: ts.Program | ts.BuilderProgram | ts.ParsedCommandLine): program is ts.Program | ts.BuilderProgram {
return !!(program as ts.Program | ts.BuilderProgram).getCompilerOptions;
}
export function commandLineCallbacks(
sys: TscCompileSystem | TestServerHost,
originalReadCall?: ts.System["readFile"],
): CommandLineCallbacks {
let programs: CommandLineProgram[] | undefined;
return {
cb: program => {
if (isAnyProgram(program)) {
baselineBuildInfo(program.getCompilerOptions(), sys, originalReadCall);
(programs || (programs = [])).push(ts.isBuilderProgram(program) ?
[program.getProgram(), program] :
[program]
);
}
else {
baselineBuildInfo(program.options, sys, originalReadCall);
}
},
getPrograms: () => {
const result = programs || ts.emptyArray;
programs = undefined;
return result;
}
};
}
export function baselinePrograms(baseline: string[], getPrograms: () => readonly CommandLineProgram[], oldPrograms: readonly (CommandLineProgram | undefined)[], baselineDependencies: boolean | undefined) {
const programs = getPrograms();
for (let i = 0; i < programs.length; i++) {
baselineProgram(baseline, programs[i], oldPrograms[i], baselineDependencies);
}
return programs;
}
function baselineProgram(baseline: string[], [program, builderProgram]: CommandLineProgram, oldProgram: CommandLineProgram | undefined, baselineDependencies: boolean | undefined) {
if (program !== oldProgram?.[0]) {
const options = program.getCompilerOptions();
baseline.push(`Program root files: ${JSON.stringify(program.getRootFileNames())}`);
baseline.push(`Program options: ${JSON.stringify(options)}`);
baseline.push(`Program structureReused: ${(ts as any).StructureIsReused[program.structureIsReused]}`);
baseline.push("Program files::");
for (const file of program.getSourceFiles()) {
baseline.push(file.fileName);
}
}
else {
baseline.push(`Program: Same as old program`);
}
baseline.push("");
if (!builderProgram) return;
if (builderProgram !== oldProgram?.[1]) {
const state = builderProgram.getState();
const internalState = state as unknown as ts.BuilderProgramState;
if (state.semanticDiagnosticsPerFile?.size) {
baseline.push("Semantic diagnostics in builder refreshed for::");
for (const file of program.getSourceFiles()) {
if (!internalState.semanticDiagnosticsFromOldState || !internalState.semanticDiagnosticsFromOldState.has(file.resolvedPath)) {
baseline.push(file.fileName);
}
}
}
else {
baseline.push("No cached semantic diagnostics in the builder::");
}
if (internalState) {
baseline.push("");
if (internalState.hasCalledUpdateShapeSignature?.size) {
baseline.push("Shape signatures in builder refreshed for::");
internalState.hasCalledUpdateShapeSignature.forEach((path: ts.Path) => {
const info = state.fileInfos.get(path);
if (info?.version === info?.signature || !info?.signature) {
baseline.push(path + " (used version)");
}
else if (internalState.filesChangingSignature?.has(path)) {
baseline.push(path + " (computed .d.ts during emit)");
}
else {
baseline.push(path + " (computed .d.ts)");
}
});
}
else {
baseline.push("No shapes updated in the builder::");
}
}
baseline.push("");
if (!baselineDependencies) return;
baseline.push("Dependencies for::");
for (const file of builderProgram.getSourceFiles()) {
baseline.push(`${file.fileName}:`);
for (const depenedency of builderProgram.getAllDependencies(file)) {
baseline.push(` ${depenedency}`);
}
}
}
else {
baseline.push(`BuilderProgram: Same as old builder program`);
}
baseline.push("");
}
export function generateSourceMapBaselineFiles(sys: ts.System & { writtenFiles: ts.ReadonlyCollection<ts.Path>; }) {
const mapFileNames = ts.mapDefinedIterator(sys.writtenFiles.keys(), f => f.endsWith(".map") ? f : undefined);
for (const mapFile of mapFileNames) {
const text = Harness.SourceMapRecorder.getSourceMapRecordWithSystem(sys, mapFile);
sys.writeFile(`${mapFile}.baseline.txt`, text);
}
}
function generateBundleFileSectionInfo(sys: ts.System, originalReadCall: ts.System["readFile"], baselineRecorder: Harness.Compiler.WriterAggregator, bundleFileInfo: ts.BundleFileInfo | undefined, outFile: string | undefined) {
if (!ts.length(bundleFileInfo && bundleFileInfo.sections) && !outFile) return; // Nothing to baseline
const content = outFile && sys.fileExists(outFile) ? originalReadCall.call(sys, outFile, "utf8")! : "";
baselineRecorder.WriteLine("======================================================================");
baselineRecorder.WriteLine(`File:: ${outFile}`);
for (const section of bundleFileInfo ? bundleFileInfo.sections : ts.emptyArray) {
baselineRecorder.WriteLine("----------------------------------------------------------------------");
writeSectionHeader(section);
if (section.kind !== ts.BundleFileSectionKind.Prepend) {
writeTextOfSection(section.pos, section.end);
}
else if (section.texts.length > 0) {
ts.Debug.assert(section.pos === ts.first(section.texts).pos);
ts.Debug.assert(section.end === ts.last(section.texts).end);
for (const text of section.texts) {
baselineRecorder.WriteLine(">>--------------------------------------------------------------------");
writeSectionHeader(text);
writeTextOfSection(text.pos, text.end);
}
}
else {
ts.Debug.assert(section.pos === section.end);
}
}
baselineRecorder.WriteLine("======================================================================");
function writeTextOfSection(pos: number, end: number) {
const textLines = content.substring(pos, end).split(/\r?\n/);
for (const line of textLines) {
baselineRecorder.WriteLine(line);
}
}
function writeSectionHeader(section: ts.BundleFileSection) {
baselineRecorder.WriteLine(`${section.kind}: (${section.pos}-${section.end})${section.data ? ":: " + section.data : ""}${section.kind === ts.BundleFileSectionKind.Prepend ? " texts:: " + section.texts.length : ""}`);
}
}
export type ReadableProgramBuildInfoDiagnostic = string | [string, readonly ts.ReusableDiagnostic[]];
export type ReadableBuilderFileEmit = string & { __readableBuilderFileEmit: any; };
export type ReadableProgramBuilderInfoFilePendingEmit = [original: string | [string], emitKind: ReadableBuilderFileEmit];
export type ReadableProgramBuildInfoEmitSignature = string | [string, ts.EmitSignature | []];
export type ReadableProgramBuildInfoFileInfo<T> = Omit<ts.BuilderState.FileInfo, "impliedFormat"> & {
impliedFormat: string | undefined;
original: T | undefined;
};
export type ReadableProgramBuildInfoRoot =
[original: ts.ProgramBuildInfoFileId, readable: string] |
[orginal: ts.ProgramBuildInfoRootStartEnd, readable: readonly string[]];
export type ReadableProgramMultiFileEmitBuildInfo = Omit<ts.ProgramMultiFileEmitBuildInfo,
"fileIdsList" | "fileInfos" | "root" |
"referencedMap" | "exportedModulesMap" | "semanticDiagnosticsPerFile" |
"affectedFilesPendingEmit" | "changeFileSet" | "emitSignatures"
> & {
fileNamesList: readonly (readonly string[])[] | undefined;
fileInfos: ts.MapLike<ReadableProgramBuildInfoFileInfo<ts.ProgramMultiFileEmitBuildInfoFileInfo>>;
root: readonly ReadableProgramBuildInfoRoot[];
referencedMap: ts.MapLike<string[]> | undefined;
exportedModulesMap: ts.MapLike<string[]> | undefined;
semanticDiagnosticsPerFile: readonly ReadableProgramBuildInfoDiagnostic[] | undefined;
affectedFilesPendingEmit: readonly ReadableProgramBuilderInfoFilePendingEmit[] | undefined;
changeFileSet: readonly string[] | undefined;
emitSignatures: readonly ReadableProgramBuildInfoEmitSignature[] | undefined;
};
export type ReadableProgramBuildInfoBundlePendingEmit = [emitKind: ReadableBuilderFileEmit, original: ts.ProgramBuildInfoBundlePendingEmit];
export type ReadableProgramBundleEmitBuildInfo = Omit<ts.ProgramBundleEmitBuildInfo, "fileInfos" | "root" | "pendingEmit"> & {
fileInfos: ts.MapLike<string | ReadableProgramBuildInfoFileInfo<ts.BuilderState.FileInfo>>;
root: readonly ReadableProgramBuildInfoRoot[];
pendingEmit: ReadableProgramBuildInfoBundlePendingEmit | undefined;
};
export type ReadableProgramBuildInfo = ReadableProgramMultiFileEmitBuildInfo | ReadableProgramBundleEmitBuildInfo;
export function isReadableProgramBundleEmitBuildInfo(info: ReadableProgramBuildInfo | undefined): info is ReadableProgramBundleEmitBuildInfo {
return !!info && !!ts.outFile(info.options || {});
}
export type ReadableBuildInfo = Omit<ts.BuildInfo, "program"> & { program: ReadableProgramBuildInfo | undefined; size: number; };
function generateBuildInfoProgramBaseline(sys: ts.System, buildInfoPath: string, buildInfo: ts.BuildInfo) {
let program: ReadableProgramBuildInfo | undefined;
let fileNamesList: string[][] | undefined;
if (buildInfo.program && ts.isProgramBundleEmitBuildInfo(buildInfo.program)) {
const fileInfos: ReadableProgramBundleEmitBuildInfo["fileInfos"] = {};
buildInfo.program?.fileInfos?.forEach((fileInfo, index) =>
fileInfos[toFileName(index + 1 as ts.ProgramBuildInfoFileId)] = ts.isString(fileInfo) ?
fileInfo :
toReadableFileInfo(fileInfo, ts.identity)
);
const pendingEmit = buildInfo.program.pendingEmit;
program = {
...buildInfo.program,
fileInfos,
root: buildInfo.program.root.map(toReadableProgramBuildInfoRoot),
pendingEmit: pendingEmit === undefined ?
undefined :
[
toReadableBuilderFileEmit(ts.toProgramEmitPending(pendingEmit, buildInfo.program.options)),
pendingEmit
],
};
}
else if (buildInfo.program) {
const fileInfos: ReadableProgramMultiFileEmitBuildInfo["fileInfos"] = {};
buildInfo.program?.fileInfos?.forEach((fileInfo, index) => fileInfos[toFileName(index + 1 as ts.ProgramBuildInfoFileId)] = toReadableFileInfo(fileInfo, ts.toBuilderStateFileInfoForMultiEmit));
fileNamesList = buildInfo.program.fileIdsList?.map(fileIdsListId => fileIdsListId.map(toFileName));
const fullEmitForOptions = buildInfo.program.affectedFilesPendingEmit ? ts.getBuilderFileEmit(buildInfo.program.options || {}) : undefined;
program = buildInfo.program && {
fileNames: buildInfo.program.fileNames,
fileNamesList,
fileInfos: buildInfo.program.fileInfos ? fileInfos : undefined!,
root: buildInfo.program.root.map(toReadableProgramBuildInfoRoot),
options: buildInfo.program.options,
referencedMap: toMapOfReferencedSet(buildInfo.program.referencedMap),
exportedModulesMap: toMapOfReferencedSet(buildInfo.program.exportedModulesMap),
semanticDiagnosticsPerFile: buildInfo.program.semanticDiagnosticsPerFile?.map(d =>
ts.isNumber(d) ?
toFileName(d) :
[toFileName(d[0]), d[1]]
),
affectedFilesPendingEmit: buildInfo.program.affectedFilesPendingEmit?.map(value => toReadableProgramBuilderInfoFilePendingEmit(value, fullEmitForOptions!)),
changeFileSet: buildInfo.program.changeFileSet?.map(toFileName),
emitSignatures: buildInfo.program.emitSignatures?.map(s =>
ts.isNumber(s) ?
toFileName(s) :
[toFileName(s[0]), s[1]]
),
latestChangedDtsFile: buildInfo.program.latestChangedDtsFile,
};
}
const version = buildInfo.version === ts.version ? fakes.version : buildInfo.version;
const result: ReadableBuildInfo = {
// Baseline fixed order for bundle
bundle: buildInfo.bundle && {
...buildInfo.bundle,
js: buildInfo.bundle.js && {
sections: buildInfo.bundle.js.sections,
hash: buildInfo.bundle.js.hash,
mapHash: buildInfo.bundle.js.mapHash,
sources: buildInfo.bundle.js.sources,
},
dts: buildInfo.bundle.dts && {
sections: buildInfo.bundle.dts.sections,
hash: buildInfo.bundle.dts.hash,
mapHash: buildInfo.bundle.dts.mapHash,
sources: buildInfo.bundle.dts.sources,
},
},
program,
version,
size: ts.getBuildInfoText({ ...buildInfo, version }).length,
};
// For now its just JSON.stringify
sys.writeFile(`${buildInfoPath}.readable.baseline.txt`, JSON.stringify(result, /*replacer*/ undefined, 2));
function toFileName(fileId: ts.ProgramBuildInfoFileId) {
return buildInfo.program!.fileNames[fileId - 1];
}
function toFileNames(fileIdsListId: ts.ProgramBuildInfoFileIdListId) {
return fileNamesList![fileIdsListId - 1];
}
function toReadableFileInfo<T>(original: T, toFileInfo: (fileInfo: T) => ts.BuilderState.FileInfo): ReadableProgramBuildInfoFileInfo<T> {
const info = toFileInfo(original);
return {
original: ts.isString(original) ? undefined : original,
...info,
impliedFormat: info.impliedFormat && ts.getNameOfCompilerOptionValue(info.impliedFormat, ts.moduleOptionDeclaration.type),
};
}
function toReadableProgramBuildInfoRoot(original: ts.ProgramBuildInfoRoot): ReadableProgramBuildInfoRoot {
if (!ts.isArray(original)) return [original, toFileName(original)];
const readable: string[] = [];
for (let index = original[0]; index <= original[1]; index++) readable.push(toFileName(index));
return [original, readable];
}
function toMapOfReferencedSet(referenceMap: ts.ProgramBuildInfoReferencedMap | undefined): ts.MapLike<string[]> | undefined {
if (!referenceMap) return undefined;
const result: ts.MapLike<string[]> = {};
for (const [fileNamesKey, fileNamesListKey] of referenceMap) {
result[toFileName(fileNamesKey)] = toFileNames(fileNamesListKey);
}
return result;
}
function toReadableProgramBuilderInfoFilePendingEmit(value: ts.ProgramBuilderInfoFilePendingEmit, fullEmitForOptions: ts.BuilderFileEmit): ReadableProgramBuilderInfoFilePendingEmit {
return [
ts.isNumber(value) ? toFileName(value) : [toFileName(value[0])],
toReadableBuilderFileEmit(ts.toBuilderFileEmit(value, fullEmitForOptions)),
];
}
function toReadableBuilderFileEmit(emit: ts.BuilderFileEmit | undefined): ReadableBuilderFileEmit {
let result = "";
if (emit) {
if (emit & ts.BuilderFileEmit.Js) addFlags("Js");
if (emit & ts.BuilderFileEmit.JsMap) addFlags("JsMap");
if (emit & ts.BuilderFileEmit.JsInlineMap) addFlags("JsInlineMap");
if (emit & ts.BuilderFileEmit.Dts) addFlags("Dts");
if (emit & ts.BuilderFileEmit.DtsMap) addFlags("DtsMap");
}
return (result || "None") as ReadableBuilderFileEmit;
function addFlags(flag: string) {
result = result ? `${result} | ${flag}` : flag;
}
}
}
export function toPathWithSystem(sys: ts.System, fileName: string): ts.Path {
return ts.toPath(fileName, sys.getCurrentDirectory(), ts.createGetCanonicalFileName(sys.useCaseSensitiveFileNames));
}
export function baselineBuildInfo(
options: ts.CompilerOptions,
sys: TscCompileSystem | TestServerHost,
originalReadCall?: ts.System["readFile"],
) {
const buildInfoPath = ts.getTsBuildInfoEmitOutputFilePath(options);
if (!buildInfoPath || !sys.writtenFiles!.has(toPathWithSystem(sys, buildInfoPath))) return;
if (!sys.fileExists(buildInfoPath)) return;
const buildInfo = ts.getBuildInfo(buildInfoPath, (originalReadCall || sys.readFile).call(sys, buildInfoPath, "utf8")!);
if (!buildInfo) return sys.writeFile(`${buildInfoPath}.baseline.txt`, "Error reading valid buildinfo file");
generateBuildInfoProgramBaseline(sys, buildInfoPath, buildInfo);
if (!ts.outFile(options)) return;
const { jsFilePath, declarationFilePath } = ts.getOutputPathsForBundle(options, /*forceDtsPaths*/ false);
const bundle = buildInfo.bundle;
if (!bundle || (!ts.length(bundle.js && bundle.js.sections) && !ts.length(bundle.dts && bundle.dts.sections))) return;
// Write the baselines:
const baselineRecorder = new Harness.Compiler.WriterAggregator();
generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.js, jsFilePath);
generateBundleFileSectionInfo(sys, originalReadCall || sys.readFile, baselineRecorder, bundle.dts, declarationFilePath);
baselineRecorder.Close();
const text = baselineRecorder.lines.join("\r\n");
sys.writeFile(`${buildInfoPath}.baseline.txt`, text);
}
export function tscBaselineName(scenario: string, subScenario: string, commandLineArgs: readonly string[], isWatch?: boolean, suffix?: string) {
return `${ts.isBuild(commandLineArgs) ? "tsbuild" : "tsc"}${isWatch ? "Watch" : ""}/${scenario}/${subScenario.split(" ").join("-")}${suffix ? suffix : ""}.js`;
}
@@ -0,0 +1,25 @@
import * as ts from "../../_namespaces/ts";
import { libFile } from "./virtualFileSystemWithWatch";
export function compilerOptionsToConfigJson(options: ts.CompilerOptions) {
return ts.optionMapToObject(ts.serializeCompilerOptions(options));
}
export const libContent = `${libFile.content}
interface ReadonlyArray<T> {}
declare const console: { log(msg: any): void; };`;
export const symbolLibContent = `
interface SymbolConstructor {
readonly species: symbol;
readonly toStringTag: symbol;
}
declare var Symbol: SymbolConstructor;
interface Symbol {
readonly [Symbol.toStringTag]: string;
}
`;
export interface FsContents {
[path: string]: string;
}
@@ -0,0 +1,86 @@
import { dedent } from "../../_namespaces/Utils";
import { FsContents, libContent } from "./contents";
import { loadProjectFromFiles } from "./vfs";
import { createServerHost, createWatchedSystem } from "./virtualFileSystemWithWatch";
function getFsContentsForLibResolution(libRedirection?: boolean): FsContents {
return {
"/home/src/projects/project1/utils.d.ts": `export const y = 10;`,
"/home/src/projects/project1/file.ts": `export const file = 10;`,
"/home/src/projects/project1/core.d.ts": `export const core = 10;`,
"/home/src/projects/project1/index.ts": `export const x = "type1";`,
"/home/src/projects/project1/file2.ts": dedent`
/// <reference lib="webworker"/>
/// <reference lib="scripthost"/>
/// <reference lib="es5"/>
`,
"/home/src/projects/project1/tsconfig.json": JSON.stringify({
compilerOptions: { composite: true, typeRoots: ["./typeroot1"], lib: ["es5", "dom"], traceResolution: true },
}),
"/home/src/projects/project1/typeroot1/sometype/index.d.ts": `export type TheNum = "type1";`,
"/home/src/projects/project2/utils.d.ts": `export const y = 10;`,
"/home/src/projects/project2/index.ts": `export const y = 10`,
"/home/src/projects/project2/tsconfig.json": JSON.stringify({
compilerOptions: { composite: true, lib: ["es5", "dom"], traceResolution: true },
}),
"/home/src/projects/project3/utils.d.ts": `export const y = 10;`,
"/home/src/projects/project3/index.ts": `export const z = 10`,
"/home/src/projects/project3/tsconfig.json": JSON.stringify({
compilerOptions: { composite: true, lib: ["es5", "dom"], traceResolution: true },
}),
"/home/src/projects/project4/utils.d.ts": `export const y = 10;`,
"/home/src/projects/project4/index.ts": `export const z = 10`,
"/home/src/projects/project4/tsconfig.json": JSON.stringify({
compilerOptions: { composite: true, lib: ["esnext", "dom", "webworker"], traceResolution: true },
}),
"/home/src/lib/lib.es5.d.ts": libContent,
"/home/src/lib/lib.esnext.d.ts": libContent,
"/home/src/lib/lib.dom.d.ts": "interface DOMInterface { }",
"/home/src/lib/lib.webworker.d.ts": "interface WebWorkerInterface { }",
"/home/src/lib/lib.scripthost.d.ts": "interface ScriptHostInterface { }",
"/home/src/projects/node_modules/@typescript/unlreated/index.d.ts": "export const unrelated = 10;",
...libRedirection ? {
"/home/src/projects/node_modules/@typescript/lib-es5/index.d.ts": libContent,
"/home/src/projects/node_modules/@typescript/lib-esnext/index.d.ts": libContent,
"/home/src/projects/node_modules/@typescript/lib-dom/index.d.ts": "interface DOMInterface { }",
"/home/src/projects/node_modules/@typescript/lib-webworker/index.d.ts": "interface WebworkerInterface { }",
"/home/src/projects/node_modules/@typescript/lib-scripthost/index.d.ts": "interface ScriptHostInterface { }",
} : undefined
};
}
export function getFsForLibResolution(libRedirection: true | undefined) {
return loadProjectFromFiles(
getFsContentsForLibResolution(libRedirection),
{
cwd: "/home/src/projects",
executingFilePath: "/home/src/lib/tsc.js",
}
);
}
export function getSysForLibResolution(libRedirection?: true) {
return createWatchedSystem(
getFsContentsForLibResolution(libRedirection),
{
currentDirectory: "/home/src/projects",
executingFilePath: "/home/src/lib/tsc.js",
}
);
}
export function getServerHosForLibResolution(libRedirection?: true) {
return createServerHost(
getFsContentsForLibResolution(libRedirection),
{
currentDirectory: "/home/src/projects",
executingFilePath: "/home/src/lib/tsc.js",
}
);
}
export function getCommandLineArgsForLibResolution(withoutConfig: true | undefined) {
return withoutConfig ?
["project1/core.d.ts", "project1/utils.d.ts", "project1/file.ts", "project1/index.ts", "project1/file2.ts", "--lib", "es5,dom", "--traceResolution", "--explainFiles"] :
["-p", "project1", "--explainFiles"];
}
@@ -0,0 +1,85 @@
import { dedent } from "../../_namespaces/Utils";
import { FsContents } from "./contents";
import { libFile } from "./virtualFileSystemWithWatch";
export function getFsConentsForNode10ResultAtTypesPackageJson(packageName: string, addTypesCondition: boolean) {
return JSON.stringify({
name: `@types/${packageName}`,
version: "1.0.0",
types: "index.d.ts",
exports: {
".": {
...(addTypesCondition ? { types: "./index.d.ts" } : {}),
require: "./index.d.ts"
}
}
}, undefined, " ");
}
export function getFsContentsForNode10ResultPackageJson(packageName: string, addTypes: boolean, addTypesCondition: boolean) {
return JSON.stringify({
name: packageName,
version: "1.0.0",
main: "index.js",
...(addTypes ? { types: "index.d.ts" } : {}),
exports: {
".": {
...(addTypesCondition ? { types: "./index.d.ts" } : {}),
import: "./index.mjs",
require: "./index.js"
}
}
}, undefined, " ");
}
export function getFsContentsForNode10ResultDts(packageName: string) {
return `export declare const ${packageName}: number;`;
}
function js(packageName: string) {
return `module.exports = { ${packageName}: 1 };`;
}
function mjs(packageName: string) {
return `export const ${packageName} = 1;`;
}
export function getFsContentsForNode10Result(): FsContents {
return {
"/home/src/projects/project/node_modules/@types/bar/package.json": getFsConentsForNode10ResultAtTypesPackageJson("bar", /*addTypesCondition*/ false),
"/home/src/projects/project/node_modules/@types/bar/index.d.ts": getFsContentsForNode10ResultDts("bar"),
"/home/src/projects/project/node_modules/bar/package.json": getFsContentsForNode10ResultPackageJson("bar", /*addTypes*/ false, /*addTypesCondition*/ false),
"/home/src/projects/project/node_modules/bar/index.js": js("bar"),
"/home/src/projects/project/node_modules/bar/index.mjs": mjs("bar"),
"/home/src/projects/project/node_modules/foo/package.json": getFsContentsForNode10ResultPackageJson("foo", /*addTypes*/ true, /*addTypesCondition*/ false),
"/home/src/projects/project/node_modules/foo/index.js": js("foo"),
"/home/src/projects/project/node_modules/foo/index.mjs": mjs("foo"),
"/home/src/projects/project/node_modules/foo/index.d.ts": getFsContentsForNode10ResultDts("foo"),
"/home/src/projects/project/node_modules/@types/bar2/package.json": getFsConentsForNode10ResultAtTypesPackageJson("bar2", /*addTypesCondition*/ true),
"/home/src/projects/project/node_modules/@types/bar2/index.d.ts": getFsContentsForNode10ResultDts("bar2"),
"/home/src/projects/project/node_modules/bar2/package.json": getFsContentsForNode10ResultPackageJson("bar2", /*addTypes*/ false, /*addTypesCondition*/ false),
"/home/src/projects/project/node_modules/bar2/index.js": js("bar2"),
"/home/src/projects/project/node_modules/bar2/index.mjs": mjs("bar2"),
"/home/src/projects/project/node_modules/foo2/package.json": getFsContentsForNode10ResultPackageJson("foo2", /*addTypes*/ true, /*addTypesCondition*/ true),
"/home/src/projects/project/node_modules/foo2/index.js": js("foo2"),
"/home/src/projects/project/node_modules/foo2/index.mjs": mjs("foo2"),
"/home/src/projects/project/node_modules/foo2/index.d.ts": getFsContentsForNode10ResultDts("foo2"),
"/home/src/projects/project/index.mts": dedent`
import { foo } from "foo";
import { bar } from "bar";
import { foo2 } from "foo2";
import { bar2 } from "bar2";
`,
"/home/src/projects/project/tsconfig.json": JSON.stringify({
compilerOptions: {
moduleResolution: "node16",
traceResolution: true,
incremental: true,
strict: true,
types: [],
},
files: ["index.mts"]
}),
[libFile.path]: libFile.content,
};
}
@@ -0,0 +1,63 @@
import * as fakes from "../../_namespaces/fakes";
import * as ts from "../../_namespaces/ts";
import { commandLineCallbacks } from "./baseline";
import {
makeSystemReadyForBaseline,
TscCompileSystem,
} from "./tsc";
import {
changeToHostTrackingWrittenFiles,
createWatchedSystem,
FileOrFolderOrSymLink,
FileOrFolderOrSymLinkMap,
TestServerHost,
TestServerHostCreationParameters,
} from "./virtualFileSystemWithWatch";
export function createSolutionBuilderHostForBaseline(
sys: TscCompileSystem | TestServerHost,
versionToWrite?: string,
originalRead?: (TscCompileSystem | TestServerHost)["readFile"]
) {
if (sys instanceof fakes.System) makeSystemReadyForBaseline(sys, versionToWrite);
const { cb } = commandLineCallbacks(sys, originalRead);
const host = ts.createSolutionBuilderHost(sys,
/*createProgram*/ undefined,
ts.createDiagnosticReporter(sys, /*pretty*/ true),
ts.createBuilderStatusReporter(sys, /*pretty*/ true)
);
host.afterProgramEmitAndDiagnostics = cb;
host.afterEmitBundle = cb;
return host;
}
export function createSolutionBuilder(system: TestServerHost, rootNames: readonly string[], originalRead?: TestServerHost["readFile"]) {
const host = createSolutionBuilderHostForBaseline(system, /*versionToWrite*/ undefined, originalRead);
return ts.createSolutionBuilder(host, rootNames, {});
}
export function ensureErrorFreeBuild(host: TestServerHost, rootNames: readonly string[]) {
// ts build should succeed
solutionBuildWithBaseline(host, rootNames);
assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " "));
}
export function solutionBuildWithBaseline(sys: TestServerHost, solutionRoots: readonly string[], originalRead?: TestServerHost["readFile"]) {
const originalReadFile = sys.readFile;
const originalWrite = sys.write;
const originalWriteFile = sys.writeFile;
ts.Debug.assert(sys.writtenFiles === undefined);
const solutionBuilder = createSolutionBuilder(changeToHostTrackingWrittenFiles(
fakes.patchHostForBuildInfoReadWrite(sys)
), solutionRoots, originalRead);
solutionBuilder.build();
sys.readFile = originalReadFile;
sys.write = originalWrite;
sys.writeFile = originalWriteFile;
sys.writtenFiles = undefined;
return sys;
}
export function createSystemWithSolutionBuild(solutionRoots: readonly string[], files: FileOrFolderOrSymLinkMap | readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters) {
return solutionBuildWithBaseline(createWatchedSystem(files, params), solutionRoots);
}
+545
View File
@@ -0,0 +1,545 @@
import * as fakes from "../../_namespaces/fakes";
import * as Harness from "../../_namespaces/Harness";
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import {
baselinePrograms,
CommandLineCallbacks,
commandLineCallbacks,
CommandLineProgram,
generateSourceMapBaselineFiles,
isReadableProgramBundleEmitBuildInfo,
ReadableBuildInfo,
ReadableProgramBuildInfoFileInfo,
ReadableProgramMultiFileEmitBuildInfo,
toPathWithSystem,
tscBaselineName,
} from "./baseline";
export interface DtsSignatureData {
signature: string | undefined;
exportedModules: string[] | undefined;
}
export type TscCompileSystem = fakes.System & {
writtenFiles: Set<ts.Path>;
baseLine(): { file: string; text: string; };
dtsSignaures?: Map<ts.Path, Map<string, DtsSignatureData>>;
storeFilesChangingSignatureDuringEmit?: boolean;
};
export const noChangeRun: TestTscEdit = {
caption: "no-change-run",
edit: ts.noop
};
export const noChangeOnlyRuns = [noChangeRun];
export interface TestTscCompile extends TestTscCompileLikeBase {
baselineSourceMap?: boolean;
baselineReadFileCalls?: boolean;
baselinePrograms?: boolean;
baselineDependencies?: boolean;
}
export interface TestTscCompileLikeBase extends VerifyTscCompileLike {
diffWithInitial?: boolean;
modifyFs?: (fs: vfs.FileSystem) => void;
computeDtsSignatures?: boolean;
environmentVariables?: Record<string, string>;
}
export interface TestTscCompileLike extends TestTscCompileLikeBase {
compile: (sys: TscCompileSystem) => void;
additionalBaseline?: (sys: TscCompileSystem) => void;
}
/**
* Initialize FS, run compile function and save baseline
*/
export function testTscCompileLike(input: TestTscCompileLike) {
const initialFs = input.fs();
const inputFs = initialFs.shadow();
const {
scenario, subScenario, diffWithInitial,
commandLineArgs, modifyFs,
environmentVariables,
compile: worker, additionalBaseline,
} = input;
if (modifyFs) modifyFs(inputFs);
inputFs.makeReadonly();
const fs = inputFs.shadow();
// Create system
const sys = new fakes.System(fs, { executingFilePath: `${fs.meta.get("defaultLibLocation")}/tsc`, env: environmentVariables }) as TscCompileSystem;
sys.storeFilesChangingSignatureDuringEmit = true;
sys.write(`${sys.getExecutingFilePath()} ${commandLineArgs.join(" ")}\n`);
sys.exit = exitCode => sys.exitCode = exitCode;
worker(sys);
sys.write(`exitCode:: ExitStatus.${ts.ExitStatus[sys.exitCode as ts.ExitStatus]}\n`);
additionalBaseline?.(sys);
fs.makeReadonly();
sys.baseLine = () => {
const baseFsPatch = diffWithInitial ?
inputFs.diff(initialFs, { includeChangedFileWithSameContent: true }) :
inputFs.diff(/*base*/ undefined, { baseIsNotShadowRoot: true });
const patch = fs.diff(inputFs, { includeChangedFileWithSameContent: true });
return {
file: tscBaselineName(scenario, subScenario, commandLineArgs),
text: `Input::
${baseFsPatch ? vfs.formatPatch(baseFsPatch) : ""}
Output::
${sys.output.join("")}
${patch ? vfs.formatPatch(patch) : ""}`
};
};
return sys;
}
export function makeSystemReadyForBaseline(sys: TscCompileSystem, versionToWrite?: string) {
if (versionToWrite) {
fakes.patchHostForBuildInfoWrite(sys, versionToWrite);
}
else {
fakes.patchHostForBuildInfoReadWrite(sys);
}
const writtenFiles = sys.writtenFiles = new Set();
const originalWriteFile = sys.writeFile;
sys.writeFile = (fileName, content, writeByteOrderMark) => {
const path = toPathWithSystem(sys, fileName);
// When buildinfo is same for two projects,
// it gives error and doesnt write buildinfo but because buildInfo is written for one project,
// readable baseline will be written two times for those two projects with same contents and is ok
ts.Debug.assert(!writtenFiles.has(path) || ts.endsWith(path, "baseline.txt"));
writtenFiles.add(path);
return originalWriteFile.call(sys, fileName, content, writeByteOrderMark);
};
}
/**
* Initialize Fs, execute command line and save baseline
*/
export function testTscCompile(input: TestTscCompile) {
let actualReadFileMap: ts.MapLike<number> | undefined;
let getPrograms: CommandLineCallbacks["getPrograms"] | undefined;
return testTscCompileLike({
...input,
compile: commandLineCompile,
additionalBaseline
});
function commandLineCompile(sys: TscCompileSystem) {
makeSystemReadyForBaseline(sys);
actualReadFileMap = {};
const originalReadFile = sys.readFile;
sys.readFile = path => {
// Dont record libs
if (path.startsWith("/src/")) {
actualReadFileMap![path] = (ts.getProperty(actualReadFileMap!, path) || 0) + 1;
}
return originalReadFile.call(sys, path);
};
const result = commandLineCallbacks(sys, originalReadFile);
ts.executeCommandLine(
sys,
result.cb,
input.commandLineArgs,
);
sys.readFile = originalReadFile;
getPrograms = result.getPrograms;
}
function additionalBaseline(sys: TscCompileSystem) {
const { baselineSourceMap, baselineReadFileCalls, baselinePrograms: shouldBaselinePrograms, baselineDependencies } = input;
if (input.computeDtsSignatures) storeDtsSignatures(sys, getPrograms!());
if (shouldBaselinePrograms) {
const baseline: string[] = [];
baselinePrograms(baseline, getPrograms!, ts.emptyArray, baselineDependencies);
sys.write(baseline.join("\n"));
}
if (baselineReadFileCalls) {
sys.write(`readFiles:: ${JSON.stringify(actualReadFileMap, /*replacer*/ undefined, " ")} `);
}
if (baselineSourceMap) generateSourceMapBaselineFiles(sys);
actualReadFileMap = undefined;
getPrograms = undefined;
}
}
function storeDtsSignatures(sys: TscCompileSystem, programs: readonly CommandLineProgram[]) {
for (const [program, builderProgram] of programs) {
if (!builderProgram) continue;
const buildInfoPath = ts.getTsBuildInfoEmitOutputFilePath(program.getCompilerOptions());
if (!buildInfoPath) continue;
sys.dtsSignaures ??= new Map();
const dtsSignatureData = new Map<string, DtsSignatureData>();
sys.dtsSignaures.set(`${toPathWithSystem(sys, buildInfoPath)}.readable.baseline.txt` as ts.Path, dtsSignatureData);
const state = builderProgram.getState();
state.hasCalledUpdateShapeSignature?.forEach(resolvedPath => {
const file = program.getSourceFileByPath(resolvedPath);
if (!file || file.isDeclarationFile) return;
// Compute dts and exported map and store it
ts.BuilderState.computeDtsSignature(
program,
file,
/*cancellationToken*/ undefined,
sys,
(signature, sourceFiles) => {
const exportedModules = ts.BuilderState.getExportedModules(state.exportedModulesMap && sourceFiles[0].exportedModulesFromDeclarationEmit);
dtsSignatureData.set(relativeToBuildInfo(resolvedPath), { signature, exportedModules: exportedModules && ts.arrayFrom(exportedModules.keys(), relativeToBuildInfo) });
},
);
});
function relativeToBuildInfo(path: string) {
const currentDirectory = program.getCurrentDirectory();
const getCanonicalFileName = ts.createGetCanonicalFileName(program.useCaseSensitiveFileNames());
const buildInfoDirectory = ts.getDirectoryPath(ts.getNormalizedAbsolutePath(buildInfoPath!, currentDirectory));
return ts.ensurePathIsNonModuleName(ts.getRelativePathFromDirectory(buildInfoDirectory, path, getCanonicalFileName));
}
}
}
export function verifyTscBaseline(sys: () => { baseLine: TscCompileSystem["baseLine"]; }) {
it(`Generates files matching the baseline`, () => {
const { file, text } = sys().baseLine();
Harness.Baseline.runBaseline(file, text);
});
}
export interface VerifyTscCompileLike {
scenario: string;
subScenario: string;
commandLineArgs: readonly string[];
fs: () => vfs.FileSystem;
}
/**
* Verify by baselining after initializing FS and custom compile
*/
export function verifyTscCompileLike<T extends VerifyTscCompileLike>(verifier: (input: T) => { baseLine: TscCompileSystem["baseLine"]; }, input: T) {
describe(`tsc ${input.commandLineArgs.join(" ")} ${input.scenario}:: ${input.subScenario}`, () => {
describe(input.scenario, () => {
describe(input.subScenario, () => {
verifyTscBaseline(() => verifier({
...input,
fs: () => input.fs().makeReadonly()
}));
});
});
});
}
interface VerifyTscEditDiscrepanciesInput {
index: number;
edits: readonly TestTscEdit[];
scenario: TestTscCompile["scenario"];
baselines: string[] | undefined;
commandLineArgs: TestTscCompile["commandLineArgs"];
modifyFs: TestTscCompile["modifyFs"];
baseFs: vfs.FileSystem;
newSys: TscCompileSystem;
environmentVariables: TestTscCompile["environmentVariables"];
}
function verifyTscEditDiscrepancies({
index, edits, scenario, commandLineArgs, environmentVariables,
baselines,
modifyFs, baseFs, newSys
}: VerifyTscEditDiscrepanciesInput): string[] | undefined {
const { caption, discrepancyExplanation } = edits[index];
const sys = testTscCompile({
scenario,
subScenario: caption,
fs: () => baseFs.makeReadonly(),
commandLineArgs: edits[index].commandLineArgs || commandLineArgs,
modifyFs: fs => {
if (modifyFs) modifyFs(fs);
for (let i = 0; i <= index; i++) {
edits[i].edit(fs);
}
},
environmentVariables,
computeDtsSignatures: true,
});
let headerAdded = false;
for (const outputFile of sys.writtenFiles.keys()) {
const cleanBuildText = sys.readFile(outputFile);
const incrementalBuildText = newSys.readFile(outputFile);
if (ts.isBuildInfoFile(outputFile)) {
// Check only presence and absence and not text as we will do that for readable baseline
if (!sys.fileExists(`${outputFile}.readable.baseline.txt`)) addBaseline(`Readable baseline not present in clean build:: File:: ${outputFile}`);
if (!newSys.fileExists(`${outputFile}.readable.baseline.txt`)) addBaseline(`Readable baseline not present in incremental build:: File:: ${outputFile}`);
verifyPresenceAbsence(incrementalBuildText, cleanBuildText, `Incremental and clean tsbuildinfo file presence differs:: File:: ${outputFile}`);
}
else if (!ts.fileExtensionIs(outputFile, ".tsbuildinfo.readable.baseline.txt")) {
verifyTextEqual(incrementalBuildText, cleanBuildText, `File: ${outputFile}`);
}
else if (incrementalBuildText !== cleanBuildText) {
// Verify build info without affectedFilesPendingEmit
const { buildInfo: incrementalBuildInfo, readableBuildInfo: incrementalReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(incrementalBuildText);
const { buildInfo: cleanBuildInfo, readableBuildInfo: cleanReadableBuildInfo } = getBuildInfoForIncrementalCorrectnessCheck(cleanBuildText);
const dtsSignaures = sys.dtsSignaures?.get(outputFile);
verifyTextEqual(incrementalBuildInfo, cleanBuildInfo, `TsBuild info text without affectedFilesPendingEmit:: ${outputFile}::`);
// Verify file info sigantures
verifyMapLike(
incrementalReadableBuildInfo?.program?.fileInfos as ReadableProgramMultiFileEmitBuildInfo["fileInfos"],
cleanReadableBuildInfo?.program?.fileInfos as ReadableProgramMultiFileEmitBuildInfo["fileInfos"],
(key, incrementalFileInfo, cleanFileInfo) => {
const dtsForKey = dtsSignaures?.get(key);
if (!incrementalFileInfo || !cleanFileInfo || incrementalFileInfo.signature !== cleanFileInfo.signature && (!dtsForKey || incrementalFileInfo.signature !== dtsForKey.signature)) {
return [
`Incremental signature is neither dts signature nor file version for File:: ${key}`,
`Incremental:: ${JSON.stringify(incrementalFileInfo, /*replacer*/ undefined, 2)}`,
`Clean:: ${JSON.stringify(cleanFileInfo, /*replacer*/ undefined, 2)}`,
`Dts Signature:: $${JSON.stringify(dtsForKey?.signature)}`
];
}
},
`FileInfos:: File:: ${outputFile}`
);
if (!isReadableProgramBundleEmitBuildInfo(incrementalReadableBuildInfo?.program)) {
ts.Debug.assert(!isReadableProgramBundleEmitBuildInfo(cleanReadableBuildInfo?.program));
// Verify exportedModulesMap
verifyMapLike(
incrementalReadableBuildInfo?.program?.exportedModulesMap,
cleanReadableBuildInfo?.program?.exportedModulesMap,
(key, incrementalReferenceSet, cleanReferenceSet) => {
const dtsForKey = dtsSignaures?.get(key);
if (!ts.arrayIsEqualTo(incrementalReferenceSet, cleanReferenceSet) &&
(!dtsForKey || !ts.arrayIsEqualTo(incrementalReferenceSet, dtsForKey.exportedModules))) {
return [
`Incremental Reference set is neither from dts nor files reference map for File:: ${key}::`,
`Incremental:: ${JSON.stringify(incrementalReferenceSet, /*replacer*/ undefined, 2)}`,
`Clean:: ${JSON.stringify(cleanReferenceSet, /*replacer*/ undefined, 2)}`,
`DtsExportsMap:: ${JSON.stringify(dtsForKey?.exportedModules, /*replacer*/ undefined, 2)}`
];
}
},
`exportedModulesMap:: File:: ${outputFile}`
);
// Verify that incrementally pending affected file emit are in clean build since clean build can contain more files compared to incremental depending of noEmitOnError option
if (incrementalReadableBuildInfo?.program?.affectedFilesPendingEmit) {
if (cleanReadableBuildInfo?.program?.affectedFilesPendingEmit === undefined) {
addBaseline(
`Incremental build contains affectedFilesPendingEmit, clean build does not have it: ${outputFile}::`,
`Incremental buildInfoText:: ${incrementalBuildText}`,
`Clean buildInfoText:: ${cleanBuildText}`
);
}
let expectedIndex = 0;
incrementalReadableBuildInfo.program.affectedFilesPendingEmit.forEach(([actualFileOrArray]) => {
const actualFile = ts.isString(actualFileOrArray) ? actualFileOrArray : actualFileOrArray[0];
expectedIndex = ts.findIndex(
(cleanReadableBuildInfo!.program! as ReadableProgramMultiFileEmitBuildInfo).affectedFilesPendingEmit,
([expectedFileOrArray]) => actualFile === (ts.isString(expectedFileOrArray) ? expectedFileOrArray : expectedFileOrArray[0]),
expectedIndex
);
if (expectedIndex === -1) {
addBaseline(
`Incremental build contains ${actualFile} file as pending emit, clean build does not have it: ${outputFile}::`,
`Incremental buildInfoText:: ${incrementalBuildText}`,
`Clean buildInfoText:: ${cleanBuildText}`
);
}
expectedIndex++;
});
}
}
}
}
if (!headerAdded && discrepancyExplanation) addBaseline("*** Supplied discrepancy explanation but didnt file any difference");
return baselines;
function verifyTextEqual(incrementalText: string | undefined, cleanText: string | undefined, message: string) {
if (incrementalText !== cleanText) writeNotEqual(incrementalText, cleanText, message);
}
function verifyMapLike<T>(
incremental: ts.MapLike<T> | undefined,
clean: ts.MapLike<T> | undefined,
verifyValue: (key: string, incrementalValue: T | undefined, cleanValue: T | undefined) => string[] | undefined,
message: string,
) {
verifyPresenceAbsence(incremental, clean, `Incremental and clean do not match:: ${message}`);
if (!incremental || !clean) return;
const incrementalMap = new Map(Object.entries(incremental));
const cleanMap = new Map(Object.entries(clean));
cleanMap.forEach((cleanValue, key) => {
const result = verifyValue(key, incrementalMap.get(key), cleanValue);
if (result) addBaseline(...result);
});
incrementalMap.forEach((incremetnalValue, key) => {
if (cleanMap.has(key)) return;
// This is value only in incremental Map
const result = verifyValue(key, incremetnalValue, /*cleanValue*/ undefined);
if (result) addBaseline(...result);
});
}
function verifyPresenceAbsence<T>(actual: T | undefined, expected: T | undefined, message: string) {
if (expected === undefined) {
if (actual === undefined) return;
}
else {
if (actual !== undefined) return;
}
writeNotEqual(actual, expected, message);
}
function writeNotEqual<T>(actual: T | undefined, expected: T | undefined, message: string) {
addBaseline(
message,
"CleanBuild:",
ts.isString(expected) ? expected : JSON.stringify(expected),
"IncrementalBuild:",
ts.isString(actual) ? actual : JSON.stringify(actual),
);
}
function addBaseline(...text: string[]) {
if (!baselines || !headerAdded) {
(baselines ||= []).push(`${index}:: ${caption}`, ...(discrepancyExplanation?.()|| ["*** Needs explanation"]));
headerAdded = true;
}
baselines.push(...text);
}
}
function getBuildInfoForIncrementalCorrectnessCheck(text: string | undefined): {
buildInfo: string | undefined;
readableBuildInfo?: ReadableBuildInfo;
} {
if (!text) return { buildInfo: text };
const readableBuildInfo = JSON.parse(text) as ReadableBuildInfo;
let sanitizedFileInfos: ts.MapLike<string | Omit<ReadableProgramBuildInfoFileInfo<ts.ProgramMultiFileEmitBuildInfoFileInfo> | ReadableProgramBuildInfoFileInfo<ts.BuilderState.FileInfo>, "signature" | "original"> & { signature: undefined; original: undefined; }> | undefined;
if (readableBuildInfo.program?.fileInfos) {
sanitizedFileInfos = {};
for (const id in readableBuildInfo.program.fileInfos) {
if (ts.hasProperty(readableBuildInfo.program.fileInfos, id)) {
const info = readableBuildInfo.program.fileInfos[id];
sanitizedFileInfos[id] = ts.isString(info) ? info : { ...info, signature: undefined, original: undefined };
}
}
}
return {
buildInfo: JSON.stringify({
...readableBuildInfo,
program: readableBuildInfo.program && {
...readableBuildInfo.program,
fileNames: undefined,
fileNamesList: undefined,
fileInfos: sanitizedFileInfos,
// Ignore noEmit since that shouldnt be reason to emit the tsbuild info and presence of it in the buildinfo file does not matter
options: { ...readableBuildInfo.program.options, noEmit: undefined },
exportedModulesMap: undefined,
affectedFilesPendingEmit: undefined,
latestChangedDtsFile: readableBuildInfo.program.latestChangedDtsFile ? "FakeFileName" : undefined,
},
size: undefined, // Size doesnt need to be equal
}, /*replacer*/ undefined, 2),
readableBuildInfo,
};
}
export interface TestTscEdit {
edit: (fs: vfs.FileSystem) => void;
caption: string;
commandLineArgs?: readonly string[];
/** An array of lines to be printed in order when a discrepancy is detected */
discrepancyExplanation?: () => readonly string[];
}
export interface VerifyTscWithEditsInput extends TestTscCompile {
edits?: readonly TestTscEdit[];
}
/**
* Verify non watch tsc invokcation after each edit
*/
export function verifyTsc({
subScenario, fs, scenario, commandLineArgs, environmentVariables,
baselineSourceMap, modifyFs, baselineReadFileCalls, baselinePrograms,
edits
}: VerifyTscWithEditsInput) {
describe(`tsc ${commandLineArgs.join(" ")} ${scenario}:: ${subScenario}`, () => {
let sys: TscCompileSystem;
let baseFs: vfs.FileSystem;
let editsSys: TscCompileSystem[] | undefined;
before(() => {
baseFs = fs().makeReadonly();
sys = testTscCompile({
scenario,
subScenario,
fs: () => baseFs,
commandLineArgs,
modifyFs,
baselineSourceMap,
baselineReadFileCalls,
baselinePrograms,
environmentVariables,
});
edits?.forEach((
{ edit, caption, commandLineArgs: editCommandLineArgs },
index
) => {
(editsSys || (editsSys = [])).push(testTscCompile({
scenario,
subScenario: caption,
diffWithInitial: true,
fs: () => index === 0 ? sys.vfs : editsSys![index - 1].vfs,
commandLineArgs: editCommandLineArgs || commandLineArgs,
modifyFs: edit,
baselineSourceMap,
baselineReadFileCalls,
baselinePrograms,
environmentVariables,
}));
});
});
after(() => {
baseFs = undefined!;
sys = undefined!;
editsSys = undefined!;
});
verifyTscBaseline(() => ({
baseLine: () => {
const { file, text } = sys.baseLine();
const texts: string[] = [text];
editsSys?.forEach((sys, index) => {
const incrementalScenario = edits![index];
texts.push("");
texts.push(`Change:: ${incrementalScenario.caption}`);
texts.push(sys.baseLine().text);
});
return {
file,
text: `currentDirectory:: ${sys.getCurrentDirectory()} useCaseSensitiveFileNames: ${sys.useCaseSensitiveFileNames}\r\n` +
texts.join("\r\n"),
};
}
}));
if (edits?.length) {
it("tsc invocation after edit and clean build correctness", () => {
let baselines: string[] | undefined;
for (let index = 0; index < edits.length; index++) {
baselines = verifyTscEditDiscrepancies({
index,
edits,
scenario,
baselines,
baseFs,
newSys: editsSys![index],
commandLineArgs,
modifyFs,
environmentVariables,
});
}
Harness.Baseline.runBaseline(
tscBaselineName(scenario, subScenario, commandLineArgs, /*isWatch*/ undefined, "-discrepancies"),
baselines ? baselines.join("\r\n") : null // eslint-disable-line no-null/no-null
);
});
}
});
}
@@ -6,19 +6,15 @@ import {
CommandLineCallbacks,
commandLineCallbacks,
CommandLineProgram,
createSolutionBuilderHostForBaseline,
generateSourceMapBaselineFiles,
} from "../tsc/helpers";
tscBaselineName,
} from "./baseline";
import {
changeToHostTrackingWrittenFiles,
createWatchedSystem,
File,
FileOrFolderOrSymLink,
FileOrFolderOrSymLinkMap,
TestServerHost,
TestServerHostCreationParameters,
TestServerHostTrackingWrittenFiles,
} from "../virtualFileSystemWithWatch";
} from "./virtualFileSystemWithWatch";
export const commonFile1: File = {
path: "/a/b/commonFile1.ts",
@@ -249,10 +245,10 @@ export function runWatchBaseline<T extends ts.BuilderProgram = ts.EmitAndSemanti
});
}
}
Baseline.runBaseline(`${ts.isBuild(commandLineArgs) ? "tsbuild" : "tsc"}${isWatch(commandLineArgs) ? "Watch" : ""}/${scenario}/${subScenario.split(" ").join("-")}.js`, baseline.join("\r\n"));
Baseline.runBaseline(tscBaselineName(scenario, subScenario, commandLineArgs, isWatch(commandLineArgs)), baseline.join("\r\n"));
}
function isWatch(commandLineArgs: readonly string[]) {
export function isWatch(commandLineArgs: readonly string[]) {
return ts.forEach(commandLineArgs, arg => {
if (arg.charCodeAt(0) !== ts.CharacterCodes.minus) return false;
const option = arg.slice(arg.charCodeAt(1) === ts.CharacterCodes.minus ? 2 : 1).toLowerCase();
@@ -296,34 +292,3 @@ export function verifyTscWatch(input: VerifyTscWatch) {
}
});
}
export function createSolutionBuilder(system: TestServerHost, rootNames: readonly string[], originalRead?: TestServerHost["readFile"]) {
const host = createSolutionBuilderHostForBaseline(system, /*versionToWrite*/ undefined, originalRead);
return ts.createSolutionBuilder(host, rootNames, {});
}
export function ensureErrorFreeBuild(host: TestServerHost, rootNames: readonly string[]) {
// ts build should succeed
solutionBuildWithBaseline(host, rootNames);
assert.equal(host.getOutput().length, 0, JSON.stringify(host.getOutput(), /*replacer*/ undefined, " "));
}
export function solutionBuildWithBaseline(sys: TestServerHost, solutionRoots: readonly string[], originalRead?: TestServerHost["readFile"]) {
const originalReadFile = sys.readFile;
const originalWrite = sys.write;
const originalWriteFile = sys.writeFile;
ts.Debug.assert(sys.writtenFiles === undefined);
const solutionBuilder = createSolutionBuilder(changeToHostTrackingWrittenFiles(
patchHostForBuildInfoReadWrite(sys)
), solutionRoots, originalRead);
solutionBuilder.build();
sys.readFile = originalReadFile;
sys.write = originalWrite;
sys.writeFile = originalWriteFile;
sys.writtenFiles = undefined;
return sys;
}
export function createSystemWithSolutionBuild(solutionRoots: readonly string[], files: FileOrFolderOrSymLinkMap | readonly FileOrFolderOrSymLink[], params?: TestServerHostCreationParameters) {
return solutionBuildWithBaseline(createWatchedSystem(files, params), solutionRoots);
}
@@ -1,6 +1,7 @@
import * as Harness from "../../_namespaces/Harness";
import * as ts from "../../_namespaces/ts";
import { ensureErrorFreeBuild } from "../tscWatch/helpers";
import { ActionWatchTypingLocations } from "../../_namespaces/ts.server";
import { ensureErrorFreeBuild } from "./solutionBuilder";
import {
changeToHostTrackingWrittenFiles,
createServerHost,
@@ -9,7 +10,7 @@ import {
libFile,
TestServerHost,
TestServerHostTrackingWrittenFiles,
} from "../virtualFileSystemWithWatch";
} from "./virtualFileSystemWithWatch";
export const customTypesMap = {
path: "/typesMap.json" as ts.Path,
@@ -116,9 +117,9 @@ export function createLoggerWritingToConsole(host: TestServerHost): Logger {
function sanitizeLog(s: string) {
return s.replace(/Elapsed::?\s*\d+(?:\.\d+)?ms/g, "Elapsed:: *ms")
.replace(/\"updateGraphDurationMs\"\:\d+(?:\.\d+)?/g, `"updateGraphDurationMs":*`)
.replace(/\"createAutoImportProviderProgramDurationMs\"\:\d+(?:\.\d+)?/g, `"createAutoImportProviderProgramDurationMs":*`)
.replace(`"version":"${ts.version}"`, `"version":"FakeVersion"`)
.replace(/\"updateGraphDurationMs\"\:\s*\d+(?:\.\d+)?/g, `"updateGraphDurationMs": *`)
.replace(/\"createAutoImportProviderProgramDurationMs\"\:\s*\d+(?:\.\d+)?/g, `"createAutoImportProviderProgramDurationMs": *`)
.replace(versionRegExp, `FakeVersion`)
.replace(/getCompletionData: Get current token: \d+(?:\.\d+)?/g, `getCompletionData: Get current token: *`)
.replace(/getCompletionData: Is inside comment: \d+(?:\.\d+)?/g, `getCompletionData: Is inside comment: *`)
.replace(/getCompletionData: Get previous token: \d+(?:\.\d+)?/g, `getCompletionData: Get previous token: *`)
@@ -280,11 +281,12 @@ export class TestTypingsInstallerWorker extends ts.server.typingsInstaller.Typin
this.addPostExecAction("success", requestId, packageNames, cb);
}
sendResponse(response: ts.server.SetTypings | ts.server.InvalidateCachedTypings) {
sendResponse(response: ts.server.SetTypings | ts.server.InvalidateCachedTypings | ts.server.WatchTypingLocations) {
if (this.log.isEnabled()) {
this.log.writeLine(`Sending response:\n ${JSON.stringify(response)}`);
}
this.projectService.updateTypingsForProject(response);
if (response.kind !== ActionWatchTypingLocations) this.projectService.updateTypingsForProject(response);
else this.projectService.watchTypingLocations(response);
}
enqueueInstallTypingsRequest(project: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray<string>) {
@@ -324,7 +326,9 @@ export class TestTypingsInstaller<T extends TestTypingsInstallerWorker = TestTyp
this.projectService = projectService;
}
onProjectClosed = ts.noop;
onProjectClosed(p: ts.server.Project) {
this.installer?.closeProject({ projectName: p.getProjectName(), kind: "closeProject" });
}
enqueueInstallTypingsRequest(project: ts.server.Project, typeAcquisition: ts.TypeAcquisition, unresolvedImports: ts.SortedReadonlyArray<string>) {
if (!this.installer) {
@@ -868,4 +872,4 @@ export function logInferredProjectsOrphanStatus(projectService: ts.server.Projec
export function logConfiguredProjectsHasOpenRefStatus(projectService: ts.server.ProjectService) {
projectService.configuredProjects.forEach(configuredProject => (projectService.logger as Logger).log(`Configured project: ${configuredProject.projectName} hasOpenRef:: ${configuredProject.hasOpenRef()} isClosed: ${configuredProject.isClosed()}`));
}
}
+142
View File
@@ -0,0 +1,142 @@
import * as Harness from "../../_namespaces/Harness";
import { getDirectoryPath } from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import * as vpath from "../../_namespaces/vpath";
import { libContent } from "./contents";
export interface FsOptions {
libContentToAppend?: string;
cwd?: string;
executingFilePath?: string;
}
export type FsOptionsOrLibContentsToAppend = FsOptions | string;
function valueOfFsOptions(options: FsOptionsOrLibContentsToAppend | undefined, key: keyof FsOptions) {
return typeof options === "string" ?
key === "libContentToAppend" ? options : undefined :
options?.[key];
}
/**
* Load project from disk into /src folder
*/
export function loadProjectFromDisk(
root: string,
options?: FsOptionsOrLibContentsToAppend
): vfs.FileSystem {
const resolver = vfs.createResolver(Harness.IO);
return loadProjectFromFiles({
["/src"]: new vfs.Mount(vpath.resolve(Harness.IO.getWorkspaceRoot(), root), resolver)
}, options);
}
/**
* All the files must be in /src
*/
export function loadProjectFromFiles(
files: vfs.FileSet,
options?: FsOptionsOrLibContentsToAppend,
): vfs.FileSystem {
const executingFilePath = valueOfFsOptions(options, "executingFilePath");
const defaultLibLocation = executingFilePath ? getDirectoryPath(executingFilePath) : "/lib";
const fs = new vfs.FileSystem(/*ignoreCase*/ true, {
files,
cwd: valueOfFsOptions(options, "cwd") || "/",
meta: { defaultLibLocation },
});
const libContentToAppend = valueOfFsOptions(options, "libContentToAppend");
fs.mkdirpSync(defaultLibLocation);
fs.writeFileSync(`${defaultLibLocation}/lib.d.ts`, libContentToAppend ? `${libContent}${libContentToAppend}` : libContent);
fs.makeReadonly();
return fs;
}
export function replaceText(fs: vfs.FileSystem, path: string, oldText: string, newText: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
const old = fs.readFileSync(path, "utf-8");
if (old.indexOf(oldText) < 0) {
throw new Error(`Text "${oldText}" does not exist in file ${path}`);
}
const newContent = old.replace(oldText, newText);
fs.writeFileSync(path, newContent, "utf-8");
}
export function prependText(fs: vfs.FileSystem, path: string, additionalContent: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
const old = fs.readFileSync(path, "utf-8");
fs.writeFileSync(path, `${additionalContent}${old}`, "utf-8");
}
export function appendText(fs: vfs.FileSystem, path: string, additionalContent: string) {
if (!fs.statSync(path).isFile()) {
throw new Error(`File ${path} does not exist`);
}
const old = fs.readFileSync(path, "utf-8");
fs.writeFileSync(path, `${old}${additionalContent}`);
}
export function enableStrict(fs: vfs.FileSystem, path: string) {
replaceText(fs, path, `"strict": false`, `"strict": true`);
}
export function addTestPrologue(fs: vfs.FileSystem, path: string, prologue: string) {
prependText(fs, path, `${prologue}
`);
}
export function addShebang(fs: vfs.FileSystem, project: string, file: string) {
prependText(fs, `src/${project}/${file}.ts`, `#!someshebang ${project} ${file}
`);
}
export function restContent(project: string, file: string) {
return `function for${project}${file}Rest() {
const { b, ...rest } = { a: 10, b: 30, yy: 30 };
}`;
}
function nonrestContent(project: string, file: string) {
return `function for${project}${file}Rest() { }`;
}
export function addRest(fs: vfs.FileSystem, project: string, file: string) {
appendText(fs, `src/${project}/${file}.ts`, restContent(project, file));
}
export function removeRest(fs: vfs.FileSystem, project: string, file: string) {
replaceText(fs, `src/${project}/${file}.ts`, restContent(project, file), nonrestContent(project, file));
}
export function addStubFoo(fs: vfs.FileSystem, project: string, file: string) {
appendText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file));
}
export function changeStubToRest(fs: vfs.FileSystem, project: string, file: string) {
replaceText(fs, `src/${project}/${file}.ts`, nonrestContent(project, file), restContent(project, file));
}
export function addSpread(fs: vfs.FileSystem, project: string, file: string) {
const path = `src/${project}/${file}.ts`;
const content = fs.readFileSync(path, "utf8");
fs.writeFileSync(path, `${content}
function ${project}${file}Spread(...b: number[]) { }
const ${project}${file}_ar = [20, 30];
${project}${file}Spread(10, ...${project}${file}_ar);`);
replaceText(fs, `src/${project}/tsconfig.json`, `"strict": false,`, `"strict": false,
"downlevelIteration": true,`);
}
export function getTripleSlashRef(project: string) {
return `/src/${project}/tripleRef.d.ts`;
}
export function addTripleSlashRef(fs: vfs.FileSystem, project: string, file: string) {
fs.writeFileSync(getTripleSlashRef(project), `declare class ${project}${file} { }`);
prependText(fs, `src/${project}/${file}.ts`, `///<reference path="./tripleRef.d.ts"/>
const ${file}Const = new ${project}${file}();
`);
}
@@ -1,4 +1,4 @@
import * as Harness from "../_namespaces/Harness";
import * as Harness from "../../_namespaces/Harness";
import {
clear,
clone,
@@ -31,19 +31,19 @@ import {
isString,
mapDefined,
matchFiles,
ModuleImportResult,
ModuleResolutionHost,
MultiMap,
noop,
patchWriteFileEnsuringDirectory,
Path,
PollingInterval,
RequireResult,
server,
SortedArray,
sys,
toPath,
} from "../_namespaces/ts";
import { timeIncrements } from "../_namespaces/vfs";
} from "../../_namespaces/ts";
import { timeIncrements } from "../../_namespaces/vfs";
export const libFile: File = {
path: "/a/lib/lib.d.ts",
@@ -292,7 +292,8 @@ export class TestServerHost implements server.ServerHost, FormatDiagnosticsHost,
private readonly environmentVariables?: Map<string, string>;
private readonly executingFilePath: string;
private readonly currentDirectory: string;
public require: ((initialPath: string, moduleName: string) => RequireResult) | undefined;
require?: (initialPath: string, moduleName: string) => ModuleImportResult;
importPlugin?: (root: string, moduleName: string) => Promise<ModuleImportResult>;
public storeFilesChangingSignatureDuringEmit = true;
watchFile: HostWatchFile;
private inodeWatching: boolean | undefined;
@@ -16,7 +16,7 @@ import {
createWatchedSystem,
File,
libFile,
} from "./virtualFileSystemWithWatch";
} from "./helpers/virtualFileSystemWithWatch";
describe("unittests:: Reuse program structure:: General", () => {
function baselineCache<T>(baselines: string[], cacheType: string, cache: ts.ModeAwareCache<T> | undefined) {
@@ -549,6 +549,7 @@ describe("unittests:: Reuse program structure:: isProgramUptoDate", () => {
program, newRootFileNames, newOptions,
path => program.getSourceFileByPath(path)!.version, /*fileExists*/ ts.returnFalse,
/*hasInvalidatedResolutions*/ ts.returnFalse,
/*hasInvalidatedLibResolutions*/ ts.returnFalse,
/*hasChangedAutomaticTypeDirectiveNames*/ undefined,
/*getParsedCommandLine*/ ts.returnUndefined,
/*projectReferences*/ undefined
@@ -1,10 +1,10 @@
import * as Harness from "../../_namespaces/Harness";
import * as ts from "../../_namespaces/ts";
import { createProjectService } from "../tsserver/helpers";
import { createProjectService } from "../helpers/tsserver";
import {
createServerHost,
File,
} from "../virtualFileSystemWithWatch";
} from "../helpers/virtualFileSystemWithWatch";
import {
extractTest,
newLineCharacter,
@@ -1,10 +1,10 @@
import * as Harness from "../../../_namespaces/Harness";
import * as ts from "../../../_namespaces/ts";
import { createProjectService } from "../../tsserver/helpers";
import { createProjectService } from "../../helpers/tsserver";
import {
createServerHost,
libFile,
} from "../../virtualFileSystemWithWatch";
} from "../../helpers/virtualFileSystemWithWatch";
interface Range {
pos: number;
@@ -5,7 +5,7 @@ import {
createServerHost,
File,
libFile,
} from "../virtualFileSystemWithWatch";
} from "../helpers/virtualFileSystemWithWatch";
describe("unittests:: services:: languageService", () => {
const files: {[index: string]: string} = {
@@ -1,10 +1,10 @@
import * as Harness from "../../_namespaces/Harness";
import * as ts from "../../_namespaces/ts";
import { createProjectService } from "../tsserver/helpers";
import { createProjectService } from "../helpers/tsserver";
import {
createServerHost,
File,
} from "../virtualFileSystemWithWatch";
} from "../helpers/virtualFileSystemWithWatch";
import { newLineCharacter } from "./extract/helpers";
describe("unittests:: services:: organizeImports", () => {
@@ -130,7 +130,7 @@ var x = 0;`, {
testVerbatimModuleSyntax: true
});
transpilesCorrectly("Generates module output", `var x = 0;`, {
transpilesCorrectly("Generates module output", `var x = 0; export {};`, {
options: { compilerOptions: { module: ts.ModuleKind.AMD } }
});
@@ -139,7 +139,7 @@ var x = 0;`, {
testVerbatimModuleSyntax: true
});
transpilesCorrectly("Sets module name", "var x = 1;", {
transpilesCorrectly("Sets module name", "var x = 1; export {};", {
options: { compilerOptions: { module: ts.ModuleKind.System, newLine: ts.NewLineKind.LineFeed }, moduleName: "NamedModule" }
});
+1
View File
@@ -304,6 +304,7 @@ describe("unittests:: TransformAPI", () => {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.System,
newLine: ts.NewLineKind.CarriageReturnLineFeed,
moduleDetection: ts.ModuleDetectionKind.Force,
}
}).outputText;
@@ -1,5 +1,8 @@
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import {
verifyTsc,
} from "../helpers/tsc";
import {
addRest,
addShebang,
@@ -10,9 +13,8 @@ import {
enableStrict,
loadProjectFromDisk,
removeRest,
replaceText,
verifyTsc,
} from "../tsc/helpers";
replaceText
} from "../helpers/vfs";
describe("unittests:: tsbuild:: outFile:: on amd modules with --out", () => {
let outFileFs: vfs.FileSystem;
+2 -2
View File
@@ -1,7 +1,7 @@
import {
loadProjectFromFiles,
verifyTsc,
} from "../tsc/helpers";
} from "../helpers/tsc";
import { loadProjectFromFiles } from "../helpers/vfs";
describe("unittests:: tsbuild - clean", () => {
verifyTsc({
@@ -1,13 +1,14 @@
import * as ts from "../../_namespaces/ts";
import { compilerOptionsToConfigJson } from "../helpers/contents";
import {
appendText,
compilerOptionsToConfigJson,
loadProjectFromFiles,
noChangeRun,
replaceText,
TestTscEdit,
verifyTsc,
} from "../tsc/helpers";
} from "../helpers/tsc";
import {
appendText,
loadProjectFromFiles, replaceText
} from "../helpers/vfs";
describe("unittests:: tsbuild:: commandLine::", () => {
describe("different options::", () => {
@@ -1,12 +1,13 @@
import { dedent } from "../../_namespaces/Utils";
import {
noChangeRun,
verifyTsc,
} from "../helpers/tsc";
import {
appendText,
loadProjectFromDisk,
loadProjectFromFiles,
noChangeRun,
replaceText,
verifyTsc,
} from "../tsc/helpers";
loadProjectFromFiles, replaceText
} from "../helpers/vfs";
describe("unittests:: tsbuild:: configFileErrors:: when tsconfig extends the missing file", () => {
verifyTsc({
@@ -1,7 +1,7 @@
import {
loadProjectFromFiles,
verifyTsc,
} from "../tsc/helpers";
} from "../helpers/tsc";
import { loadProjectFromFiles } from "../helpers/vfs";
describe("unittests:: tsbuild:: configFileExtends:: when tsconfig extends another config", () => {
function getConfigExtendsWithIncludeFs() {
@@ -1,10 +1,11 @@
import {
noChangeOnlyRuns,
verifyTsc,
} from "../helpers/tsc";
import {
loadProjectFromDisk,
loadProjectFromFiles,
noChangeOnlyRuns,
replaceText,
verifyTsc,
} from "../tsc/helpers";
loadProjectFromFiles, replaceText
} from "../helpers/vfs";
describe("unittests:: tsbuild:: when containerOnly project is referenced", () => {
verifyTsc({
@@ -1,9 +1,9 @@
import * as Utils from "../../_namespaces/Utils";
import * as vfs from "../../_namespaces/vfs";
import {
loadProjectFromFiles,
verifyTsc,
} from "../tsc/helpers";
} from "../helpers/tsc";
import { loadProjectFromFiles } from "../helpers/vfs";
describe("unittests:: tsbuild:: declarationEmit", () => {
function getFiles(): vfs.FileSet {
+5 -3
View File
@@ -1,10 +1,12 @@
import * as vfs from "../../_namespaces/vfs";
import {
verifyTsc,
} from "../helpers/tsc";
import {
loadProjectFromDisk,
prependText,
replaceText,
verifyTsc,
} from "../tsc/helpers";
replaceText
} from "../helpers/vfs";
describe("unittests:: tsbuild:: on demo project", () => {
let projFs: vfs.FileSystem;
@@ -1,9 +1,11 @@
import * as vfs from "../../_namespaces/vfs";
import {
loadProjectFromDisk,
replaceText,
verifyTsc,
} from "../tsc/helpers";
} from "../helpers/tsc";
import {
loadProjectFromDisk,
replaceText
} from "../helpers/vfs";
describe("unittests:: tsbuild:: on project with emitDeclarationOnly set to true", () => {
let projFs: vfs.FileSystem;
@@ -1,8 +1,8 @@
import * as vfs from "../../_namespaces/vfs";
import {
loadProjectFromDisk,
verifyTsc,
} from "../tsc/helpers";
} from "../helpers/tsc";
import { loadProjectFromDisk } from "../helpers/vfs";
describe("unittests:: tsbuild - empty files option in tsconfig", () => {
let projFs: vfs.FileSystem;
@@ -1,7 +1,7 @@
import {
loadProjectFromFiles,
verifyTsc,
} from "../tsc/helpers";
} from "../helpers/tsc";
import { loadProjectFromFiles } from "../helpers/vfs";
// https://github.com/microsoft/TypeScript/issues/33849
describe("unittests:: tsbuild:: exitCodeOnBogusFile:: test exit code", () => {
@@ -2,11 +2,11 @@ import * as ts from "../../_namespaces/ts";
import {
dedent
} from "../../_namespaces/Utils";
import { compilerOptionsToConfigJson } from "../helpers/contents";
import {
compilerOptionsToConfigJson,
loadProjectFromFiles,
verifyTsc,
} from "../tsc/helpers";
} from "../helpers/tsc";
import { loadProjectFromFiles } from "../helpers/vfs";
describe("unittests:: tsbuild:: fileDelete::", () => {
function fs(childOptions: ts.CompilerOptions, mainOptions?: ts.CompilerOptions) {
@@ -1,10 +1,12 @@
import * as vfs from "../../_namespaces/vfs";
import {
verifyTsc,
} from "../helpers/tsc";
import {
appendText,
loadProjectFromDisk,
replaceText,
verifyTsc,
} from "../tsc/helpers";
replaceText
} from "../helpers/vfs";
describe("unittests:: tsbuild:: inferredTypeFromTransitiveModule::", () => {
let projFs: vfs.FileSystem;
@@ -1,10 +1,12 @@
import * as Utils from "../../_namespaces/Utils";
import { symbolLibContent } from "../helpers/contents";
import {
verifyTsc,
} from "../helpers/tsc";
import {
loadProjectFromFiles,
replaceText,
symbolLibContent,
verifyTsc,
} from "../tsc/helpers";
replaceText
} from "../helpers/vfs";
describe("unittests:: tsbuild:: javascriptProjectEmit::", () => {
verifyTsc({
@@ -1,9 +1,11 @@
import {
verifyTsc,
} from "../helpers/tsc";
import {
appendText,
loadProjectFromDisk,
replaceText,
verifyTsc,
} from "../tsc/helpers";
replaceText
} from "../helpers/vfs";
describe("unittests:: tsbuild:: lateBoundSymbol:: interface is merged and contains late bound member", () => {
verifyTsc({
@@ -0,0 +1,16 @@
import { getFsForLibResolution } from "../helpers/libraryResolution";
import { verifyTsc } from "../helpers/tsc";
describe("unittests:: tsbuild:: libraryResolution:: library file resolution", () => {
function verify(libRedirection?: true) {
verifyTsc({
scenario: "libraryResolution",
subScenario: `with config${libRedirection ? " with redirection" : ""}`,
fs: () => getFsForLibResolution(libRedirection),
commandLineArgs: ["-b", "project1", "project2", "project3", "project4", "--verbose", "--explainFiles"],
baselinePrograms: true,
});
}
verify();
verify(/*libRedirection*/ true);
});
@@ -1,15 +1,15 @@
import * as ts from "../../_namespaces/ts";
import * as Utils from "../../_namespaces/Utils";
import {
loadProjectFromFiles,
noChangeOnlyRuns,
verifyTsc,
} from "../tsc/helpers";
import { verifyTscWatch } from "../tscWatch/helpers";
} from "../helpers/tsc";
import { verifyTscWatch } from "../helpers/tscWatch";
import { loadProjectFromFiles } from "../helpers/vfs";
import {
createWatchedSystem,
libFile,
} from "../virtualFileSystemWithWatch";
} from "../helpers/virtualFileSystemWithWatch";
describe("unittests:: tsbuild:: moduleResolution:: handles the modules and options from referenced project correctly", () => {
function sys(optionsToExtend?: ts.CompilerOptions) {
@@ -1,10 +1,10 @@
import * as Utils from "../../_namespaces/Utils";
import { symbolLibContent } from "../helpers/contents";
import {
loadProjectFromFiles,
symbolLibContent,
verifyTsc,
} from "../tsc/helpers";
import { libFile } from "../virtualFileSystemWithWatch";
} from "../helpers/tsc";
import { loadProjectFromFiles } from "../helpers/vfs";
import { libFile } from "../helpers/virtualFileSystemWithWatch";
// https://github.com/microsoft/TypeScript/issues/31696
describe("unittests:: tsbuild:: moduleSpecifiers:: synthesized module specifiers to referenced projects resolve correctly", () => {
+2 -2
View File
@@ -1,8 +1,8 @@
import {
loadProjectFromFiles,
noChangeRun,
verifyTsc,
} from "../tsc/helpers";
} from "../helpers/tsc";
import { loadProjectFromFiles } from "../helpers/vfs";
describe("unittests:: tsbuild:: noEmit", () => {
function verifyNoEmitWorker(subScenario: string, aTsContent: string, commandLineArgs: readonly string[]) {
@@ -1,9 +1,9 @@
import * as vfs from "../../_namespaces/vfs";
import {
loadProjectFromDisk,
noChangeRun,
verifyTsc,
} from "../tsc/helpers";
} from "../helpers/tsc";
import { loadProjectFromDisk } from "../helpers/vfs";
describe("unittests:: tsbuild - with noEmitOnError", () => {
let projFs: vfs.FileSystem;
+13 -13
View File
@@ -1,6 +1,15 @@
import * as fakes from "../../_namespaces/fakes";
import * as ts from "../../_namespaces/ts";
import * as vfs from "../../_namespaces/vfs";
import { createSolutionBuilderHostForBaseline } from "../helpers/solutionBuilder";
import {
noChangeOnlyRuns,
testTscCompileLike,
TestTscEdit,
TscCompileSystem,
verifyTsc,
verifyTscCompileLike,
} from "../helpers/tsc";
import {
addRest,
addShebang,
@@ -9,20 +18,11 @@ import {
addTestPrologue,
addTripleSlashRef,
appendText,
changeStubToRest,
createSolutionBuilderHostForBaseline,
enableStrict,
loadProjectFromDisk,
noChangeOnlyRuns,
prependText,
changeStubToRest, enableStrict,
loadProjectFromDisk, prependText,
removeRest,
replaceText,
testTscCompileLike,
TestTscEdit,
TscCompileSystem,
verifyTsc,
verifyTscCompileLike,
} from "../tsc/helpers";
replaceText
} from "../helpers/vfs";
describe("unittests:: tsbuild:: outFile::", () => {
let outFileFs: vfs.FileSystem;

Some files were not shown because too many files have changed in this diff Show More