From 3e37b3158b538a370680bc551579e90fd54bca82 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Wed, 14 Oct 2015 21:36:35 -0700 Subject: [PATCH 01/22] Address code review at 5127 --- src/compiler/core.ts | 6 +++--- src/compiler/sys.ts | 2 +- src/compiler/tsc.ts | 2 +- src/compiler/utilities.ts | 19 ++++++------------- src/server/editorServices.ts | 2 +- 5 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 9a4f5452eed..55d2447dd2d 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -844,9 +844,9 @@ namespace ts { export function copyListRemovingItem(item: T, list: T[]) { let copiedList: T[] = []; - for (var i = 0, len = list.length; i < len; i++) { - if (list[i] !== item) { - copiedList.push(list[i]); + for (let e of list) { + if (e !== item) { + copiedList.push(e); } } return copiedList; diff --git a/src/compiler/sys.ts b/src/compiler/sys.ts index 0872a2e5ba5..3eeb126c065 100644 --- a/src/compiler/sys.ts +++ b/src/compiler/sys.ts @@ -407,7 +407,7 @@ namespace ts { // (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643) return _fs.watch( path, - { persisten: true, recursive: !!recursive }, + { persistent: true, recursive: !!recursive }, (eventName: string, relativeFileName: string) => { // In watchDirectory we only care about adding and removing files (when event name is // "rename"); changes made within files are handled by corresponding fileWatchers (when diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 9d79d435a04..5129f5e9efe 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -360,7 +360,7 @@ namespace ts { let newFileNames = ts.map(parsedCommandLine.fileNames, compilerHost.getCanonicalFileName); let canonicalRootFileNames = ts.map(rootFileNames, compilerHost.getCanonicalFileName); - if (!arrayStructurallyIsEqualTo(newFileNames, canonicalRootFileNames)) { + if (!arrayIsEqualTo(newFileNames, canonicalRootFileNames, /*comparer*/ undefined, /*sortBeforeComparison*/ true)) { setCachedProgram(undefined); startTimerForRecompilation(); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ccfab49375d..37109d1aa73 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -82,7 +82,7 @@ namespace ts { return node.end - node.pos; } - export function arrayIsEqualTo(arr1: T[], arr2: T[], comparer?: (a: T, b: T) => boolean): boolean { + export function arrayIsEqualTo(arr1: T[], arr2: T[], comparer?: (a: T, b: T) => boolean, sortBeforeComparison = false): boolean { if (!arr1 || !arr2) { return arr1 === arr2; } @@ -91,6 +91,11 @@ namespace ts { return false; } + if (sortBeforeComparison) { + arr1.sort(); + arr2.sort(); + } + for (let i = 0; i < arr1.length; ++i) { let equals = comparer ? comparer(arr1[i], arr2[i]) : arr1[i] === arr2[i]; if (!equals) { @@ -2414,16 +2419,4 @@ namespace ts { } } } - - export function arrayStructurallyIsEqualTo(array1: Array, array2: Array): boolean { - if (!array1 || !array2) { - return false; - } - - if (array1.length !== array2.length) { - return false; - } - - return arrayIsEqualTo(array1.sort(), array2.sort()); - } } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 09f8a372463..bd334279798 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -576,7 +576,7 @@ namespace ts.server { let newRootFiles = projectOptions.files.map((f => this.getCanonicalFileName(f))); let currentRootFiles = project.getRootFiles().map((f => this.getCanonicalFileName(f))); - if (!arrayStructurallyIsEqualTo(currentRootFiles, newRootFiles)) { + if (!arrayIsEqualTo(currentRootFiles, newRootFiles, /*comparer*/ undefined, /*sortBeforeComparison*/ true)) { // For configured projects, the change is made outside the tsconfig file, and // it is not likely to affect the project for other files opened by the client. We can // just update the current project. From 6798bd576bcc9a4c5c8f8fd8d25cd53a55ad085a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 15 Oct 2015 09:45:38 -0700 Subject: [PATCH 02/22] Primitives are not assignable to any-type indexers `string/numberIndexTypesRelatedTo` needs to prevent primitives from being assignable to an indexer of type 'any'. However, these two functions take an apparent type, which no longer has the primitive flag set. I thought of three ways to provide this information: 1. Pass the original type into `string/numberIndexTypesRelatedTo` and check its flag. 2. Record a boolean `isPrimitive` before converting to the apparent type, and pass it to `string/numberIndexTypesRelatedTo`. 3. Create a helper function `isPrimitive` that takes the apparent type and compares it to globalString/Number/Boolean/ESSymbolType. I decided on (1) because it seems like the simplest and safest. But none of the options are elegant. Please suggest improvements. --- src/compiler/checker.ts | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b13520cee2d..20ef9160333 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4895,7 +4895,7 @@ namespace ts { if (apparentType.flags & (TypeFlags.ObjectType | TypeFlags.Intersection) && target.flags & TypeFlags.ObjectType) { // Report structural errors only if we haven't reported any errors yet let reportStructuralErrors = reportErrors && errorInfo === saveErrorInfo; - if (result = objectTypeRelatedTo(apparentType, target, reportStructuralErrors)) { + if (result = objectTypeRelatedTo(apparentType, source, target, reportStructuralErrors)) { errorInfo = saveErrorInfo; return result; } @@ -4917,7 +4917,7 @@ namespace ts { return result; } } - return objectTypeRelatedTo(source, target, /*reportErrors*/ false); + return objectTypeRelatedTo(source, source, target, /*reportErrors*/ false); } if (source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.TypeParameter) { return typeParameterIdenticalTo(source, target); @@ -5071,11 +5071,11 @@ namespace ts { // Third, check if both types are part of deeply nested chains of generic type instantiations and if so assume the types are // equal and infinitely expanding. Fourth, if we have reached a depth of 100 nested comparisons, assume we have runaway recursion // and issue an error. Otherwise, actually compare the structure of the two types. - function objectTypeRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary { + function objectTypeRelatedTo(apparentSource: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary { if (overflow) { return Ternary.False; } - let id = relation !== identityRelation || source.id < target.id ? source.id + "," + target.id : target.id + "," + source.id; + let id = relation !== identityRelation || apparentSource.id < target.id ? apparentSource.id + "," + target.id : target.id + "," + apparentSource.id; let related = relation[id]; if (related !== undefined) { // If we computed this relation already and it was failed and reported, or if we're not being asked to elaborate @@ -5102,28 +5102,28 @@ namespace ts { maybeStack = []; expandingFlags = 0; } - sourceStack[depth] = source; + sourceStack[depth] = apparentSource; targetStack[depth] = target; maybeStack[depth] = {}; maybeStack[depth][id] = RelationComparisonResult.Succeeded; depth++; let saveExpandingFlags = expandingFlags; - if (!(expandingFlags & 1) && isDeeplyNestedGeneric(source, sourceStack, depth)) expandingFlags |= 1; + if (!(expandingFlags & 1) && isDeeplyNestedGeneric(apparentSource, sourceStack, depth)) expandingFlags |= 1; if (!(expandingFlags & 2) && isDeeplyNestedGeneric(target, targetStack, depth)) expandingFlags |= 2; let result: Ternary; if (expandingFlags === 3) { result = Ternary.Maybe; } else { - result = propertiesRelatedTo(source, target, reportErrors); + result = propertiesRelatedTo(apparentSource, target, reportErrors); if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Call, reportErrors); + result &= signaturesRelatedTo(apparentSource, target, SignatureKind.Call, reportErrors); if (result) { - result &= signaturesRelatedTo(source, target, SignatureKind.Construct, reportErrors); + result &= signaturesRelatedTo(apparentSource, target, SignatureKind.Construct, reportErrors); if (result) { - result &= stringIndexTypesRelatedTo(source, target, reportErrors); + result &= stringIndexTypesRelatedTo(apparentSource, originalSource, target, reportErrors); if (result) { - result &= numberIndexTypesRelatedTo(source, target, reportErrors); + result &= numberIndexTypesRelatedTo(apparentSource, originalSource, target, reportErrors); } } } @@ -5454,12 +5454,17 @@ namespace ts { return result; } - function stringIndexTypesRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary { + function stringIndexTypesRelatedTo(source: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary { if (relation === identityRelation) { return indexTypesIdenticalTo(IndexKind.String, source, target); } let targetType = getIndexTypeOfType(target, IndexKind.String); - if (targetType && !(targetType.flags & TypeFlags.Any)) { + if (targetType) { + if ((targetType.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive)) { + // non-primitive assignment to any is always allowed, eg + // `var x: { [index: string]: any } = { property: 12 };` + return Ternary.True; + } let sourceType = getIndexTypeOfType(source, IndexKind.String); if (!sourceType) { if (reportErrors) { @@ -5479,12 +5484,17 @@ namespace ts { return Ternary.True; } - function numberIndexTypesRelatedTo(source: Type, target: Type, reportErrors: boolean): Ternary { + function numberIndexTypesRelatedTo(source: Type, originalSource: Type, target: Type, reportErrors: boolean): Ternary { if (relation === identityRelation) { return indexTypesIdenticalTo(IndexKind.Number, source, target); } let targetType = getIndexTypeOfType(target, IndexKind.Number); - if (targetType && !(targetType.flags & TypeFlags.Any)) { + if (targetType) { + if ((targetType.flags & TypeFlags.Any) && !(originalSource.flags & TypeFlags.Primitive)) { + // non-primitive assignment to any is always allowed, eg + // `var x: { [index: number]: any } = { property: 12 };` + return Ternary.True; + } let sourceStringType = getIndexTypeOfType(source, IndexKind.String); let sourceNumberType = getIndexTypeOfType(source, IndexKind.Number); if (!(sourceStringType || sourceNumberType)) { From 8eacd41ab0300259c4837702e0dfddf22bd22e8a Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 15 Oct 2015 09:52:31 -0700 Subject: [PATCH 03/22] Add tests and accept baselines --- .../baselines/reference/assignmentCompat1.errors.txt | 10 +++++++++- tests/baselines/reference/assignmentCompat1.js | 5 +++++ tests/baselines/reference/indexTypeCheck.errors.txt | 5 ++++- tests/baselines/reference/intTypeCheck.errors.txt | 12 +++++++++++- tests/cases/compiler/assignmentCompat1.ts | 3 +++ 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/tests/baselines/reference/assignmentCompat1.errors.txt b/tests/baselines/reference/assignmentCompat1.errors.txt index edb8d28eecc..93afdbe1bcb 100644 --- a/tests/baselines/reference/assignmentCompat1.errors.txt +++ b/tests/baselines/reference/assignmentCompat1.errors.txt @@ -2,9 +2,11 @@ tests/cases/compiler/assignmentCompat1.ts(4,1): error TS2322: Type '{ [index: st Property 'one' is missing in type '{ [index: string]: any; }'. tests/cases/compiler/assignmentCompat1.ts(6,1): error TS2322: Type '{ [index: number]: any; }' is not assignable to type '{ one: number; }'. Property 'one' is missing in type '{ [index: number]: any; }'. +tests/cases/compiler/assignmentCompat1.ts(8,1): error TS2322: Type 'string' is not assignable to type '{ [index: string]: any; }'. + Index signature is missing in type 'String'. -==== tests/cases/compiler/assignmentCompat1.ts (2 errors) ==== +==== tests/cases/compiler/assignmentCompat1.ts (3 errors) ==== var x = { one: 1 }; var y: { [index: string]: any }; var z: { [index: number]: any }; @@ -18,4 +20,10 @@ tests/cases/compiler/assignmentCompat1.ts(6,1): error TS2322: Type '{ [index: nu !!! error TS2322: Type '{ [index: number]: any; }' is not assignable to type '{ one: number; }'. !!! error TS2322: Property 'one' is missing in type '{ [index: number]: any; }'. z = x; // Ok because index signature type is any + y = "foo"; // Error + ~ +!!! error TS2322: Type 'string' is not assignable to type '{ [index: string]: any; }'. +!!! error TS2322: Index signature is missing in type 'String'. + z = "foo"; // Error + \ No newline at end of file diff --git a/tests/baselines/reference/assignmentCompat1.js b/tests/baselines/reference/assignmentCompat1.js index 740a44cced4..a873c24c567 100644 --- a/tests/baselines/reference/assignmentCompat1.js +++ b/tests/baselines/reference/assignmentCompat1.js @@ -6,6 +6,9 @@ x = y; // Error y = x; // Ok because index signature type is any x = z; // Error z = x; // Ok because index signature type is any +y = "foo"; // Error +z = "foo"; // Error + //// [assignmentCompat1.js] @@ -16,3 +19,5 @@ x = y; // Error y = x; // Ok because index signature type is any x = z; // Error z = x; // Ok because index signature type is any +y = "foo"; // Error +z = "foo"; // Error diff --git a/tests/baselines/reference/indexTypeCheck.errors.txt b/tests/baselines/reference/indexTypeCheck.errors.txt index 2ea5a2f413a..06b844f8321 100644 --- a/tests/baselines/reference/indexTypeCheck.errors.txt +++ b/tests/baselines/reference/indexTypeCheck.errors.txt @@ -1,13 +1,14 @@ tests/cases/compiler/indexTypeCheck.ts(2,2): error TS1021: An index signature must have a type annotation. tests/cases/compiler/indexTypeCheck.ts(3,2): error TS1021: An index signature must have a type annotation. tests/cases/compiler/indexTypeCheck.ts(17,2): error TS2413: Numeric index type 'number' is not assignable to string index type 'string'. +tests/cases/compiler/indexTypeCheck.ts(22,2): error TS2413: Numeric index type 'Orange' is not assignable to string index type 'Yellow'. tests/cases/compiler/indexTypeCheck.ts(27,2): error TS2413: Numeric index type 'number' is not assignable to string index type 'string'. tests/cases/compiler/indexTypeCheck.ts(32,3): error TS1096: An index signature must have exactly one parameter. tests/cases/compiler/indexTypeCheck.ts(36,3): error TS1023: An index signature parameter type must be 'string' or 'number'. tests/cases/compiler/indexTypeCheck.ts(51,1): error TS2342: An index expression argument must be of type 'string', 'number', 'symbol', or 'any'. -==== tests/cases/compiler/indexTypeCheck.ts (7 errors) ==== +==== tests/cases/compiler/indexTypeCheck.ts (8 errors) ==== interface Red { [n:number]; // ok ~~~~~~~~~~~ @@ -36,6 +37,8 @@ tests/cases/compiler/indexTypeCheck.ts(51,1): error TS2342: An index expression interface Green { [n:number]: Orange; // error + ~~~~~~~~~~~~~~~~~~~ +!!! error TS2413: Numeric index type 'Orange' is not assignable to string index type 'Yellow'. [s:string]: Yellow; // ok } diff --git a/tests/baselines/reference/intTypeCheck.errors.txt b/tests/baselines/reference/intTypeCheck.errors.txt index 7f1cc88e2a2..e48d1b1dc73 100644 --- a/tests/baselines/reference/intTypeCheck.errors.txt +++ b/tests/baselines/reference/intTypeCheck.errors.txt @@ -30,6 +30,8 @@ tests/cases/compiler/intTypeCheck.ts(134,21): error TS1109: Expression expected. tests/cases/compiler/intTypeCheck.ts(134,22): error TS2304: Cannot find name 'i3'. tests/cases/compiler/intTypeCheck.ts(135,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. tests/cases/compiler/intTypeCheck.ts(142,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. +tests/cases/compiler/intTypeCheck.ts(148,5): error TS2322: Type 'boolean' is not assignable to type 'i4'. + Index signature is missing in type 'Boolean'. tests/cases/compiler/intTypeCheck.ts(148,21): error TS1109: Expression expected. tests/cases/compiler/intTypeCheck.ts(148,22): error TS2304: Cannot find name 'i4'. tests/cases/compiler/intTypeCheck.ts(149,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. @@ -66,12 +68,14 @@ tests/cases/compiler/intTypeCheck.ts(190,21): error TS1109: Expression expected. tests/cases/compiler/intTypeCheck.ts(190,22): error TS2304: Cannot find name 'i7'. tests/cases/compiler/intTypeCheck.ts(191,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. tests/cases/compiler/intTypeCheck.ts(198,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. +tests/cases/compiler/intTypeCheck.ts(204,5): error TS2322: Type 'boolean' is not assignable to type 'i8'. + Index signature is missing in type 'Boolean'. tests/cases/compiler/intTypeCheck.ts(204,21): error TS1109: Expression expected. tests/cases/compiler/intTypeCheck.ts(204,22): error TS2304: Cannot find name 'i8'. tests/cases/compiler/intTypeCheck.ts(205,17): error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature. -==== tests/cases/compiler/intTypeCheck.ts (61 errors) ==== +==== tests/cases/compiler/intTypeCheck.ts (63 errors) ==== interface i1 { //Property Signatures p; @@ -280,6 +284,9 @@ tests/cases/compiler/intTypeCheck.ts(205,17): error TS2351: Cannot use 'new' wit //var obj40: i4 = function foo() { }; var obj41: i4 = anyVar; var obj42: i4 = new anyVar; + ~~~~~ +!!! error TS2322: Type 'boolean' is not assignable to type 'i4'. +!!! error TS2322: Index signature is missing in type 'Boolean'. ~ !!! error TS1109: Expression expected. ~~ @@ -402,6 +409,9 @@ tests/cases/compiler/intTypeCheck.ts(205,17): error TS2351: Cannot use 'new' wit //var obj84: i8 = function foo() { }; var obj85: i8 = anyVar; var obj86: i8 = new anyVar; + ~~~~~ +!!! error TS2322: Type 'boolean' is not assignable to type 'i8'. +!!! error TS2322: Index signature is missing in type 'Boolean'. ~ !!! error TS1109: Expression expected. ~~ diff --git a/tests/cases/compiler/assignmentCompat1.ts b/tests/cases/compiler/assignmentCompat1.ts index b37a11b20d9..54dc91adab0 100644 --- a/tests/cases/compiler/assignmentCompat1.ts +++ b/tests/cases/compiler/assignmentCompat1.ts @@ -5,3 +5,6 @@ x = y; // Error y = x; // Ok because index signature type is any x = z; // Error z = x; // Ok because index signature type is any +y = "foo"; // Error +z = "foo"; // Error + From 5cd0ca19af72f1cdeebf16e3310bf746a567afce Mon Sep 17 00:00:00 2001 From: Nathan Shively-Sanders Date: Thu, 15 Oct 2015 11:04:36 -0700 Subject: [PATCH 04/22] Add test case, correct existing test case Existing: String assignment to a numeric indexer should succeed, not fail. (The baseline was already correct but the inline comment was wrong.) New: Boolean assignment to a numeric indexer should fail. --- tests/baselines/reference/assignmentCompat1.errors.txt | 10 ++++++++-- tests/baselines/reference/assignmentCompat1.js | 6 ++++-- tests/cases/compiler/assignmentCompat1.ts | 3 ++- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/tests/baselines/reference/assignmentCompat1.errors.txt b/tests/baselines/reference/assignmentCompat1.errors.txt index 93afdbe1bcb..7539af92887 100644 --- a/tests/baselines/reference/assignmentCompat1.errors.txt +++ b/tests/baselines/reference/assignmentCompat1.errors.txt @@ -4,9 +4,11 @@ tests/cases/compiler/assignmentCompat1.ts(6,1): error TS2322: Type '{ [index: nu Property 'one' is missing in type '{ [index: number]: any; }'. tests/cases/compiler/assignmentCompat1.ts(8,1): error TS2322: Type 'string' is not assignable to type '{ [index: string]: any; }'. Index signature is missing in type 'String'. +tests/cases/compiler/assignmentCompat1.ts(10,1): error TS2322: Type 'boolean' is not assignable to type '{ [index: number]: any; }'. + Index signature is missing in type 'Boolean'. -==== tests/cases/compiler/assignmentCompat1.ts (3 errors) ==== +==== tests/cases/compiler/assignmentCompat1.ts (4 errors) ==== var x = { one: 1 }; var y: { [index: string]: any }; var z: { [index: number]: any }; @@ -24,6 +26,10 @@ tests/cases/compiler/assignmentCompat1.ts(8,1): error TS2322: Type 'string' is n ~ !!! error TS2322: Type 'string' is not assignable to type '{ [index: string]: any; }'. !!! error TS2322: Index signature is missing in type 'String'. - z = "foo"; // Error + z = "foo"; // OK, string has numeric indexer + z = false; // Error + ~ +!!! error TS2322: Type 'boolean' is not assignable to type '{ [index: number]: any; }'. +!!! error TS2322: Index signature is missing in type 'Boolean'. \ No newline at end of file diff --git a/tests/baselines/reference/assignmentCompat1.js b/tests/baselines/reference/assignmentCompat1.js index a873c24c567..7451d342fa1 100644 --- a/tests/baselines/reference/assignmentCompat1.js +++ b/tests/baselines/reference/assignmentCompat1.js @@ -7,7 +7,8 @@ y = x; // Ok because index signature type is any x = z; // Error z = x; // Ok because index signature type is any y = "foo"; // Error -z = "foo"; // Error +z = "foo"; // OK, string has numeric indexer +z = false; // Error @@ -20,4 +21,5 @@ y = x; // Ok because index signature type is any x = z; // Error z = x; // Ok because index signature type is any y = "foo"; // Error -z = "foo"; // Error +z = "foo"; // OK, string has numeric indexer +z = false; // Error diff --git a/tests/cases/compiler/assignmentCompat1.ts b/tests/cases/compiler/assignmentCompat1.ts index 54dc91adab0..e93da76d95e 100644 --- a/tests/cases/compiler/assignmentCompat1.ts +++ b/tests/cases/compiler/assignmentCompat1.ts @@ -6,5 +6,6 @@ y = x; // Ok because index signature type is any x = z; // Error z = x; // Ok because index signature type is any y = "foo"; // Error -z = "foo"; // Error +z = "foo"; // OK, string has numeric indexer +z = false; // Error From ea9bf7313ae7334cdbed9a32306bccb4a22e3f5a Mon Sep 17 00:00:00 2001 From: zhengbli Date: Thu, 15 Oct 2015 13:53:37 -0700 Subject: [PATCH 05/22] CR feedback --- src/compiler/tsc.ts | 2 +- src/compiler/utilities.ts | 7 +------ src/server/editorServices.ts | 2 +- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 5129f5e9efe..5420caabe7c 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -360,7 +360,7 @@ namespace ts { let newFileNames = ts.map(parsedCommandLine.fileNames, compilerHost.getCanonicalFileName); let canonicalRootFileNames = ts.map(rootFileNames, compilerHost.getCanonicalFileName); - if (!arrayIsEqualTo(newFileNames, canonicalRootFileNames, /*comparer*/ undefined, /*sortBeforeComparison*/ true)) { + if (!arrayIsEqualTo(newFileNames.sort(), canonicalRootFileNames.sort())) { setCachedProgram(undefined); startTimerForRecompilation(); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 37109d1aa73..2a16c2c71fb 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -82,7 +82,7 @@ namespace ts { return node.end - node.pos; } - export function arrayIsEqualTo(arr1: T[], arr2: T[], comparer?: (a: T, b: T) => boolean, sortBeforeComparison = false): boolean { + export function arrayIsEqualTo(arr1: T[], arr2: T[], comparer?: (a: T, b: T) => boolean): boolean { if (!arr1 || !arr2) { return arr1 === arr2; } @@ -91,11 +91,6 @@ namespace ts { return false; } - if (sortBeforeComparison) { - arr1.sort(); - arr2.sort(); - } - for (let i = 0; i < arr1.length; ++i) { let equals = comparer ? comparer(arr1[i], arr2[i]) : arr1[i] === arr2[i]; if (!equals) { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index bd334279798..b98129f99e9 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -576,7 +576,7 @@ namespace ts.server { let newRootFiles = projectOptions.files.map((f => this.getCanonicalFileName(f))); let currentRootFiles = project.getRootFiles().map((f => this.getCanonicalFileName(f))); - if (!arrayIsEqualTo(currentRootFiles, newRootFiles, /*comparer*/ undefined, /*sortBeforeComparison*/ true)) { + if (!arrayIsEqualTo(currentRootFiles.sort(), newRootFiles.sort())) { // For configured projects, the change is made outside the tsconfig file, and // it is not likely to affect the project for other files opened by the client. We can // just update the current project. From e7e1fa72ec198f63d794abb296c4befb8b823dbb Mon Sep 17 00:00:00 2001 From: zhengbli Date: Fri, 16 Oct 2015 12:00:31 -0700 Subject: [PATCH 06/22] Add sortBeforeComparison option back to arrayIsEqualTo --- src/compiler/tsc.ts | 3 ++- src/compiler/utilities.ts | 16 ++++++++++------ src/server/editorServices.ts | 3 ++- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index 5420caabe7c..b4774b9a842 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -360,7 +360,8 @@ namespace ts { let newFileNames = ts.map(parsedCommandLine.fileNames, compilerHost.getCanonicalFileName); let canonicalRootFileNames = ts.map(rootFileNames, compilerHost.getCanonicalFileName); - if (!arrayIsEqualTo(newFileNames.sort(), canonicalRootFileNames.sort())) { + // We check if the project file list has changed. If so, we just throw away the old program and start fresh. + if (!arrayIsEqualTo(newFileNames, canonicalRootFileNames, /*equaler*/ undefined, /*sortBeforeComparison*/ true)) { setCachedProgram(undefined); startTimerForRecompilation(); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 2a16c2c71fb..b56c63013aa 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -82,17 +82,21 @@ namespace ts { return node.end - node.pos; } - export function arrayIsEqualTo(arr1: T[], arr2: T[], comparer?: (a: T, b: T) => boolean): boolean { - if (!arr1 || !arr2) { - return arr1 === arr2; + export function arrayIsEqualTo(array1: T[], array2: T[], equaler?: (a: T, b: T) => boolean, + sortBeforeComparison?: boolean, comparer?: (a: T, b: T) => number): boolean { + if (!array1 || !array2) { + return array1 === array2; } - if (arr1.length !== arr2.length) { + if (array1.length !== array2.length) { return false; } - for (let i = 0; i < arr1.length; ++i) { - let equals = comparer ? comparer(arr1[i], arr2[i]) : arr1[i] === arr2[i]; + let newArray1 = sortBeforeComparison ? array1.slice().sort(comparer) : array1; + let newArray2 = sortBeforeComparison ? array2.slice().sort(comparer) : array2; + + for (let i = 0; i < array1.length; ++i) { + let equals = equaler ? equaler(newArray1[i], newArray2[i]) : newArray1[i] === newArray2[i]; if (!equals) { return false; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index b98129f99e9..7cb047bb74d 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -576,7 +576,8 @@ namespace ts.server { let newRootFiles = projectOptions.files.map((f => this.getCanonicalFileName(f))); let currentRootFiles = project.getRootFiles().map((f => this.getCanonicalFileName(f))); - if (!arrayIsEqualTo(currentRootFiles.sort(), newRootFiles.sort())) { + // We check if the project file list has changed. If so, we update the project. + if (!arrayIsEqualTo(currentRootFiles, newRootFiles, /*equaler*/ undefined, /*sortBeforeComparison*/ true)) { // For configured projects, the change is made outside the tsconfig file, and // it is not likely to affect the project for other files opened by the client. We can // just update the current project. From 6ccb2a5ef21fb9962114eb4fb91edec99d42cf64 Mon Sep 17 00:00:00 2001 From: Ryan Cavanaugh Date: Fri, 16 Oct 2015 13:47:57 -0700 Subject: [PATCH 07/22] Better error recovery for adjacent JSX elements in expression positions Fixes #5286 --- src/compiler/checker.ts | 3 -- src/compiler/diagnosticMessages.json | 4 +++ src/compiler/parser.ts | 28 +++++++++++++++-- .../jsxEsprimaFbTestSuite.errors.txt | 9 ++---- .../reference/jsxEsprimaFbTestSuite.js | 4 +-- .../jsxInvalidEsprimaTestSuite.errors.txt | 30 ++++--------------- .../reference/jsxInvalidEsprimaTestSuite.js | 10 +++---- .../reference/tsxErrorRecovery2.errors.txt | 18 +++++++++++ .../baselines/reference/tsxErrorRecovery2.js | 19 ++++++++++++ .../reference/tsxErrorRecovery3.errors.txt | 30 +++++++++++++++++++ .../baselines/reference/tsxErrorRecovery3.js | 19 ++++++++++++ .../conformance/jsx/tsxErrorRecovery2.tsx | 10 +++++++ .../conformance/jsx/tsxErrorRecovery3.tsx | 10 +++++++ 13 files changed, 151 insertions(+), 43 deletions(-) create mode 100644 tests/baselines/reference/tsxErrorRecovery2.errors.txt create mode 100644 tests/baselines/reference/tsxErrorRecovery2.js create mode 100644 tests/baselines/reference/tsxErrorRecovery3.errors.txt create mode 100644 tests/baselines/reference/tsxErrorRecovery3.js create mode 100644 tests/cases/conformance/jsx/tsxErrorRecovery2.tsx create mode 100644 tests/cases/conformance/jsx/tsxErrorRecovery3.tsx diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b13520cee2d..85817734fd7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7510,9 +7510,6 @@ namespace ts { case SyntaxKind.JsxSelfClosingElement: checkJsxSelfClosingElement(child); break; - default: - // No checks for JSX Text - Debug.assert(child.kind === SyntaxKind.JsxText); } } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index a8ed6ac6127..9ec439d96fe 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1724,6 +1724,10 @@ "category": "Error", "code": 2656 }, + "JSX expressions must have one parent element": { + "category": "Error", + "code": 2657 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", "code": 4000 diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index 1551706a337..f021251f7d9 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -3454,19 +3454,43 @@ namespace ts { function parseJsxElementOrSelfClosingElement(inExpressionContext: boolean): JsxElement | JsxSelfClosingElement { let opening = parseJsxOpeningOrSelfClosingElement(inExpressionContext); + let result: JsxElement | JsxSelfClosingElement; if (opening.kind === SyntaxKind.JsxOpeningElement) { let node = createNode(SyntaxKind.JsxElement, opening.pos); node.openingElement = opening; node.children = parseJsxChildren(node.openingElement.tagName); node.closingElement = parseJsxClosingElement(inExpressionContext); - return finishNode(node); + result = finishNode(node); } else { Debug.assert(opening.kind === SyntaxKind.JsxSelfClosingElement); // Nothing else to do for self-closing elements - return opening; + result = opening; } + + // If the user writes the invalid code '
' in an expression context (i.e. not wrapped in + // an enclosing tag), we'll naively try to parse ^ this as a 'less than' operator and the remainder of the tag + // as garbage, which will cause the formatter to badly mangle the JSX. Perform a speculative parse of a JSX + // element if we see a < token so that we can wrap it in a synthetic binary expression so the formatter + // does less damage and we can report a better error. + // Since JSX elements are invalid < operands anyway, this lookahead parse will only occur in error scenarios + // of one sort or another. + if (inExpressionContext && token === SyntaxKind.LessThanToken) { + let invalidElement = tryParse(() => parseJsxElementOrSelfClosingElement(/*inExpressionContext*/true)); + if (invalidElement) { + parseErrorAtCurrentToken(Diagnostics.JSX_expressions_must_have_one_parent_element); + let badNode = createNode(SyntaxKind.BinaryExpression, result.pos); + badNode.end = invalidElement.end; + badNode.left = result; + badNode.right = invalidElement; + badNode.operatorToken = createMissingNode(SyntaxKind.CommaToken, /*reportAtCurrentPosition*/ false, /*diagnosticMessage*/ undefined); + badNode.operatorToken.pos = badNode.operatorToken.end = badNode.right.pos; + return badNode; + } + } + + return result; } function parseJsxText(): JsxText { diff --git a/tests/baselines/reference/jsxEsprimaFbTestSuite.errors.txt b/tests/baselines/reference/jsxEsprimaFbTestSuite.errors.txt index 5758db36960..3a6059d8d07 100644 --- a/tests/baselines/reference/jsxEsprimaFbTestSuite.errors.txt +++ b/tests/baselines/reference/jsxEsprimaFbTestSuite.errors.txt @@ -4,11 +4,10 @@ tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,29): error TS1005: '{' tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,57): error TS1109: Expression expected. tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(39,58): error TS1109: Expression expected. tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,1): error TS1003: Identifier expected. -tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,6): error TS1109: Expression expected. -tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,12): error TS1109: Expression expected. +tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,12): error TS2657: JSX expressions must have one parent element -==== tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx (8 errors) ==== +==== tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx (7 errors) ==== declare var React: any; declare var 日本語; declare var AbC_def; @@ -62,10 +61,8 @@ tests/cases/conformance/jsx/jsxEsprimaFbTestSuite.tsx(41,12): error TS1109: Expr ; ~ !!! error TS1003: Identifier expected. - ~~ -!!! error TS1109: Expression expected. ~ -!!! error TS1109: Expression expected. +!!! error TS2657: JSX expressions must have one parent element ; diff --git a/tests/baselines/reference/jsxEsprimaFbTestSuite.js b/tests/baselines/reference/jsxEsprimaFbTestSuite.js index 1e22826f440..465d265efb2 100644 --- a/tests/baselines/reference/jsxEsprimaFbTestSuite.js +++ b/tests/baselines/reference/jsxEsprimaFbTestSuite.js @@ -72,8 +72,8 @@ baz
@test content
;

7x invalid-js-identifier
; } right={monkeys /> gorillas / > }/> - < a.b > ; -a.b > ; + , + ; ; (
) < x;
; diff --git a/tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt b/tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt index 02bc56e823e..578159dc651 100644 --- a/tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt +++ b/tests/baselines/reference/jsxInvalidEsprimaTestSuite.errors.txt @@ -12,17 +12,11 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(6,6): error TS2304: C tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(6,9): error TS1109: Expression expected. tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(6,10): error TS1109: Expression expected. tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(7,1): error TS1003: Identifier expected. -tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(7,2): error TS2304: Cannot find name 'a'. -tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(7,4): error TS1109: Expression expected. tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(8,4): error TS17002: Expected corresponding JSX closing tag for 'a'. tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(9,13): error TS1002: Unterminated string literal. tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,1): error TS1003: Identifier expected. -tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,2): error TS2304: Cannot find name 'a'. -tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,3): error TS1005: ';' expected. -tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,4): error TS2304: Cannot find name 'b'. -tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,6): error TS1109: Expression expected. -tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,8): error TS2304: Cannot find name 'b'. -tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,10): error TS1109: Expression expected. +tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,6): error TS17002: Expected corresponding JSX closing tag for 'a'. +tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(10,10): error TS2657: JSX expressions must have one parent element tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(11,3): error TS1003: Identifier expected. tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(11,5): error TS1003: Identifier expected. tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(11,11): error TS1005: '>' expected. @@ -71,7 +65,7 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,4): error TS1003: tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002: Expected corresponding JSX closing tag for 'a'. -==== tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx (71 errors) ==== +==== tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx (65 errors) ==== declare var React: any; ; @@ -107,10 +101,6 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002 ; ~ !!! error TS1003: Identifier expected. - ~ -!!! error TS2304: Cannot find name 'a'. - ~ -!!! error TS1109: Expression expected. ; ~~~~ !!! error TS17002: Expected corresponding JSX closing tag for 'a'. @@ -120,18 +110,10 @@ tests/cases/conformance/jsx/jsxInvalidEsprimaTestSuite.tsx(35,21): error TS17002 ; ~ !!! error TS1003: Identifier expected. - ~ -!!! error TS2304: Cannot find name 'a'. - ~ -!!! error TS1005: ';' expected. - ~ -!!! error TS2304: Cannot find name 'b'. - ~~ -!!! error TS1109: Expression expected. - ~ -!!! error TS2304: Cannot find name 'b'. + ~~~~ +!!! error TS17002: Expected corresponding JSX closing tag for 'a'. ~ -!!! error TS1109: Expression expected. +!!! error TS2657: JSX expressions must have one parent element ; ~ !!! error TS1003: Identifier expected. diff --git a/tests/baselines/reference/jsxInvalidEsprimaTestSuite.js b/tests/baselines/reference/jsxInvalidEsprimaTestSuite.js index d623e127be7..7f01666bd78 100644 --- a/tests/baselines/reference/jsxInvalidEsprimaTestSuite.js +++ b/tests/baselines/reference/jsxInvalidEsprimaTestSuite.js @@ -41,12 +41,10 @@ var x =
one
/* intervening comment */
two
;; < ; a / > ;
}/> - < a > ; -; -a:b>; ; b.c > ; ; diff --git a/tests/baselines/reference/tsxErrorRecovery2.errors.txt b/tests/baselines/reference/tsxErrorRecovery2.errors.txt new file mode 100644 index 00000000000..9a0b5790b2c --- /dev/null +++ b/tests/baselines/reference/tsxErrorRecovery2.errors.txt @@ -0,0 +1,18 @@ +tests/cases/conformance/jsx/file1.tsx(6,1): error TS2657: JSX expressions must have one parent element +tests/cases/conformance/jsx/file2.tsx(2,1): error TS2657: JSX expressions must have one parent element + + +==== tests/cases/conformance/jsx/file1.tsx (1 errors) ==== + + declare namespace JSX { interface Element { } } + +
+
+ + +!!! error TS2657: JSX expressions must have one parent element +==== tests/cases/conformance/jsx/file2.tsx (1 errors) ==== + var x =
+ + +!!! error TS2657: JSX expressions must have one parent element \ No newline at end of file diff --git a/tests/baselines/reference/tsxErrorRecovery2.js b/tests/baselines/reference/tsxErrorRecovery2.js new file mode 100644 index 00000000000..be5b6c73424 --- /dev/null +++ b/tests/baselines/reference/tsxErrorRecovery2.js @@ -0,0 +1,19 @@ +//// [tests/cases/conformance/jsx/tsxErrorRecovery2.tsx] //// + +//// [file1.tsx] + +declare namespace JSX { interface Element { } } + +
+
+ +//// [file2.tsx] +var x =
+ + +//// [file1.jsx] +
+ , +
; +//// [file2.jsx] +var x =
,
; diff --git a/tests/baselines/reference/tsxErrorRecovery3.errors.txt b/tests/baselines/reference/tsxErrorRecovery3.errors.txt new file mode 100644 index 00000000000..f7e4bc170a9 --- /dev/null +++ b/tests/baselines/reference/tsxErrorRecovery3.errors.txt @@ -0,0 +1,30 @@ +tests/cases/conformance/jsx/file1.tsx(4,2): error TS2304: Cannot find name 'React'. +tests/cases/conformance/jsx/file1.tsx(5,2): error TS2304: Cannot find name 'React'. +tests/cases/conformance/jsx/file1.tsx(6,1): error TS2657: JSX expressions must have one parent element +tests/cases/conformance/jsx/file2.tsx(1,10): error TS2304: Cannot find name 'React'. +tests/cases/conformance/jsx/file2.tsx(1,21): error TS2304: Cannot find name 'React'. +tests/cases/conformance/jsx/file2.tsx(2,1): error TS2657: JSX expressions must have one parent element + + +==== tests/cases/conformance/jsx/file1.tsx (3 errors) ==== + + declare namespace JSX { interface Element { } } + +
+ ~~~ +!!! error TS2304: Cannot find name 'React'. +
+ ~~~ +!!! error TS2304: Cannot find name 'React'. + + +!!! error TS2657: JSX expressions must have one parent element +==== tests/cases/conformance/jsx/file2.tsx (3 errors) ==== + var x =
+ ~~~ +!!! error TS2304: Cannot find name 'React'. + ~~~ +!!! error TS2304: Cannot find name 'React'. + + +!!! error TS2657: JSX expressions must have one parent element \ No newline at end of file diff --git a/tests/baselines/reference/tsxErrorRecovery3.js b/tests/baselines/reference/tsxErrorRecovery3.js new file mode 100644 index 00000000000..93b34a14c19 --- /dev/null +++ b/tests/baselines/reference/tsxErrorRecovery3.js @@ -0,0 +1,19 @@ +//// [tests/cases/conformance/jsx/tsxErrorRecovery3.tsx] //// + +//// [file1.tsx] + +declare namespace JSX { interface Element { } } + +
+
+ +//// [file2.tsx] +var x =
+ + +//// [file1.js] +React.createElement("div", null) + , + React.createElement("div", null); +//// [file2.js] +var x = React.createElement("div", null), React.createElement("div", null); diff --git a/tests/cases/conformance/jsx/tsxErrorRecovery2.tsx b/tests/cases/conformance/jsx/tsxErrorRecovery2.tsx new file mode 100644 index 00000000000..e189e0e7791 --- /dev/null +++ b/tests/cases/conformance/jsx/tsxErrorRecovery2.tsx @@ -0,0 +1,10 @@ +//@jsx: preserve + +//@filename: file1.tsx +declare namespace JSX { interface Element { } } + +
+
+ +//@filename: file2.tsx +var x =
diff --git a/tests/cases/conformance/jsx/tsxErrorRecovery3.tsx b/tests/cases/conformance/jsx/tsxErrorRecovery3.tsx new file mode 100644 index 00000000000..9d444b07cb2 --- /dev/null +++ b/tests/cases/conformance/jsx/tsxErrorRecovery3.tsx @@ -0,0 +1,10 @@ +//@jsx: react + +//@filename: file1.tsx +declare namespace JSX { interface Element { } } + +
+
+ +//@filename: file2.tsx +var x =
From 0bd50ca08c3990c5cb4dfc8e38b2e432241266d9 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Sat, 17 Oct 2015 14:35:28 -0700 Subject: [PATCH 08/22] extract 'convertCompilerOptionsFromJson' to separate function --- src/compiler/commandLineParser.ts | 100 ++++++++++++++++-------------- src/compiler/types.ts | 2 +- 2 files changed, 54 insertions(+), 48 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index acf0474b7bf..95583a19038 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -410,63 +410,19 @@ namespace ts { /** * Parse the contents of a config file (tsconfig.json). * @param json The contents of the config file to parse + * @param host Instance of ParseConfigHost used to enumerate files in folder. * @param basePath A root directory to resolve relative path entries in the config * file to. e.g. outDir */ export function parseJsonConfigFileContent(json: any, host: ParseConfigHost, basePath: string): ParsedCommandLine { - let errors: Diagnostic[] = []; + let { options, errors } = convertCompilerOptionsFromJson(json["compilerOptions"], basePath); return { - options: getCompilerOptions(), + options, fileNames: getFileNames(), errors }; - function getCompilerOptions(): CompilerOptions { - let options: CompilerOptions = {}; - let optionNameMap: Map = {}; - forEach(optionDeclarations, option => { - optionNameMap[option.name] = option; - }); - let jsonOptions = json["compilerOptions"]; - if (jsonOptions) { - for (let id in jsonOptions) { - if (hasProperty(optionNameMap, id)) { - let opt = optionNameMap[id]; - let optType = opt.type; - let value = jsonOptions[id]; - let expectedType = typeof optType === "string" ? optType : "string"; - if (typeof value === expectedType) { - if (typeof optType !== "string") { - let key = value.toLowerCase(); - if (hasProperty(optType, key)) { - value = optType[key]; - } - else { - errors.push(createCompilerDiagnostic((opt).error)); - value = 0; - } - } - if (opt.isFilePath) { - value = normalizePath(combinePaths(basePath, value)); - if (value === "") { - value = "."; - } - } - options[opt.name] = value; - } - else { - errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, id, expectedType)); - } - } - else { - errors.push(createCompilerDiagnostic(Diagnostics.Unknown_compiler_option_0, id)); - } - } - } - return options; - } - function getFileNames(): string[] { let fileNames: string[] = []; if (hasProperty(json, "files")) { @@ -501,4 +457,54 @@ namespace ts { return fileNames; } } + + export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string): { options: CompilerOptions, errors: Diagnostic[] } { + let options: CompilerOptions = {}; + let optionNameMap: Map = {}; + let errors: Diagnostic[] = []; + + if (!jsonOptions) { + return { options, errors }; + } + + forEach(optionDeclarations, option => { + optionNameMap[option.name] = option; + }); + + for (let id in jsonOptions) { + if (hasProperty(optionNameMap, id)) { + let opt = optionNameMap[id]; + let optType = opt.type; + let value = jsonOptions[id]; + let expectedType = typeof optType === "string" ? optType : "string"; + if (typeof value === expectedType) { + if (typeof optType !== "string") { + let key = value.toLowerCase(); + if (hasProperty(optType, key)) { + value = optType[key]; + } + else { + errors.push(createCompilerDiagnostic((opt).error)); + value = 0; + } + } + if (opt.isFilePath) { + value = normalizePath(combinePaths(basePath, value)); + if (value === "") { + value = "."; + } + } + options[opt.name] = value; + } + else { + errors.push(createCompilerDiagnostic(Diagnostics.Compiler_option_0_requires_a_value_of_type_1, id, expectedType)); + } + } + else { + errors.push(createCompilerDiagnostic(Diagnostics.Unknown_compiler_option_0, id)); + } + } + + return { options, errors }; + } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index c44c6ad2cc0..2818ef04a48 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1305,7 +1305,7 @@ namespace ts { getCurrentDirectory(): string; } - export interface ParseConfigHost extends ModuleResolutionHost { + export interface ParseConfigHost { readDirectory(rootDir: string, extension: string, exclude: string[]): string[]; } From 14ea332d9405740f6e2b1cac1a618c7aa6a9531d Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Sun, 18 Oct 2015 16:34:02 -0700 Subject: [PATCH 09/22] Updated test options in 'CONTRIBUTING.md'. --- CONTRIBUTING.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 51b232712e6..9010b14311f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,17 +1,21 @@ ## Contributing bug fixes + TypeScript is currently accepting contributions in the form of bug fixes. A bug must have an issue tracking it in the issue tracker that has been approved ("Milestone == Community") by the TypeScript team. Your pull request should include a link to the bug that you are fixing. If you've submitted a PR for a bug, please post a comment in the bug to avoid duplication of effort. ## Contributing features + Features (things that add new or improved functionality to TypeScript) may be accepted, but will need to first be approved (marked as "Milestone == Community" by a TypeScript coordinator with the message "Approved") in the suggestion issue. Features with language design impact, or that are adequately satisfied with external tools, will not be accepted. Design changes will not be accepted at this time. If you have a design change proposal, please log a suggestion issue. ## Legal + You will need to complete a Contributor License Agreement (CLA). Briefly, this agreement testifies that you are granting us permission to use the submitted change according to the terms of the project's license, and that the work being submitted is under appropriate copyright. Please submit a Contributor License Agreement (CLA) before submitting a pull request. You may visit https://cla.microsoft.com to sign digitally. Alternatively, download the agreement ([Microsoft Contribution License Agreement.docx](https://www.codeplex.com/Download?ProjectName=typescript&DownloadId=822190) or [Microsoft Contribution License Agreement.pdf](https://www.codeplex.com/Download?ProjectName=typescript&DownloadId=921298)), sign, scan, and email it back to . Be sure to include your github user name along with the agreement. Once we have received the signed CLA, we'll review the request. ## Housekeeping + Your pull request should: * Include a description of what your change intends to do @@ -75,14 +79,15 @@ jake runtests tests=2dArrays debug=true ``` ## Adding a Test + To add a new testcase, simply place a `.ts` file in `tests\cases\compiler` containing code that exemplifies the bugfix or change you are making. These files support metadata tags in the format `// @metaDataName: value`. The supported names and values are: -* `comments`, `sourcemap`, `noimplicitany`, `declaration`: true or false (corresponds to the compiler command-line options of the same name) -* `target`: ES3 or ES5 (same as compiler) -* `out`, outDir: path (same as compiler) -* `module`: local, commonjs, or amd (local corresponds to not passing any compiler --module flag) +* `comments`, `sourcemap`, `noimplicitany`, `declaration`: `true` or `false` (corresponds to the compiler command-line options of the same name) +* `target`: `ES3`, `ES5`, `ES6`, `ES2015`, or `ES7` (same as compiler) +* `outFile`, `out`, `outDir`: path (same as compiler) +* `module`: `local`, `commonjs`, or `amd`, `umd`, `system`, `es6` (local corresponds to not passing any compiler `--module` flag) * `fileName`: path * These tags delimit sections of a file to be used as separate compilation units. They are useful for tests relating to modules. See below for examples. @@ -107,6 +112,7 @@ var x = g(); One can also write a project test, but it is slightly more involved. ## Managing the Baselines + Compiler testcases generate baselines that track the emitted `.js`, the errors produced by the compiler, and the type of each expression in the file. Additionally, some testcases opt in to baselining the source map output. When a change in the baselines is detected, the test will fail. To inspect changes vs the expected baselines, use @@ -123,4 +129,4 @@ jake baseline-accept to establish the new baselines as the desired behavior. This will change the files in `tests\baselines\reference`, which should be included as part of your commit. It's important to carefully validate changes in the baselines. -**Note** that baseline-accept should only be run after a full test run! Accepting baselines after running a subset of tests will delete baseline files for the tests that didn't run. +**Note** that `baseline-accept` should only be run after a full test run! Accepting baselines after running a subset of tests will delete baseline files for the tests that didn't run. From e2c858fee625b728d2c809111dd7ddf0059208d2 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Sun, 18 Oct 2015 19:07:57 -0700 Subject: [PATCH 10/22] Minor fixups. --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9010b14311f..5c45580ff55 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -87,7 +87,7 @@ These files support metadata tags in the format `// @metaDataName: value`. The * `comments`, `sourcemap`, `noimplicitany`, `declaration`: `true` or `false` (corresponds to the compiler command-line options of the same name) * `target`: `ES3`, `ES5`, `ES6`, `ES2015`, or `ES7` (same as compiler) * `outFile`, `out`, `outDir`: path (same as compiler) -* `module`: `local`, `commonjs`, or `amd`, `umd`, `system`, `es6` (local corresponds to not passing any compiler `--module` flag) +* `module`: `local`, `commonjs`, or `amd`, `umd`, `system`, `es6` (`local` corresponds to not passing any compiler `--module` flag) * `fileName`: path * These tags delimit sections of a file to be used as separate compilation units. They are useful for tests relating to modules. See below for examples. From 9fa268a44a527560fd2f096ab9e043e1375435e6 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Mon, 19 Oct 2015 10:15:59 -0700 Subject: [PATCH 11/22] addressed PR feedback --- src/compiler/commandLineParser.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/compiler/commandLineParser.ts b/src/compiler/commandLineParser.ts index 95583a19038..0c561ab06d8 100644 --- a/src/compiler/commandLineParser.ts +++ b/src/compiler/commandLineParser.ts @@ -460,16 +460,13 @@ namespace ts { export function convertCompilerOptionsFromJson(jsonOptions: any, basePath: string): { options: CompilerOptions, errors: Diagnostic[] } { let options: CompilerOptions = {}; - let optionNameMap: Map = {}; let errors: Diagnostic[] = []; if (!jsonOptions) { return { options, errors }; } - forEach(optionDeclarations, option => { - optionNameMap[option.name] = option; - }); + let optionNameMap = arrayToMap(optionDeclarations, opt => opt.name); for (let id in jsonOptions) { if (hasProperty(optionNameMap, id)) { From 174f8c3e6ef4f13b912e393cc03a0735454e6488 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 19 Oct 2015 11:49:21 -0700 Subject: [PATCH 12/22] Fixups. --- CONTRIBUTING.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5c45580ff55..b3588e7c601 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,8 @@ Your pull request should: * To avoid line ending issues, set `autocrlf = input` and `whitespace = cr-at-eol` in your git configuration ## Running the Tests -To run all tests, invoke the runtests target using jake: + +To run all tests, invoke the `runtests` target using jake: ```Shell jake runtests @@ -59,7 +60,7 @@ jake runtests tests=2dArrays ## Debugging the tests -To debug the tests, invoke the runtests-browser using jake. +To debug the tests, invoke the `runtests-browser` task from jake. You will probably only want to debug one test at a time: ```Shell @@ -80,7 +81,7 @@ jake runtests tests=2dArrays debug=true ## Adding a Test -To add a new testcase, simply place a `.ts` file in `tests\cases\compiler` containing code that exemplifies the bugfix or change you are making. +To add a new test case, simply place a `.ts` file in `tests\cases\compiler` containing code that exemplifies the bugfix or change you are making. These files support metadata tags in the format `// @metaDataName: value`. The supported names and values are: From 67d43ad75f1258254b452e27e7f28b9be592a750 Mon Sep 17 00:00:00 2001 From: Daniel Rosenwasser Date: Mon, 19 Oct 2015 14:22:00 -0700 Subject: [PATCH 13/22] Just say all compiler options are supported. --- CONTRIBUTING.md | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b3588e7c601..2e2c28590c2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,14 +83,11 @@ jake runtests tests=2dArrays debug=true To add a new test case, simply place a `.ts` file in `tests\cases\compiler` containing code that exemplifies the bugfix or change you are making. -These files support metadata tags in the format `// @metaDataName: value`. The supported names and values are: - -* `comments`, `sourcemap`, `noimplicitany`, `declaration`: `true` or `false` (corresponds to the compiler command-line options of the same name) -* `target`: `ES3`, `ES5`, `ES6`, `ES2015`, or `ES7` (same as compiler) -* `outFile`, `out`, `outDir`: path (same as compiler) -* `module`: `local`, `commonjs`, or `amd`, `umd`, `system`, `es6` (`local` corresponds to not passing any compiler `--module` flag) -* `fileName`: path - * These tags delimit sections of a file to be used as separate compilation units. They are useful for tests relating to modules. See below for examples. +These files support metadata tags in the format `// @metaDataName: value`. +The supported names and values are the same as those supported in the compiler itself, with the addition of the `fileName` flag. +`fileName` tags delimit sections of a file to be used as separate compilation units. +They are useful for tests relating to modules. +See below for examples. **Note** that if you have a test corresponding to a specific spec compliance item, you can place it in `tests\cases\conformance` in an appropriately-named subfolder. **Note** that filenames here must be distinct from all other compiler testcase names, so you may have to work a bit to find a unique name if it's something common. From 39254b54ae3b5b51517aa7a2217c5c852e8ceab2 Mon Sep 17 00:00:00 2001 From: zhengbli Date: Mon, 19 Oct 2015 21:48:40 -0700 Subject: [PATCH 14/22] CR feedback --- src/compiler/tsc.ts | 2 +- src/compiler/utilities.ts | 8 ++------ src/server/editorServices.ts | 2 +- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/compiler/tsc.ts b/src/compiler/tsc.ts index b4774b9a842..e51fda074c5 100644 --- a/src/compiler/tsc.ts +++ b/src/compiler/tsc.ts @@ -361,7 +361,7 @@ namespace ts { let canonicalRootFileNames = ts.map(rootFileNames, compilerHost.getCanonicalFileName); // We check if the project file list has changed. If so, we just throw away the old program and start fresh. - if (!arrayIsEqualTo(newFileNames, canonicalRootFileNames, /*equaler*/ undefined, /*sortBeforeComparison*/ true)) { + if (!arrayIsEqualTo(newFileNames && newFileNames.sort(), canonicalRootFileNames && canonicalRootFileNames.sort())) { setCachedProgram(undefined); startTimerForRecompilation(); } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index b56c63013aa..7c1b606dd00 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -82,8 +82,7 @@ namespace ts { return node.end - node.pos; } - export function arrayIsEqualTo(array1: T[], array2: T[], equaler?: (a: T, b: T) => boolean, - sortBeforeComparison?: boolean, comparer?: (a: T, b: T) => number): boolean { + export function arrayIsEqualTo(array1: T[], array2: T[], equaler?: (a: T, b: T) => boolean): boolean { if (!array1 || !array2) { return array1 === array2; } @@ -92,11 +91,8 @@ namespace ts { return false; } - let newArray1 = sortBeforeComparison ? array1.slice().sort(comparer) : array1; - let newArray2 = sortBeforeComparison ? array2.slice().sort(comparer) : array2; - for (let i = 0; i < array1.length; ++i) { - let equals = equaler ? equaler(newArray1[i], newArray2[i]) : newArray1[i] === newArray2[i]; + let equals = equaler ? equaler(array1[i], array2[i]) : array1[i] === array2[i]; if (!equals) { return false; } diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 7cb047bb74d..efcca0e37c5 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -577,7 +577,7 @@ namespace ts.server { let currentRootFiles = project.getRootFiles().map((f => this.getCanonicalFileName(f))); // We check if the project file list has changed. If so, we update the project. - if (!arrayIsEqualTo(currentRootFiles, newRootFiles, /*equaler*/ undefined, /*sortBeforeComparison*/ true)) { + if (!arrayIsEqualTo(currentRootFiles && currentRootFiles.sort(), newRootFiles && newRootFiles.sort())) { // For configured projects, the change is made outside the tsconfig file, and // it is not likely to affect the project for other files opened by the client. We can // just update the current project. From 85e587e1d330fdeea3b806477b03410be125c242 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 20 Oct 2015 10:59:23 -0700 Subject: [PATCH 15/22] Fixes issue in emitExpressionIdentifier when combining --target ES6 with --module. Fixes #5315. --- src/compiler/emitter.ts | 55 ++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index f2468e7244d..70ab3606c6c 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1769,34 +1769,39 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi write("."); } } - else if (modulekind !== ModuleKind.ES6) { - let declaration = resolver.getReferencedImportDeclaration(node); - if (declaration) { - if (declaration.kind === SyntaxKind.ImportClause) { - // Identifier references default import - write(getGeneratedNameForNode(declaration.parent)); - write(languageVersion === ScriptTarget.ES3 ? "[\"default\"]" : ".default"); - return; - } - else if (declaration.kind === SyntaxKind.ImportSpecifier) { - // Identifier references named import - write(getGeneratedNameForNode(declaration.parent.parent.parent)); - let name = (declaration).propertyName || (declaration).name; - let identifier = getSourceTextOfNodeFromSourceFile(currentSourceFile, name); - if (languageVersion === ScriptTarget.ES3 && identifier === "default") { - write(`["default"]`); + else { + if (modulekind !== ModuleKind.ES6) { + let declaration = resolver.getReferencedImportDeclaration(node); + if (declaration) { + if (declaration.kind === SyntaxKind.ImportClause) { + // Identifier references default import + write(getGeneratedNameForNode(declaration.parent)); + write(languageVersion === ScriptTarget.ES3 ? "[\"default\"]" : ".default"); + return; } - else { - write("."); - write(identifier); + else if (declaration.kind === SyntaxKind.ImportSpecifier) { + // Identifier references named import + write(getGeneratedNameForNode(declaration.parent.parent.parent)); + let name = (declaration).propertyName || (declaration).name; + let identifier = getSourceTextOfNodeFromSourceFile(currentSourceFile, name); + if (languageVersion === ScriptTarget.ES3 && identifier === "default") { + write(`["default"]`); + } + else { + write("."); + write(identifier); + } + return; } - return; } } - declaration = resolver.getReferencedNestedRedeclaration(node); - if (declaration) { - write(getGeneratedNameForNode(declaration.name)); - return; + + if (languageVersion !== ScriptTarget.ES6) { + let declaration = resolver.getReferencedNestedRedeclaration(node); + if (declaration) { + write(getGeneratedNameForNode(declaration.name)); + return; + } } } @@ -2785,7 +2790,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi /** * Emit ES7 exponentiation operator downlevel using Math.pow - * @param node a binary expression node containing exponentiationOperator (**, **=) + * @param node a binary expression node containing exponentiationOperator (**, **=) */ function emitExponentiationOperator(node: BinaryExpression) { let leftHandSideExpression = node.left; From c627802a438ec2e4e5a748d78f0dd12906b9d761 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 20 Oct 2015 12:32:49 -0700 Subject: [PATCH 16/22] Tests --- .../reference/nestedRedeclarationInES6AMD.js | 15 +++++++++++++++ .../reference/nestedRedeclarationInES6AMD.symbols | 11 +++++++++++ .../reference/nestedRedeclarationInES6AMD.types | 14 ++++++++++++++ .../cases/compiler/nestedRedeclarationInES6AMD.ts | 8 ++++++++ 4 files changed, 48 insertions(+) create mode 100644 tests/baselines/reference/nestedRedeclarationInES6AMD.js create mode 100644 tests/baselines/reference/nestedRedeclarationInES6AMD.symbols create mode 100644 tests/baselines/reference/nestedRedeclarationInES6AMD.types create mode 100644 tests/cases/compiler/nestedRedeclarationInES6AMD.ts diff --git a/tests/baselines/reference/nestedRedeclarationInES6AMD.js b/tests/baselines/reference/nestedRedeclarationInES6AMD.js new file mode 100644 index 00000000000..bb90759dc8b --- /dev/null +++ b/tests/baselines/reference/nestedRedeclarationInES6AMD.js @@ -0,0 +1,15 @@ +//// [nestedRedeclarationInES6AMD.ts] +function a() { + { + let status = 1; + status = 2; + } +} + +//// [nestedRedeclarationInES6AMD.js] +function a() { + { + let status = 1; + status = 2; + } +} diff --git a/tests/baselines/reference/nestedRedeclarationInES6AMD.symbols b/tests/baselines/reference/nestedRedeclarationInES6AMD.symbols new file mode 100644 index 00000000000..65f310a7ed2 --- /dev/null +++ b/tests/baselines/reference/nestedRedeclarationInES6AMD.symbols @@ -0,0 +1,11 @@ +=== tests/cases/compiler/nestedRedeclarationInES6AMD.ts === +function a() { +>a : Symbol(a, Decl(nestedRedeclarationInES6AMD.ts, 0, 0)) + { + let status = 1; +>status : Symbol(status, Decl(nestedRedeclarationInES6AMD.ts, 2, 11)) + + status = 2; +>status : Symbol(status, Decl(nestedRedeclarationInES6AMD.ts, 2, 11)) + } +} diff --git a/tests/baselines/reference/nestedRedeclarationInES6AMD.types b/tests/baselines/reference/nestedRedeclarationInES6AMD.types new file mode 100644 index 00000000000..2b8d971e3c1 --- /dev/null +++ b/tests/baselines/reference/nestedRedeclarationInES6AMD.types @@ -0,0 +1,14 @@ +=== tests/cases/compiler/nestedRedeclarationInES6AMD.ts === +function a() { +>a : () => void + { + let status = 1; +>status : number +>1 : number + + status = 2; +>status = 2 : number +>status : number +>2 : number + } +} diff --git a/tests/cases/compiler/nestedRedeclarationInES6AMD.ts b/tests/cases/compiler/nestedRedeclarationInES6AMD.ts new file mode 100644 index 00000000000..e518e354cd0 --- /dev/null +++ b/tests/cases/compiler/nestedRedeclarationInES6AMD.ts @@ -0,0 +1,8 @@ +// @target: ES6 +// @module: AMD +function a() { + { + let status = 1; + status = 2; + } +} \ No newline at end of file From 6d8950e13c6b586e1b55bef037e8c35d2a948cd0 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 20 Oct 2015 12:39:43 -0700 Subject: [PATCH 17/22] Fix check for excess properties in union and intersection types --- src/compiler/checker.ts | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 04cc9a2e426..823e01129d6 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4946,34 +4946,34 @@ namespace ts { resolved.stringIndexType || resolved.numberIndexType || getPropertyOfType(type, name)) { return true; } - return false; } - if (type.flags & TypeFlags.UnionOrIntersection) { + else if (type.flags & TypeFlags.UnionOrIntersection) { for (let t of (type).types) { if (isKnownProperty(t, name)) { return true; } } - return false; } - return true; + return false; } function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean { - for (let prop of getPropertiesOfObjectType(source)) { - if (!isKnownProperty(target, prop.name)) { - if (reportErrors) { - // We know *exactly* where things went wrong when comparing the types. - // Use this property as the error node as this will be more helpful in - // reasoning about what went wrong. - errorNode = prop.valueDeclaration; - reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, - symbolToString(prop), - typeToString(target)); + if (someConstituentTypeHasKind(target, TypeFlags.ObjectType)) { + for (let prop of getPropertiesOfObjectType(source)) { + if (!isKnownProperty(target, prop.name)) { + if (reportErrors) { + // We know *exactly* where things went wrong when comparing the types. + // Use this property as the error node as this will be more helpful in + // reasoning about what went wrong. + errorNode = prop.valueDeclaration; + reportError(Diagnostics.Object_literal_may_only_specify_known_properties_and_0_does_not_exist_in_type_1, + symbolToString(prop), typeToString(target)); + } + return true; } - return true; } } + return false; } function eachTypeRelatedToSomeType(source: UnionOrIntersectionType, target: UnionOrIntersectionType): Ternary { From a22f9b87bd979872f4ce0fe75759f184c15930c4 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 20 Oct 2015 12:40:39 -0700 Subject: [PATCH 18/22] Adding test --- .../objectLiteralExcessProperties.errors.txt | 66 +++++++++++++++++++ .../objectLiteralExcessProperties.js | 32 +++++++++ .../compiler/objectLiteralExcessProperties.ts | 21 ++++++ 3 files changed, 119 insertions(+) create mode 100644 tests/baselines/reference/objectLiteralExcessProperties.errors.txt create mode 100644 tests/baselines/reference/objectLiteralExcessProperties.js create mode 100644 tests/cases/compiler/objectLiteralExcessProperties.ts diff --git a/tests/baselines/reference/objectLiteralExcessProperties.errors.txt b/tests/baselines/reference/objectLiteralExcessProperties.errors.txt new file mode 100644 index 00000000000..84f635bf947 --- /dev/null +++ b/tests/baselines/reference/objectLiteralExcessProperties.errors.txt @@ -0,0 +1,66 @@ +tests/cases/compiler/objectLiteralExcessProperties.ts(9,18): error TS2322: Type '{ forword: string; }' is not assignable to type 'Book'. + Object literal may only specify known properties, and 'forword' does not exist in type 'Book'. +tests/cases/compiler/objectLiteralExcessProperties.ts(11,27): error TS2322: Type '{ foreward: string; }' is not assignable to type 'Book | string'. + Object literal may only specify known properties, and 'foreward' does not exist in type 'Book | string'. +tests/cases/compiler/objectLiteralExcessProperties.ts(13,53): error TS2322: Type '({ foreword: string; } | { forwards: string; })[]' is not assignable to type 'Book | Book[]'. + Type '({ foreword: string; } | { forwards: string; })[]' is not assignable to type 'Book[]'. + Type '{ foreword: string; } | { forwards: string; }' is not assignable to type 'Book'. + Type '{ forwards: string; }' is not assignable to type 'Book'. + Object literal may only specify known properties, and 'forwards' does not exist in type 'Book'. +tests/cases/compiler/objectLiteralExcessProperties.ts(15,42): error TS2322: Type '{ foreword: string; colour: string; }' is not assignable to type 'Book & Cover'. + Object literal may only specify known properties, and 'colour' does not exist in type 'Book & Cover'. +tests/cases/compiler/objectLiteralExcessProperties.ts(17,26): error TS2322: Type '{ foreward: string; color: string; }' is not assignable to type 'Book & Cover'. + Object literal may only specify known properties, and 'foreward' does not exist in type 'Book & Cover'. +tests/cases/compiler/objectLiteralExcessProperties.ts(19,57): error TS2322: Type '{ foreword: string; color: string; price: number; }' is not assignable to type 'Book & Cover'. + Object literal may only specify known properties, and 'price' does not exist in type 'Book & Cover'. +tests/cases/compiler/objectLiteralExcessProperties.ts(21,43): error TS2322: Type '{ foreword: string; price: number; }' is not assignable to type 'Book & number'. + Object literal may only specify known properties, and 'price' does not exist in type 'Book & number'. + + +==== tests/cases/compiler/objectLiteralExcessProperties.ts (7 errors) ==== + interface Book { + foreword: string; + } + + interface Cover { + color?: string; + } + + var b1: Book = { forword: "oops" }; + ~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ forword: string; }' is not assignable to type 'Book'. +!!! error TS2322: Object literal may only specify known properties, and 'forword' does not exist in type 'Book'. + + var b2: Book | string = { foreward: "nope" }; + ~~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ foreward: string; }' is not assignable to type 'Book | string'. +!!! error TS2322: Object literal may only specify known properties, and 'foreward' does not exist in type 'Book | string'. + + var b3: Book | (Book[]) = [{ foreword: "hello" }, { forwards: "back" }]; + ~~~~~~~~~~~~~~~~ +!!! error TS2322: Type '({ foreword: string; } | { forwards: string; })[]' is not assignable to type 'Book | Book[]'. +!!! error TS2322: Type '({ foreword: string; } | { forwards: string; })[]' is not assignable to type 'Book[]'. +!!! error TS2322: Type '{ foreword: string; } | { forwards: string; }' is not assignable to type 'Book'. +!!! error TS2322: Type '{ forwards: string; }' is not assignable to type 'Book'. +!!! error TS2322: Object literal may only specify known properties, and 'forwards' does not exist in type 'Book'. + + var b4: Book & Cover = { foreword: "hi", colour: "blue" }; + ~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ foreword: string; colour: string; }' is not assignable to type 'Book & Cover'. +!!! error TS2322: Object literal may only specify known properties, and 'colour' does not exist in type 'Book & Cover'. + + var b5: Book & Cover = { foreward: "hi", color: "blue" }; + ~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ foreward: string; color: string; }' is not assignable to type 'Book & Cover'. +!!! error TS2322: Object literal may only specify known properties, and 'foreward' does not exist in type 'Book & Cover'. + + var b6: Book & Cover = { foreword: "hi", color: "blue", price: 10.99 }; + ~~~~~~~~~~~~ +!!! error TS2322: Type '{ foreword: string; color: string; price: number; }' is not assignable to type 'Book & Cover'. +!!! error TS2322: Object literal may only specify known properties, and 'price' does not exist in type 'Book & Cover'. + + var b7: Book & number = { foreword: "hi", price: 10.99 }; + ~~~~~~~~~~~~ +!!! error TS2322: Type '{ foreword: string; price: number; }' is not assignable to type 'Book & number'. +!!! error TS2322: Object literal may only specify known properties, and 'price' does not exist in type 'Book & number'. + \ No newline at end of file diff --git a/tests/baselines/reference/objectLiteralExcessProperties.js b/tests/baselines/reference/objectLiteralExcessProperties.js new file mode 100644 index 00000000000..747928ccb20 --- /dev/null +++ b/tests/baselines/reference/objectLiteralExcessProperties.js @@ -0,0 +1,32 @@ +//// [objectLiteralExcessProperties.ts] +interface Book { + foreword: string; +} + +interface Cover { + color?: string; +} + +var b1: Book = { forword: "oops" }; + +var b2: Book | string = { foreward: "nope" }; + +var b3: Book | (Book[]) = [{ foreword: "hello" }, { forwards: "back" }]; + +var b4: Book & Cover = { foreword: "hi", colour: "blue" }; + +var b5: Book & Cover = { foreward: "hi", color: "blue" }; + +var b6: Book & Cover = { foreword: "hi", color: "blue", price: 10.99 }; + +var b7: Book & number = { foreword: "hi", price: 10.99 }; + + +//// [objectLiteralExcessProperties.js] +var b1 = { forword: "oops" }; +var b2 = { foreward: "nope" }; +var b3 = [{ foreword: "hello" }, { forwards: "back" }]; +var b4 = { foreword: "hi", colour: "blue" }; +var b5 = { foreward: "hi", color: "blue" }; +var b6 = { foreword: "hi", color: "blue", price: 10.99 }; +var b7 = { foreword: "hi", price: 10.99 }; diff --git a/tests/cases/compiler/objectLiteralExcessProperties.ts b/tests/cases/compiler/objectLiteralExcessProperties.ts new file mode 100644 index 00000000000..0aa81703c33 --- /dev/null +++ b/tests/cases/compiler/objectLiteralExcessProperties.ts @@ -0,0 +1,21 @@ +interface Book { + foreword: string; +} + +interface Cover { + color?: string; +} + +var b1: Book = { forword: "oops" }; + +var b2: Book | string = { foreward: "nope" }; + +var b3: Book | (Book[]) = [{ foreword: "hello" }, { forwards: "back" }]; + +var b4: Book & Cover = { foreword: "hi", colour: "blue" }; + +var b5: Book & Cover = { foreward: "hi", color: "blue" }; + +var b6: Book & Cover = { foreword: "hi", color: "blue", price: 10.99 }; + +var b7: Book & number = { foreword: "hi", price: 10.99 }; From 912e49b66852e9dd598790446026de9b7e3efe8f Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 21 Oct 2015 12:49:14 -0700 Subject: [PATCH 19/22] do not indent token if its start line matches end line of previous token\trivia --- src/services/formatting/formatting.ts | 8 +++++--- tests/cases/fourslash/formatAfterMultilineComment.ts | 7 +++++++ 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 tests/cases/fourslash/formatAfterMultilineComment.ts diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 4a7033c6f3e..651105e9d5c 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -695,8 +695,8 @@ namespace ts.formatting { let tokenStart = sourceFile.getLineAndCharacterOfPosition(currentTokenInfo.token.pos); if (isTokenInRange) { let rangeHasError = rangeContainsError(currentTokenInfo.token); - // save prevStartLine since processRange will overwrite this value with current ones - let prevStartLine = previousRangeStartLine; + // save previousRange since processRange will overwrite this value with current one + let savePreviousRange = previousRange; lineAdded = processRange(currentTokenInfo.token, tokenStart, parent, childContextNode, dynamicIndentation); if (rangeHasError) { // do not indent comments\token if token range overlaps with some error @@ -707,7 +707,9 @@ namespace ts.formatting { indentToken = lineAdded; } else { - indentToken = lastTriviaWasNewLine && tokenStart.line !== prevStartLine; + // indent token only if end line of previous range does not match start line of the token + const prevEndLine = savePreviousRange && sourceFile.getLineAndCharacterOfPosition(savePreviousRange.end).line; + indentToken = lastTriviaWasNewLine && tokenStart.line !== prevEndLine; } } } diff --git a/tests/cases/fourslash/formatAfterMultilineComment.ts b/tests/cases/fourslash/formatAfterMultilineComment.ts new file mode 100644 index 00000000000..b795ab5c8c9 --- /dev/null +++ b/tests/cases/fourslash/formatAfterMultilineComment.ts @@ -0,0 +1,7 @@ +/// +/////*foo +////*/"123123"; + +format.document(); +verify.currentFileContentIs(`/*foo +*/"123123";`) From 7fc29d1b621aa942c139ffd2aa9e334e51f6fbba Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 21 Oct 2015 13:49:51 -0700 Subject: [PATCH 20/22] pre-initialize node fields in constructor --- src/compiler/core.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 55d2447dd2d..5aa5850e028 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -797,13 +797,13 @@ namespace ts { export let objectAllocator: ObjectAllocator = { getNodeConstructor: kind => { function Node() { + this.pos = -1; + this.end = -1; + this.flags = 0; + this.parent = undefined; } Node.prototype = { - kind: kind, - pos: -1, - end: -1, - flags: 0, - parent: undefined, + kind: kind }; return Node; }, From 7c064af052efe45da9ff3122470963e714eb5ab6 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 21 Oct 2015 15:33:30 -0700 Subject: [PATCH 21/22] initialize fields from constructor parameters --- src/compiler/core.ts | 14 ++++++-------- src/compiler/parser.ts | 14 +++++--------- src/compiler/types.ts | 1 + src/compiler/utilities.ts | 2 +- src/harness/harness.ts | 4 +++- src/services/services.ts | 14 ++++++-------- 6 files changed, 22 insertions(+), 27 deletions(-) diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 5aa5850e028..8fd92818c46 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -775,7 +775,7 @@ namespace ts { }; export interface ObjectAllocator { - getNodeConstructor(kind: SyntaxKind): new () => Node; + getNodeConstructor(kind: SyntaxKind): new (pos?: number, end?: number) => Node; getSymbolConstructor(): new (flags: SymbolFlags, name: string) => Symbol; getTypeConstructor(): new (checker: TypeChecker, flags: TypeFlags) => Type; getSignatureConstructor(): new (checker: TypeChecker) => Signature; @@ -796,15 +796,13 @@ namespace ts { export let objectAllocator: ObjectAllocator = { getNodeConstructor: kind => { - function Node() { - this.pos = -1; - this.end = -1; - this.flags = 0; + function Node(pos: number, end: number) { + this.pos = pos; + this.end = end; + this.flags = NodeFlags.None; this.parent = undefined; } - Node.prototype = { - kind: kind - }; + Node.prototype = { kind }; return Node; }, getSymbolConstructor: () => Symbol, diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index f021251f7d9..d2344722ed5 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -2,15 +2,15 @@ /// namespace ts { - let nodeConstructors = new Array Node>(SyntaxKind.Count); + let nodeConstructors = new Array Node>(SyntaxKind.Count); /* @internal */ export let parseTime = 0; - export function getNodeConstructor(kind: SyntaxKind): new () => Node { + export function getNodeConstructor(kind: SyntaxKind): new (pos?: number, end?: number) => Node { return nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)); } - export function createNode(kind: SyntaxKind): Node { - return new (getNodeConstructor(kind))(); + export function createNode(kind: SyntaxKind, pos?: number, end?: number): Node { + return new (getNodeConstructor(kind))(pos, end); } function visitNode(cbNode: (node: Node) => T, node: Node): T { @@ -993,14 +993,10 @@ namespace ts { function createNode(kind: SyntaxKind, pos?: number): Node { nodeCount++; - let node = new (nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)))(); if (!(pos >= 0)) { pos = scanner.getStartPos(); } - - node.pos = pos; - node.end = pos; - return node; + return new (nodeConstructors[kind] || (nodeConstructors[kind] = objectAllocator.getNodeConstructor(kind)))(pos, pos); } function finishNode(node: T, end?: number): T { diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2818ef04a48..8e0338696dd 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -361,6 +361,7 @@ namespace ts { } export const enum NodeFlags { + None = 0, Export = 0x00000001, // Declarations Ambient = 0x00000002, // Declarations Public = 0x00000010, // Property/Method diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 7c1b606dd00..eeca6a43d07 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1505,7 +1505,7 @@ namespace ts { } export function createSynthesizedNode(kind: SyntaxKind, startsOnNewLine?: boolean): Node { - let node = createNode(kind); + let node = createNode(kind, /* pos */ -1, /* end */ -1); node.startsOnNewLine = startsOnNewLine; return node; } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index a37b647a124..806f89be04d 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -274,7 +274,9 @@ namespace Utils { case "flags": // Print out flags with their enum names. - o[propertyName] = getNodeFlagName(n.flags); + if (n.flags) { + o[propertyName] = getNodeFlagName(n.flags); + } break; case "parserContextFlags": diff --git a/src/services/services.ts b/src/services/services.ts index 15c6e3df56f..ec29a89bdbc 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -174,9 +174,7 @@ namespace ts { let jsDocCompletionEntries: CompletionEntry[]; function createNode(kind: SyntaxKind, pos: number, end: number, flags: NodeFlags, parent?: Node): NodeObject { - let node = new (getNodeConstructor(kind))(); - node.pos = pos; - node.end = end; + let node = new (getNodeConstructor(kind))(pos, end); node.flags = flags; node.parent = parent; return node; @@ -7967,14 +7965,14 @@ namespace ts { function initializeServices() { objectAllocator = { getNodeConstructor: kind => { - function Node() { + function Node(pos: number, end: number) { + this.pos = pos; + this.end = end; + this.flags = NodeFlags.None; + this.parent = undefined; } let proto = kind === SyntaxKind.SourceFile ? new SourceFileObject() : new NodeObject(); proto.kind = kind; - proto.pos = -1; - proto.end = -1; - proto.flags = 0; - proto.parent = undefined; Node.prototype = proto; return Node; }, From 0834a29b15e2f04fbce56329271662c457dea481 Mon Sep 17 00:00:00 2001 From: Vladimir Matveev Date: Wed, 21 Oct 2015 17:04:18 -0700 Subject: [PATCH 22/22] pick default chrome location based on platform --- tests/webTestServer.ts | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/webTestServer.ts b/tests/webTestServer.ts index 6d3144eeaec..18a1f05d47b 100644 --- a/tests/webTestServer.ts +++ b/tests/webTestServer.ts @@ -5,6 +5,7 @@ import fs = require("fs"); import path = require("path"); import url = require("url"); import child_process = require("child_process"); +import os = require('os'); /// Command line processing /// @@ -263,7 +264,20 @@ http.createServer(function (req: http.ServerRequest, res: http.ServerResponse) { var browserPath: string; if ((browser && browser === 'chrome')) { - var defaultChromePath = "C:/Program Files (x86)/Google/Chrome/Application/chrome.exe"; + let defaultChromePath = ""; + switch (os.platform()) { + case "win32": + case "win64": + defaultChromePath = "C:/Program Files (x86)/Google/Chrome/Application/chrome.exe"; + break; + case "linux": + defaultChromePath = "/opt/google/chrome/chrome" + break; + default: + console.log(`default Chrome location is unknown for platform '${os.platform()}'`); + break; + } + if (fs.existsSync(defaultChromePath)) { browserPath = defaultChromePath; } else {