From 77bdaa2be789ac472901cd7599f2cb78dfde5b6d Mon Sep 17 00:00:00 2001 From: Titian Cernicova-Dragomir Date: Mon, 13 Mar 2023 11:30:02 +0000 Subject: [PATCH] Added extra restrictions to declaration emit in isolated declaration mode. --- src/compiler/checker.ts | 11 +++- src/compiler/transformers/declarations.ts | 59 ++++++++++++++----- src/compiler/utilities.ts | 21 +++++++ .../typeOnly/importsNotUsedAsValues_error.ts | 2 +- 4 files changed, 76 insertions(+), 17 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2515d81a58f..a9176863300 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -1065,6 +1065,7 @@ import { WideningContext, WithStatement, YieldExpression, + isLiteralExpression, } from "./_namespaces/ts"; import * as moduleSpecifiers from "./_namespaces/ts.moduleSpecifiers"; import * as performance from "./_namespaces/ts.performance"; @@ -2687,6 +2688,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return symbol; } if (symbol.flags & SymbolFlags.Alias) { + // Do not take target symbol meaning into account in isolated declaration mode since we don't have access to info from other files. + if(compilerOptions.isolatedDeclarations) { + return symbol; + } const targetFlags = getAllSymbolFlags(symbol); // `targetFlags` will be `SymbolFlags.All` if an error occurred in alias resolution; this avoids cascading errors if (targetFlags & meaning) { @@ -46203,7 +46208,11 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { function isLiteralConstDeclaration(node: VariableDeclaration | PropertyDeclaration | PropertySignature | ParameterDeclaration): boolean { if (isDeclarationReadonly(node) || isVariableDeclaration(node) && isVarConst(node)) { - return isFreshLiteralType(getTypeOfSymbol(getSymbolOfDeclaration(node))); + // TODO: isolated declarations: Add a test for this. + // In isolated declaration mode we can't really use the freshness of the type as this would require type information. + return compilerOptions.isolatedDeclarations? + !node.type && !!node.initializer && isLiteralExpression(node.initializer): + isFreshLiteralType(getTypeOfSymbol(getSymbolOfDeclaration(node))); } return false; } diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index 0ebdbc2a8e1..5a4f99f9e8c 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -81,6 +81,7 @@ import { hasDynamicName, hasEffectiveModifier, hasExtension, + hasIdentifierComputedName, hasJSDocNodes, HasModifiers, hasSyntacticModifier, @@ -120,6 +121,7 @@ import { isInterfaceDeclaration, isJsonSourceFile, isLateVisibilityPaintedStatement, + isLiteralExpression, isLiteralImportTypeNode, isMappedTypeNode, isMethodDeclaration, @@ -317,14 +319,14 @@ export function transformDeclarations(context: TransformationContext) { const resolver = context.getEmitResolver(); const options = context.getCompilerOptions(); const { noResolve, stripInternal } = options; - const isolatedDeclarations = false; + const isolatedDeclarations = options.isolatedDeclarations; return transformRoot; - function reportIsolatedDeclarationError(_node: Node) { - // context.addDiagnostic(createDiagnosticForNode( - // node, - // Diagnostics.Declaration_emit_for_this_file_requires_type_resolution_An_explicit_type_annotation_may_unblock_declaration_emit - // )); + function reportIsolatedDeclarationError(node: Node) { + context.addDiagnostic(createDiagnosticForNode( + node, + Diagnostics.Declaration_emit_for_this_file_requires_type_resolution_An_explicit_type_annotation_may_unblock_declaration_emit + )); } function recordTypeReferenceDirectivesIfNecessary(typeReferenceDirectives: readonly [specifier: string, mode: ResolutionMode][] | undefined): void { if (!typeReferenceDirectives) { @@ -584,7 +586,8 @@ export function transformDeclarations(context: TransformationContext) { combinedStatements = setTextRange(factory.createNodeArray([...combinedStatements, createEmptyExports(factory)]), combinedStatements); } } - const updated = factory.updateSourceFile(node, combinedStatements, /*isDeclarationFile*/ true, references, getFileReferencesForUsedTypeReferences(), node.hasNoDefaultLib, getLibReferences()); + const typeReferences = isolatedDeclarations? node.typeReferenceDirectives: getFileReferencesForUsedTypeReferences(); + const updated = factory.updateSourceFile(node, combinedStatements, /*isDeclarationFile*/ true, references, typeReferences, node.hasNoDefaultLib, getLibReferences()); updated.exportedModulesFromDeclarationEmit = exportedModulesFromDeclarationEmit; return updated; @@ -704,7 +707,9 @@ export function transformDeclarations(context: TransformationContext) { if (elem.kind === SyntaxKind.OmittedExpression) { return elem; } - if (elem.propertyName && isIdentifier(elem.propertyName) && isIdentifier(elem.name) && !elem.symbol.isReferenced && !isIdentifierANonContextualKeyword(elem.propertyName)) { + if (elem.propertyName && isIdentifier(elem.propertyName) && isIdentifier(elem.name) + // TODO: isolated declarations: find a better way for this since we don't actually do signature usage analysis + && !isolatedDeclarations&& !elem.symbol.isReferenced && !isIdentifierANonContextualKeyword(elem.propertyName)) { // Unnecessary property renaming is forbidden in types, so remove renaming return factory.updateBindingElement( elem, @@ -746,12 +751,14 @@ export function transformDeclarations(context: TransformationContext) { } function shouldPrintWithInitializer(node: Node) { - if (isolatedDeclarations) return false; return canHaveLiteralInitializer(node) && resolver.isLiteralConstDeclaration(getParseTreeNode(node) as CanHaveLiteralInitializer); // TODO: Make safe } function ensureNoInitializer(node: CanHaveLiteralInitializer) { if (shouldPrintWithInitializer(node)) { + if(node.initializer && isLiteralExpression(node.initializer)) { + return node.initializer; + } return resolver.createLiteralConstValue(getParseTreeNode(node) as CanHaveLiteralInitializer, symbolTracker); // TODO: Make safe } return undefined; @@ -898,7 +905,10 @@ export function transformDeclarations(context: TransformationContext) { if (!isPrivate) { const valueParameter = getSetAccessorValueParameter(input); if (valueParameter) { - const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); + const accessorType = + isolatedDeclarations ? + undefined: + getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); newValueParameter = ensureParameter(valueParameter, /*modifierMask*/ undefined, accessorType); } } @@ -1034,6 +1044,12 @@ export function transformDeclarations(context: TransformationContext) { } // Augmentation of export depends on import if (resolver.isImportRequiredByAugmentation(decl)) { + if(isolatedDeclarations) { + // TODO: Should report better error here. Suggest we add the syntax import type '....' + // Also add a test for this. + reportIsolatedDeclarationError(decl) + return undefined; + } return factory.updateImportDeclaration( decl, decl.modifiers, @@ -1114,7 +1130,11 @@ export function transformDeclarations(context: TransformationContext) { if (isDeclaration(input)) { if (isDeclarationAndNotVisible(input)) return; if (hasDynamicName(input) && !resolver.isLateBound(getParseTreeNode(input) as Declaration)) { - return; + if (hasIdentifierComputedName(input)) { + reportIsolatedDeclarationError(input); + }else { + return; + } } } @@ -1208,7 +1228,10 @@ export function transformDeclarations(context: TransformationContext) { if (isPrivateIdentifier(input.name)) { return cleanup(/*returnValue*/ undefined); } - const accessorType = getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); + const accessorType = + isolatedDeclarations ? + input.type: + getTypeAnnotationFromAllAccessorDeclarations(input, resolver.getAllAccessorDeclarations(input)); return cleanup(factory.updateGetAccessorDeclaration( input, ensureModifiers(input), @@ -1406,7 +1429,7 @@ export function transformDeclarations(context: TransformationContext) { reportIsolatedDeclarationError(input); } const type = isolatedDeclarations ? - factory.createTypeReferenceNode("invalud") : + factory.createTypeReferenceNode("invalid") : resolver.createTypeOfExpression(input.expression, input, declarationEmitNodeBuilderFlags, symbolTracker) const varDecl = factory.createVariableDeclaration(newId, /*exclamationToken*/ undefined, type, /*initializer*/ undefined); errorFallbackNode = undefined; @@ -1502,7 +1525,12 @@ export function transformDeclarations(context: TransformationContext) { ensureType(input, input.type), /*body*/ undefined )); - if (clean && resolver.isExpandoFunctionDeclaration(input) && shouldEmitFunctionProperties(input)) { + const isExpandoFunctionDeclaration = clean && resolver.isExpandoFunctionDeclaration(input); + if (isExpandoFunctionDeclaration && shouldEmitFunctionProperties(input)) { + if(isExpandoFunctionDeclaration && isolatedDeclarations) { + reportIsolatedDeclarationError(input); + return clean; + } const props = resolver.getPropertiesOfContainerFunction(input); // Use parseNodeFactory so it is usable as an enclosing declaration const fakespace = parseNodeFactory.createModuleDeclaration(/*modifiers*/ undefined, clean.name || factory.createIdentifier("_default"), factory.createModuleBlock([]), NodeFlags.Namespace); @@ -1830,8 +1858,9 @@ export function transformDeclarations(context: TransformationContext) { getSymbolAccessibilityDiagnostic = createGetSymbolAccessibilityDiagnosticForNodeName(node); } errorNameNode = (node as NamedDeclaration).name; - Debug.assert(resolver.isLateBound(getParseTreeNode(node) as Declaration)); // Should only be called with dynamic names const decl = node as NamedDeclaration as LateBoundDeclaration; + + Debug.assert((hasIdentifierComputedName(decl) && options.isolatedDeclarations) || resolver.isLateBound(getParseTreeNode(node) as Declaration)); // Should only be called with dynamic names const entityName = decl.name.expression; checkEntityNameVisibility(entityName, enclosingDeclaration); if (!suppressNewDiagnosticContexts) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 159aedabbf2..d8673c9c6f9 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -4897,6 +4897,27 @@ export function isDynamicName(name: DeclarationName): boolean { !isSignedNumericLiteral(expr); } +/** + * + * @internal + */ +export function hasIdentifierComputedName(declaration: Declaration): declaration is DynamicNamedDeclaration | DynamicNamedBinaryExpression { + const name = getNameOfDeclaration(declaration); + return !!name && isIdentifierComputedName(name); +} +/** @internal */ +export function isIdentifierComputedName(name: DeclarationName): boolean { + if (!(name.kind === SyntaxKind.ComputedPropertyName || name.kind === SyntaxKind.ElementAccessExpression)) { + return false; + } + let expr = isElementAccessExpression(name) ? skipParentheses(name.argumentExpression) : name.expression; + while(isPropertyAccessExpression(expr)) { + expr = expr.expression; + } + return isIdentifier(expr); +} + + /** @internal */ export function getPropertyNameForPropertyNameNode(name: PropertyName): __String | undefined { switch (name.kind) { diff --git a/tests/cases/conformance/externalModules/typeOnly/importsNotUsedAsValues_error.ts b/tests/cases/conformance/externalModules/typeOnly/importsNotUsedAsValues_error.ts index ae697e7c3cb..6440828b735 100644 --- a/tests/cases/conformance/externalModules/typeOnly/importsNotUsedAsValues_error.ts +++ b/tests/cases/conformance/externalModules/typeOnly/importsNotUsedAsValues_error.ts @@ -62,5 +62,5 @@ export = K; import K = require('./k'); K.One; -// @Filename: /j.ts +// @Filename: /m.ts // Sad face https://github.com/microsoft/TypeScript/blob/6b04f5039429b9d412696fe2febe39ecc69ad365/src/testRunner/compilerRunner.ts#L207 \ No newline at end of file