Skip to content

Commit 6ff081f

Browse files
committed
Merge branch 'master' into publicTransformers
2 parents 511cc41 + 8a5bebe commit 6ff081f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+530
-144
lines changed

src/compiler/binder.ts

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,7 @@ namespace ts {
265265
return "export=";
266266
case SpecialPropertyAssignmentKind.ExportsProperty:
267267
case SpecialPropertyAssignmentKind.ThisProperty:
268+
case SpecialPropertyAssignmentKind.Property:
268269
// exports.x = ... or this.y = ...
269270
return ((node as BinaryExpression).left as PropertyAccessExpression).name.text;
270271
case SpecialPropertyAssignmentKind.PrototypeProperty:
@@ -1051,8 +1052,8 @@ namespace ts {
10511052
// second -> edge that represents post-finally flow.
10521053
// these edges are used in following scenario:
10531054
// let a; (1)
1054-
// try { a = someOperation(); (2)}
1055-
// finally { (3) console.log(a) } (4)
1055+
// try { a = someOperation(); (2)}
1056+
// finally { (3) console.log(a) } (4)
10561057
// (5) a
10571058

10581059
// flow graph for this case looks roughly like this (arrows show ):
@@ -1064,11 +1065,11 @@ namespace ts {
10641065
// In case when we walk the flow starting from inside the finally block we want to take edge '*****' into account
10651066
// since it ensures that finally is always reachable. However when we start outside the finally block and go through label (5)
10661067
// then edge '*****' should be discarded because label 4 is only reachable if post-finally label-4 is reachable
1067-
// Simply speaking code inside finally block is treated as reachable as pre-try-flow
1068+
// Simply speaking code inside finally block is treated as reachable as pre-try-flow
10681069
// since we conservatively assume that any line in try block can throw or return in which case we'll enter finally.
10691070
// However code after finally is reachable only if control flow was not abrupted in try/catch or finally blocks - it should be composed from
10701071
// final flows of these blocks without taking pre-try flow into account.
1071-
//
1072+
//
10721073
// extra edges that we inject allows to control this behavior
10731074
// if when walking the flow we step on post-finally edge - we can mark matching pre-finally edge as locked so it will be skipped.
10741075
const preFinallyFlow: PreFinallyFlow = { flags: FlowFlags.PreFinally, antecedent: preTryFlow, lock: {} };
@@ -1969,6 +1970,9 @@ namespace ts {
19691970
case SpecialPropertyAssignmentKind.ThisProperty:
19701971
bindThisPropertyAssignment(<BinaryExpression>node);
19711972
break;
1973+
case SpecialPropertyAssignmentKind.Property:
1974+
bindStaticPropertyAssignment(<BinaryExpression>node);
1975+
break;
19721976
case SpecialPropertyAssignmentKind.None:
19731977
// Nothing to do
19741978
break;
@@ -2265,18 +2269,41 @@ namespace ts {
22652269
constructorFunction.parent = classPrototype;
22662270
classPrototype.parent = leftSideOfAssignment;
22672271

2268-
const funcSymbol = container.locals.get(constructorFunction.text);
2269-
if (!funcSymbol || !(funcSymbol.flags & SymbolFlags.Function || isDeclarationOfFunctionExpression(funcSymbol))) {
2272+
bindPropertyAssignment(constructorFunction.text, leftSideOfAssignment, /*isPrototypeProperty*/ true);
2273+
}
2274+
2275+
function bindStaticPropertyAssignment(node: BinaryExpression) {
2276+
// We saw a node of the form 'x.y = z'. Declare a 'member' y on x if x was a function.
2277+
2278+
// Look up the function in the local scope, since prototype assignments should
2279+
// follow the function declaration
2280+
const leftSideOfAssignment = node.left as PropertyAccessExpression;
2281+
const target = leftSideOfAssignment.expression as Identifier;
2282+
2283+
// Fix up parent pointers since we're going to use these nodes before we bind into them
2284+
leftSideOfAssignment.parent = node;
2285+
target.parent = leftSideOfAssignment;
2286+
2287+
bindPropertyAssignment(target.text, leftSideOfAssignment, /*isPrototypeProperty*/ false);
2288+
}
2289+
2290+
function bindPropertyAssignment(functionName: string, propertyAccessExpression: PropertyAccessExpression, isPrototypeProperty: boolean) {
2291+
let targetSymbol = container.locals.get(functionName);
2292+
if (targetSymbol && isDeclarationOfFunctionOrClassExpression(targetSymbol)) {
2293+
targetSymbol = (targetSymbol.valueDeclaration as VariableDeclaration).initializer.symbol;
2294+
}
2295+
2296+
if (!targetSymbol || !(targetSymbol.flags & (SymbolFlags.Function | SymbolFlags.Class))) {
22702297
return;
22712298
}
22722299

22732300
// Set up the members collection if it doesn't exist already
2274-
if (!funcSymbol.members) {
2275-
funcSymbol.members = createMap<Symbol>();
2276-
}
2301+
const symbolTable = isPrototypeProperty ?
2302+
(targetSymbol.members || (targetSymbol.members = createMap<Symbol>())) :
2303+
(targetSymbol.exports || (targetSymbol.exports = createMap<Symbol>()));
22772304

22782305
// Declare the method/property
2279-
declareSymbol(funcSymbol.members, funcSymbol, leftSideOfAssignment, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
2306+
declareSymbol(symbolTable, targetSymbol, propertyAccessExpression, SymbolFlags.Property, SymbolFlags.PropertyExcludes);
22802307
}
22812308

22822309
function bindCallExpression(node: CallExpression) {

src/compiler/checker.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ namespace ts {
9595
getSignaturesOfType,
9696
getIndexTypeOfType,
9797
getBaseTypes,
98+
getBaseTypeOfLiteralType,
99+
getWidenedType,
98100
getTypeFromTypeNode: node => {
99101
node = getParseTreeNode(node, isTypeNode);
100102
return node ? getTypeFromTypeNode(node) : unknownType;
@@ -2247,13 +2249,15 @@ namespace ts {
22472249
return type.flags & TypeFlags.StringLiteral ? `"${escapeString((<LiteralType>type).text)}"` : (<LiteralType>type).text;
22482250
}
22492251

2250-
22512252
function getNameOfSymbol(symbol: Symbol): string {
22522253
if (symbol.declarations && symbol.declarations.length) {
22532254
const declaration = symbol.declarations[0];
22542255
if (declaration.name) {
22552256
return declarationNameToString(declaration.name);
22562257
}
2258+
if (declaration.parent && declaration.parent.kind === SyntaxKind.VariableDeclaration) {
2259+
return declarationNameToString((<VariableDeclaration>declaration.parent).name);
2260+
}
22572261
switch (declaration.kind) {
22582262
case SyntaxKind.ClassExpression:
22592263
return "(Anonymous class)";
@@ -4755,7 +4759,7 @@ namespace ts {
47554759
// Combinations of function, class, enum and module
47564760
let members = emptySymbols;
47574761
let constructSignatures: Signature[] = emptyArray;
4758-
if (symbol.flags & SymbolFlags.HasExports) {
4762+
if (symbol.exports) {
47594763
members = getExportsOfSymbol(symbol);
47604764
}
47614765
if (symbol.flags & SymbolFlags.Class) {
@@ -14640,10 +14644,13 @@ namespace ts {
1464014644
// in a JS file
1464114645
// Note:JS inferred classes might come from a variable declaration instead of a function declaration.
1464214646
// In this case, using getResolvedSymbol directly is required to avoid losing the members from the declaration.
14643-
const funcSymbol = node.expression.kind === SyntaxKind.Identifier ?
14647+
let funcSymbol = node.expression.kind === SyntaxKind.Identifier ?
1464414648
getResolvedSymbol(node.expression as Identifier) :
1464514649
checkExpression(node.expression).symbol;
14646-
if (funcSymbol && funcSymbol.members && (funcSymbol.flags & SymbolFlags.Function || isDeclarationOfFunctionExpression(funcSymbol))) {
14650+
if (funcSymbol && isDeclarationOfFunctionOrClassExpression(funcSymbol)) {
14651+
funcSymbol = getSymbolOfNode((<VariableDeclaration>funcSymbol.valueDeclaration).initializer);
14652+
}
14653+
if (funcSymbol && funcSymbol.members && funcSymbol.flags & SymbolFlags.Function) {
1464714654
return getInferredClassType(funcSymbol);
1464814655
}
1464914656
else if (compilerOptions.noImplicitAny) {
@@ -20692,22 +20699,29 @@ namespace ts {
2069220699
return getLeftSideOfImportEqualsOrExportAssignment(node) !== undefined;
2069320700
}
2069420701

20702+
function getSpecialPropertyAssignmentSymbolFromEntityName(entityName: EntityName | PropertyAccessExpression) {
20703+
const specialPropertyAssignmentKind = getSpecialPropertyAssignmentKind(entityName.parent.parent);
20704+
switch (specialPropertyAssignmentKind) {
20705+
case SpecialPropertyAssignmentKind.ExportsProperty:
20706+
case SpecialPropertyAssignmentKind.PrototypeProperty:
20707+
return getSymbolOfNode(entityName.parent);
20708+
case SpecialPropertyAssignmentKind.ThisProperty:
20709+
case SpecialPropertyAssignmentKind.ModuleExports:
20710+
case SpecialPropertyAssignmentKind.Property:
20711+
return getSymbolOfNode(entityName.parent.parent);
20712+
}
20713+
}
20714+
2069520715
function getSymbolOfEntityNameOrPropertyAccessExpression(entityName: EntityName | PropertyAccessExpression): Symbol | undefined {
2069620716
if (isDeclarationName(entityName)) {
2069720717
return getSymbolOfNode(entityName.parent);
2069820718
}
2069920719

2070020720
if (isInJavaScriptFile(entityName) && entityName.parent.kind === SyntaxKind.PropertyAccessExpression) {
20701-
const specialPropertyAssignmentKind = getSpecialPropertyAssignmentKind(entityName.parent.parent);
20702-
switch (specialPropertyAssignmentKind) {
20703-
case SpecialPropertyAssignmentKind.ExportsProperty:
20704-
case SpecialPropertyAssignmentKind.PrototypeProperty:
20705-
return getSymbolOfNode(entityName.parent);
20706-
case SpecialPropertyAssignmentKind.ThisProperty:
20707-
case SpecialPropertyAssignmentKind.ModuleExports:
20708-
return getSymbolOfNode(entityName.parent.parent);
20709-
default:
20710-
// Fall through if it is not a special property assignment
20721+
// Check if this is a special property assignment
20722+
const specialPropertyAssignmentSymbol = getSpecialPropertyAssignmentSymbolFromEntityName(entityName);
20723+
if (specialPropertyAssignmentSymbol) {
20724+
return specialPropertyAssignmentSymbol;
2071120725
}
2071220726
}
2071320727

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3315,6 +3315,14 @@
33153315
"category": "Message",
33163316
"code": 90015
33173317
},
3318+
"Add declaration for missing property '{0}'": {
3319+
"category": "Message",
3320+
"code": 90016
3321+
},
3322+
"Add index signature for missing property '{0}'": {
3323+
"category": "Message",
3324+
"code": 90017
3325+
},
33183326
"Octal literal types must use ES2015 syntax. Use the syntax '{0}'.": {
33193327
"category": "Error",
33203328
"code": 8017

src/compiler/types.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2402,6 +2402,8 @@
24022402
getSignaturesOfType(type: Type, kind: SignatureKind): Signature[];
24032403
getIndexTypeOfType(type: Type, kind: IndexKind): Type;
24042404
getBaseTypes(type: InterfaceType): BaseType[];
2405+
getBaseTypeOfLiteralType(type: Type): Type;
2406+
getWidenedType(type: Type): Type;
24052407
getReturnTypeOfSignature(signature: Signature): Type;
24062408
/**
24072409
* Gets the type of a parameter at a given position in a signature.
@@ -3194,7 +3196,9 @@
31943196
/// className.prototype.name = expr
31953197
PrototypeProperty,
31963198
/// this.name = expr
3197-
ThisProperty
3199+
ThisProperty,
3200+
// F.name = expr
3201+
Property
31983202
}
31993203

32003204
export interface JsFileExtensionInfo {

src/compiler/utilities.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1417,10 +1417,10 @@ namespace ts {
14171417
* Returns true if the node is a variable declaration whose initializer is a function expression.
14181418
* This function does not test if the node is in a JavaScript file or not.
14191419
*/
1420-
export function isDeclarationOfFunctionExpression(s: Symbol) {
1420+
export function isDeclarationOfFunctionOrClassExpression(s: Symbol) {
14211421
if (s.valueDeclaration && s.valueDeclaration.kind === SyntaxKind.VariableDeclaration) {
14221422
const declaration = s.valueDeclaration as VariableDeclaration;
1423-
return declaration.initializer && declaration.initializer.kind === SyntaxKind.FunctionExpression;
1423+
return declaration.initializer && (declaration.initializer.kind === SyntaxKind.FunctionExpression || declaration.initializer.kind === SyntaxKind.ClassExpression);
14241424
}
14251425
return false;
14261426
}
@@ -1449,6 +1449,10 @@ namespace ts {
14491449
// module.exports = expr
14501450
return SpecialPropertyAssignmentKind.ModuleExports;
14511451
}
1452+
else {
1453+
// F.x = expr
1454+
return SpecialPropertyAssignmentKind.Property;
1455+
}
14521456
}
14531457
else if (lhs.expression.kind === SyntaxKind.ThisKeyword) {
14541458
return SpecialPropertyAssignmentKind.ThisProperty;
@@ -1468,6 +1472,7 @@ namespace ts {
14681472
}
14691473
}
14701474

1475+
14711476
return SpecialPropertyAssignmentKind.None;
14721477
}
14731478

src/harness/fourslash.ts

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2122,15 +2122,15 @@ namespace FourSlash {
21222122
* Because codefixes are only applied on the working file, it is unsafe
21232123
* to apply this more than once (consider a refactoring across files).
21242124
*/
2125-
public verifyRangeAfterCodeFix(expectedText: string, errorCode?: number, includeWhiteSpace?: boolean) {
2125+
public verifyRangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number) {
21262126
const ranges = this.getRanges();
21272127
if (ranges.length !== 1) {
21282128
this.raiseError("Exactly one range should be specified in the testfile.");
21292129
}
21302130

21312131
const fileName = this.activeFile.fileName;
21322132

2133-
this.applyCodeFixActions(fileName, this.getCodeFixActions(fileName, errorCode));
2133+
this.applyCodeAction(fileName, this.getCodeFixActions(fileName, errorCode), index);
21342134

21352135
const actualText = this.rangeText(ranges[0]);
21362136

@@ -2155,7 +2155,7 @@ namespace FourSlash {
21552155
public verifyFileAfterCodeFix(expectedContents: string, fileName?: string) {
21562156
fileName = fileName ? fileName : this.activeFile.fileName;
21572157

2158-
this.applyCodeFixActions(fileName, this.getCodeFixActions(fileName));
2158+
this.applyCodeAction(fileName, this.getCodeFixActions(fileName));
21592159

21602160
const actualContents: string = this.getFileContent(fileName);
21612161
if (this.removeWhitespace(actualContents) !== this.removeWhitespace(expectedContents)) {
@@ -2193,12 +2193,20 @@ namespace FourSlash {
21932193
return actions;
21942194
}
21952195

2196-
private applyCodeFixActions(fileName: string, actions: ts.CodeAction[]): void {
2197-
if (!(actions && actions.length === 1)) {
2198-
this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found.`);
2196+
private applyCodeAction(fileName: string, actions: ts.CodeAction[], index?: number): void {
2197+
if (index === undefined) {
2198+
if (!(actions && actions.length === 1)) {
2199+
this.raiseError(`Should find exactly one codefix, but ${actions ? actions.length : "none"} found.`);
2200+
}
2201+
index = 0;
2202+
}
2203+
else {
2204+
if (!(actions && actions.length >= index + 1)) {
2205+
this.raiseError(`Should find at least ${index + 1} codefix(es), but ${actions ? actions.length : "none"} found.`);
2206+
}
21992207
}
22002208

2201-
const fileChanges = ts.find(actions[0].changes, change => change.fileName === fileName);
2209+
const fileChanges = ts.find(actions[index].changes, change => change.fileName === fileName);
22022210
if (!fileChanges) {
22032211
this.raiseError("The CodeFix found doesn't provide any changes in this file.");
22042212
}
@@ -3535,8 +3543,8 @@ namespace FourSlashInterface {
35353543
this.DocCommentTemplate(/*expectedText*/ undefined, /*expectedOffset*/ undefined, /*empty*/ true);
35363544
}
35373545

3538-
public rangeAfterCodeFix(expectedText: string, errorCode?: number, includeWhiteSpace?: boolean): void {
3539-
this.state.verifyRangeAfterCodeFix(expectedText, errorCode, includeWhiteSpace);
3546+
public rangeAfterCodeFix(expectedText: string, includeWhiteSpace?: boolean, errorCode?: number, index?: number): void {
3547+
this.state.verifyRangeAfterCodeFix(expectedText, includeWhiteSpace, errorCode, index);
35403548
}
35413549

35423550
public importFixAtPosition(expectedTextArray: string[], errorCode?: number): void {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
registerCodeFix({
4+
errorCodes: [Diagnostics.Property_0_does_not_exist_on_type_1.code],
5+
getCodeActions: getActionsForAddMissingMember
6+
});
7+
8+
function getActionsForAddMissingMember(context: CodeFixContext): CodeAction[] | undefined {
9+
10+
const sourceFile = context.sourceFile;
11+
const start = context.span.start;
12+
// This is the identifier of the missing property. eg:
13+
// this.missing = 1;
14+
// ^^^^^^^
15+
const token = getTokenAtPosition(sourceFile, start);
16+
17+
if (token.kind != SyntaxKind.Identifier) {
18+
return undefined;
19+
}
20+
21+
const classDeclaration = getContainingClass(token);
22+
if (!classDeclaration) {
23+
return undefined;
24+
}
25+
26+
if (!(token.parent && token.parent.kind === SyntaxKind.PropertyAccessExpression)) {
27+
return undefined;
28+
}
29+
30+
if ((token.parent as PropertyAccessExpression).expression.kind !== SyntaxKind.ThisKeyword) {
31+
return undefined;
32+
}
33+
34+
let typeString = "any";
35+
36+
if (token.parent.parent.kind === SyntaxKind.BinaryExpression) {
37+
const binaryExpression = token.parent.parent as BinaryExpression;
38+
39+
const checker = context.program.getTypeChecker();
40+
const widenedType = checker.getWidenedType(checker.getBaseTypeOfLiteralType(checker.getTypeAtLocation(binaryExpression.right)));
41+
typeString = checker.typeToString(widenedType);
42+
}
43+
44+
const startPos = classDeclaration.members.pos;
45+
46+
return [{
47+
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_declaration_for_missing_property_0), [token.getText()]),
48+
changes: [{
49+
fileName: sourceFile.fileName,
50+
textChanges: [{
51+
span: { start: startPos, length: 0 },
52+
newText: `${token.getFullText(sourceFile)}: ${typeString};`
53+
}]
54+
}]
55+
},
56+
{
57+
description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_missing_property_0), [token.getText()]),
58+
changes: [{
59+
fileName: sourceFile.fileName,
60+
textChanges: [{
61+
span: { start: startPos, length: 0 },
62+
newText: `[name: string]: ${typeString};`
63+
}]
64+
}]
65+
}];
66+
}
67+
}

0 commit comments

Comments
 (0)