From b8329a05c3ea5611e34581f0704b80eef3e461c6 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Sun, 22 Jan 2017 10:45:23 -0800 Subject: [PATCH] basic support for declaring properties on funcitons --- src/compiler/binder.ts | 30 +++++++++++++++++++ src/compiler/checker.ts | 29 +++++++++++------- src/compiler/types.ts | 4 ++- src/compiler/utilities.ts | 9 ++++++ .../fourslash/renameJsPropertyAssignment.ts | 11 +++++++ 5 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 tests/cases/fourslash/renameJsPropertyAssignment.ts diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 712c96a49f9..da1ba466530 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -265,6 +265,7 @@ namespace ts { return "export="; case SpecialPropertyAssignmentKind.ExportsProperty: case SpecialPropertyAssignmentKind.ThisProperty: + case SpecialPropertyAssignmentKind.Property: // exports.x = ... or this.y = ... return ((node as BinaryExpression).left as PropertyAccessExpression).name.text; case SpecialPropertyAssignmentKind.PrototypeProperty: @@ -1921,6 +1922,9 @@ namespace ts { case SpecialPropertyAssignmentKind.ThisProperty: bindThisPropertyAssignment(node); break; + case SpecialPropertyAssignmentKind.Property: + bindPropertyAssignment(node); + break; case SpecialPropertyAssignmentKind.None: // Nothing to do break; @@ -2225,6 +2229,32 @@ namespace ts { declareSymbol(funcSymbol.members, funcSymbol, leftSideOfAssignment, SymbolFlags.Property, SymbolFlags.PropertyExcludes); } + function bindPropertyAssignment(node: BinaryExpression) { + // We saw a node of the form 'x.y = z'. Declare a 'member' y on x if x was a function. + + // Look up the function in the local scope, since prototype assignments should + // follow the function declaration + const leftSideOfAssignment = node.left as PropertyAccessExpression; + const target = leftSideOfAssignment.expression as Identifier; + + // Fix up parent pointers since we're going to use these nodes before we bind into them + leftSideOfAssignment.parent = node; + target.parent = leftSideOfAssignment; + + const funcSymbol = container.locals[target.text]; + if (!funcSymbol || !(funcSymbol.flags & SymbolFlags.Function || isDeclarationOfFunctionExpression(funcSymbol))) { + return; + } + + // Set up the members collection if it doesn't exist already + if (!funcSymbol.exports) { + funcSymbol.exports = createMap(); + } + + // Declare the method/property + declareSymbol(funcSymbol.exports, funcSymbol, leftSideOfAssignment, SymbolFlags.Property, SymbolFlags.PropertyExcludes); + } + function bindCallExpression(node: CallExpression) { // We're only inspecting call expressions to detect CommonJS modules, so we can skip // this check if we've already seen the module indicator diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bd8ff7568f3..78f67ae438f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4580,7 +4580,7 @@ namespace ts { // Combinations of function, class, enum and module let members = emptySymbols; let constructSignatures: Signature[] = emptyArray; - if (symbol.flags & SymbolFlags.HasExports) { + if (symbol.exports) { members = getExportsOfSymbol(symbol); } if (symbol.flags & SymbolFlags.Class) { @@ -19871,22 +19871,29 @@ namespace ts { return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined; } + function getSpecialPropertyAssignmentSymbolFromEntityName(entityName: EntityName | PropertyAccessExpression) { + const specialPropertyAssignmentKind = getSpecialPropertyAssignmentKind(entityName.parent.parent); + switch (specialPropertyAssignmentKind) { + case SpecialPropertyAssignmentKind.ExportsProperty: + case SpecialPropertyAssignmentKind.PrototypeProperty: + return getSymbolOfNode(entityName.parent); + case SpecialPropertyAssignmentKind.ThisProperty: + case SpecialPropertyAssignmentKind.ModuleExports: + case SpecialPropertyAssignmentKind.Property: + return getSymbolOfNode(entityName.parent.parent); + } + } + function getSymbolOfEntityNameOrPropertyAccessExpression(entityName: EntityName | PropertyAccessExpression): Symbol | undefined { if (isDeclarationName(entityName)) { return getSymbolOfNode(entityName.parent); } if (isInJavaScriptFile(entityName) && entityName.parent.kind === SyntaxKind.PropertyAccessExpression) { - const specialPropertyAssignmentKind = getSpecialPropertyAssignmentKind(entityName.parent.parent); - switch (specialPropertyAssignmentKind) { - case SpecialPropertyAssignmentKind.ExportsProperty: - case SpecialPropertyAssignmentKind.PrototypeProperty: - return getSymbolOfNode(entityName.parent); - case SpecialPropertyAssignmentKind.ThisProperty: - case SpecialPropertyAssignmentKind.ModuleExports: - return getSymbolOfNode(entityName.parent.parent); - default: - // Fall through if it is not a special property assignment + // Check if this is a special property assignment + const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(entityName); + if (specialPropertyAssignmentSymbol) { + return specialPropertyAssignmentSymbol; } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c16c199d3eb..8fa47183a7e 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3133,7 +3133,9 @@ /// className.prototype.name = expr PrototypeProperty, /// this.name = expr - ThisProperty + ThisProperty, + // F.name = expr + Property } export interface FileExtensionInfo { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b7cc0f0de6d..5cd0746b039 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1374,6 +1374,10 @@ namespace ts { return false; } + export function isValidSpecialPropertyAssignmentParent(parentSymbol: Symbol) { + return parentSymbol && (parentSymbol.flags & SymbolFlags.Function || isDeclarationOfFunctionExpression(parentSymbol)); + } + /// Given a BinaryExpression, returns SpecialPropertyAssignmentKind for the various kinds of property /// assignments we treat as special in the binder export function getSpecialPropertyAssignmentKind(expression: Node): SpecialPropertyAssignmentKind { @@ -1398,6 +1402,10 @@ namespace ts { // module.exports = expr return SpecialPropertyAssignmentKind.ModuleExports; } + else { + // F.x = expr + return SpecialPropertyAssignmentKind.Property; + } } else if (lhs.expression.kind === SyntaxKind.ThisKeyword) { return SpecialPropertyAssignmentKind.ThisProperty; @@ -1417,6 +1425,7 @@ namespace ts { } } + return SpecialPropertyAssignmentKind.None; } diff --git a/tests/cases/fourslash/renameJsPropertyAssignment.ts b/tests/cases/fourslash/renameJsPropertyAssignment.ts new file mode 100644 index 00000000000..fd1ba47569d --- /dev/null +++ b/tests/cases/fourslash/renameJsPropertyAssignment.ts @@ -0,0 +1,11 @@ +/// + +// @allowJs: true +// @Filename: a.js +////function bar() { +////} +////bar.[|foo|] = "foo"; +////console.log(bar./**/[|foo|]); + +goTo.marker(); +verify.renameLocations( /*findInStrings*/ false, /*findInComments*/ false); \ No newline at end of file