Skip to content

Commit

Permalink
Merge pull request #120 from bloomberg/isolated-declarations-expando-…
Browse files Browse the repository at this point in the history
…functions

Isolated declarations expando functions
  • Loading branch information
dragomirtitian authored Nov 29, 2023
2 parents 0f12363 + 26e9574 commit 7ab0f0d
Show file tree
Hide file tree
Showing 45 changed files with 3,242 additions and 485 deletions.
20 changes: 15 additions & 5 deletions src/compiler/transformers/declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import {
isAnyImportSyntax,
isArray,
isArrayBindingElement,
isBinaryExpression,
isBindingElement,
isBindingPattern,
isClassDeclaration,
Expand Down Expand Up @@ -1702,14 +1703,23 @@ export function transformDeclarations(context: TransformationContext) {
/*body*/ undefined,
));
if (clean && resolver.isExpandoFunction(input) && shouldEmitFunctionProperties(input)) {
const props = resolver.getPropertiesOfContainerFunction(input);

if (isolatedDeclarations) {
context.addDiagnostic(createDiagnosticForNode(
input,
Diagnostics.Assigning_properties_to_functions_without_declaring_them_is_not_supported_with_isolatedDeclarations_Add_an_explicit_declaration_for_the_properties_assigned_to_this_function,
));
props.forEach(p => {
if (isExpandoPropertyDeclaration(p.valueDeclaration)) {
const errorTarget = isBinaryExpression(p.valueDeclaration) ?
p.valueDeclaration.left :
p.valueDeclaration;

context.addDiagnostic(createDiagnosticForNode(
errorTarget,
Diagnostics.Assigning_properties_to_functions_without_declaring_them_is_not_supported_with_isolatedDeclarations_Add_an_explicit_declaration_for_the_properties_assigned_to_this_function,
));
}
});
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);
setParent(fakespace, enclosingDeclaration as SourceFile | NamespaceDeclaration);
Expand Down
476 changes: 326 additions & 150 deletions src/compiler/transformers/declarations/emitBinder.ts

Large diffs are not rendered by default.

137 changes: 8 additions & 129 deletions src/compiler/transformers/declarations/emitResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
factory,
filter,
findAncestor,
forEachEntry,
FunctionDeclaration,
FunctionLikeDeclaration,
getAnyImportSyntax,
Expand All @@ -26,17 +27,15 @@ import {
hasDynamicName,
hasProperty,
hasSyntacticModifier,
Identifier,
isAccessor,
isBigIntLiteral,
isBinaryExpression,
isBindingElement,
isDeclarationReadonly,
isElementAccessExpression,
isEntityNameExpression,
isEnumDeclaration,
isEnumMember,
isExpressionStatement,
isExpandoPropertyDeclaration,
isFunctionDeclaration,
isFunctionExpressionOrArrowFunction,
isFunctionLike,
Expand All @@ -50,7 +49,6 @@ import {
isPartOfTypeNode,
isPrefixUnaryExpression,
isPropertyAccessExpression,
isPropertyName,
isSetAccessor,
isSetAccessorDeclaration,
isStringLiteralLike,
Expand All @@ -63,7 +61,6 @@ import {
LateVisibilityPaintedStatement,
ModifierFlags,
Node,
NodeArray,
NodeFlags,
nodeIsPresent,
NoSubstitutionTemplateLiteral,
Expand All @@ -75,7 +72,6 @@ import {
PropertySignature,
skipParentheses,
SourceFile,
Statement,
SymbolAccessibility,
SymbolFlags,
SymbolVisibilityResult,
Expand All @@ -90,21 +86,11 @@ import {
} from "./emitBinder";
import {
IsolatedEmitResolver,
MemberKey,
} from "./types";

const knownFunctionMembers = new Set([
"I:apply",
"I:call",
"I:bind",
"I:toString",
"I:prototype",
"I:length",
]);

/** @internal */
export function createEmitDeclarationResolver(file: SourceFile): IsolatedEmitResolver {
const { getNodeLinks, resolveMemberKey, resolveName } = bindSourceFileForDeclarationEmit(file);
const { getNodeLinks, resolveMemberKey, resolveName, resolveEntityName } = bindSourceFileForDeclarationEmit(file);

function getEnumValueFromName(name: PropertyName | NoSubstitutionTemplateLiteral, location: EnumDeclaration) {
const enumKey = getMemberKey(name);
Expand All @@ -117,27 +103,6 @@ export function createEmitDeclarationResolver(file: SourceFile): IsolatedEmitRes
return undefined;
}

function resolveEntityName(location: Node, node: Expression, meaning: SymbolFlags): EmitDeclarationSymbol | undefined {
if (isIdentifier(node)) {
return resolveName(location, node.escapedText, meaning);
}
else if (isPropertyAccessExpression(node) || isElementAccessExpression(node)) {
const symbol = resolveEntityName(location, node.expression, meaning);
if (symbol === undefined) return undefined;

const name = isElementAccessExpression(node) ? node.argumentExpression : node.name;
if (!isPropertyName(name)) return;

const memberSymbol = symbol.exports?.get(getMemberKey(name));
if (!memberSymbol || !(memberSymbol.flags & meaning)) {
return undefined;
}
return memberSymbol;
}
else {
return undefined;
}
}
function isExpressionMemberOfEnum(target: Expression, location: EnumDeclaration) {
const symbol = resolveEntityName(location, target, SymbolFlags.Namespace);

Expand Down Expand Up @@ -279,66 +244,14 @@ export function createEmitDeclarationResolver(file: SourceFile): IsolatedEmitRes
}
return isIdentifier(expr);
}
function getExpandoMembers(declaration: FunctionDeclaration | VariableDeclaration, functionName: Identifier) {
const scope = getParentScope(declaration);
if (!scope) return undefined;
const members = new Set<MemberKey>();
for (const statement of scope.statements) {
// Looking for name functionName.member = init;
if (!isExpressionStatement(statement)) continue;
if (!isBinaryExpression(statement.expression)) continue;
const assignment = statement.expression;
if (assignment.operatorToken.kind !== SyntaxKind.EqualsToken) continue;

const isPropertyAccess = isPropertyAccessExpression(assignment.left);
if (
(isPropertyAccess || isElementAccessExpression(assignment.left))
&& isIdentifier(assignment.left.expression)
&& assignment.left.expression.escapedText === functionName.escapedText
) {
let name;
if (isPropertyAccess) {
name = assignment.left.name;
}
else {
const argumentExpression = assignment.left.argumentExpression;
name = factory.createComputedPropertyName(argumentExpression);
}
const key = getMemberKey(name);
if (!key || knownFunctionMembers.has(key)) {
continue;
}
members.add(key);
}
}
return members;
}

function getDefinedMembers(declaration: FunctionDeclaration | VariableDeclaration, functionName: Identifier) {
const scope = getParentScope(declaration);
if (!scope) return undefined;
const cls = resolveName(scope, functionName.escapedText, SymbolFlags.Class);

const namespace = resolveName(scope, functionName.escapedText, SymbolFlags.Namespace);

if (!cls?.exports) {
return namespace?.exports;
}
if (!namespace?.exports) {
return cls.exports;
}
return new Set([
...(namespace.exports.keys() ?? []),
...(cls.exports.keys() ?? []),
]);
}

// Do a best effort to find expando functions
function isExpandoFunction(node: FunctionDeclaration | VariableDeclaration) {
const declaration = getParseTreeNode(node, (n): n is FunctionDeclaration | VariableDeclaration => isFunctionDeclaration(n) || isVariableDeclaration(n));
if (!declaration) {
return false;
}
const symbol = declaration.symbol;
if (isVariableDeclaration(declaration)) {
if (declaration.type || !isVarConst(declaration)) {
return false;
Expand All @@ -347,28 +260,7 @@ export function createEmitDeclarationResolver(file: SourceFile): IsolatedEmitRes
return false;
}
}

const functionName = node.name;
if (!functionName || !isIdentifier(functionName)) return false;

const expandoMembers = getExpandoMembers(declaration, functionName);
if (!expandoMembers || expandoMembers.size === 0) return false;

if (isVariableDeclaration(declaration) && expandoMembers.size) {
return true;
}
const definedMembers = getDefinedMembers(declaration, functionName);
// No namespace definitions, and it has assignments => is Expando function
if (!definedMembers) {
return true;
}

// Some assigned members are not in the defined the namespaces
// computed members are ignored, since they don't name it to declarations anyway
if ([...expandoMembers.keys()].some(n => !definedMembers.has(n))) {
return true;
}
return false;
return !!symbol.exports && !!forEachEntry(symbol.exports, p => p.flags & SymbolFlags.Value && isExpandoPropertyDeclaration(p.valueDeclaration));
}

return {
Expand All @@ -378,6 +270,9 @@ export function createEmitDeclarationResolver(file: SourceFile): IsolatedEmitRes
tryFindAmbientModule() {
return undefined;
},
getPropertiesOfContainerFunction(node: FunctionDeclaration | VariableDeclaration) {
return [...node.symbol.exports?.values() ?? []];
},
getAllAccessorDeclarations(declaration) {
const parentLinks = getNodeLinks(declaration.parent);
const key = getMemberKey(declaration.name);
Expand Down Expand Up @@ -658,19 +553,3 @@ export function createEmitDeclarationResolver(file: SourceFile): IsolatedEmitRes
};
}
}

function getParentScope(declaration: VariableDeclaration | FunctionDeclaration):
| undefined
| Node & {
statements: NodeArray<Statement>;
}
{
let scope: Node = declaration;
while (scope) {
if (hasProperty(scope, "statements")) {
return (scope as any);
}
scope = scope.parent;
}
return undefined;
}
1 change: 1 addition & 0 deletions src/compiler/transformers/declarations/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,5 @@ export interface IsolatedEmitResolver {
getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): string | number | undefined;
getAllAccessorDeclarations(declaration: AccessorDeclaration): AllAccessorDeclarations;
tryFindAmbientModule(moduleReferenceExpression: Expression): Symbol | undefined;
getPropertiesOfContainerFunction(node: FunctionDeclaration | VariableDeclaration): Symbol[]
}
117 changes: 117 additions & 0 deletions tests/baselines/reference/expandoFunctionNestedAssigments.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//// [tests/cases/compiler/expandoFunctionNestedAssigments.ts] ////

//// [expandoFunctionNestedAssigments.ts]
function Foo(): void {

}

(Foo.bla = { foo: 1}).foo = (Foo.baz = 1) + (Foo.bar = 0);

if(Foo.fromIf = 1) {
Foo.inIf = 1;
}

while(Foo.fromWhileCondition = 1) {
Foo.fromWhileBody = 1;
{
Foo.fromWhileBodyNested = 1;
}
}

do {
Foo.fromDoBody = 1;
{
Foo.fromDoBodyNested = 1;
}
} while(Foo.fromDoCondition = 1);

for(Foo.forInit = 1; (Foo.forCond = 1) > 1; Foo.forIncr = 1){
Foo.fromForBody = 1;
{
Foo.fromForBodyNested = 1;
}
}

for(let f of (Foo.forOf = []) ){
Foo.fromForOfBody = 1;
{
Foo.fromForOfBodyNested = 1;
}
}


for(let f in (Foo.forIn = []) ){
Foo.fromForInBody = 1;
{
Foo.fromForInBodyNested = 1;
}
}

//// [expandoFunctionNestedAssigments.js]
function Foo() {
}
(Foo.bla = { foo: 1 }).foo = (Foo.baz = 1) + (Foo.bar = 0);
if (Foo.fromIf = 1) {
Foo.inIf = 1;
}
while (Foo.fromWhileCondition = 1) {
Foo.fromWhileBody = 1;
{
Foo.fromWhileBodyNested = 1;
}
}
do {
Foo.fromDoBody = 1;
{
Foo.fromDoBodyNested = 1;
}
} while (Foo.fromDoCondition = 1);
for (Foo.forInit = 1; (Foo.forCond = 1) > 1; Foo.forIncr = 1) {
Foo.fromForBody = 1;
{
Foo.fromForBodyNested = 1;
}
}
for (var _i = 0, _a = (Foo.forOf = []); _i < _a.length; _i++) {
var f = _a[_i];
Foo.fromForOfBody = 1;
{
Foo.fromForOfBodyNested = 1;
}
}
for (var f in (Foo.forIn = [])) {
Foo.fromForInBody = 1;
{
Foo.fromForInBodyNested = 1;
}
}


//// [expandoFunctionNestedAssigments.d.ts]
declare function Foo(): void;
declare namespace Foo {
var bla: {
foo: number;
};
var baz: number;
var bar: number;
var fromIf: number;
var inIf: number;
var fromWhileCondition: number;
var fromWhileBody: number;
var fromWhileBodyNested: number;
var fromDoBody: number;
var fromDoBodyNested: number;
var fromDoCondition: number;
var forInit: number;
var forCond: number;
var fromForBody: number;
var fromForBodyNested: number;
var forIncr: number;
var forOf: any[];
var fromForOfBody: number;
var fromForOfBodyNested: number;
var forIn: any[];
var fromForInBody: number;
var fromForInBodyNested: number;
}
Loading

0 comments on commit 7ab0f0d

Please sign in to comment.