Skip to content

Commit 8103035

Browse files
committed
Merge branch 'master' of https://github.com/Microsoft/TypeScript into feature/eslint
2 parents f6a5006 + b3d55d0 commit 8103035

Some content is hidden

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

48 files changed

+2484
-1928
lines changed

Gulpfile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ task("runtests-parallel").flags = {
418418
" --workers=<number>": "The number of parallel workers to use.",
419419
" --timeout=<ms>": "Overrides the default test timeout.",
420420
" --built": "Compile using the built version of the compiler.",
421+
" --skipPercent=<number>": "Skip expensive tests with <percent> chance to miss an edit. Default 5%.",
421422
};
422423

423424
task("diff", () => exec(getDiffTool(), [refBaseline, localBaseline], { ignoreExitCode: true }));

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
"prex": "^0.4.3",
8888
"q": "latest",
8989
"remove-internal": "^2.9.2",
90+
"simple-git": "^1.113.0",
9091
"source-map-support": "latest",
9192
"through2": "latest",
9293
"travis-fold": "latest",
@@ -106,7 +107,8 @@
106107
"gulp": "gulp",
107108
"jake": "gulp",
108109
"lint": "gulp lint",
109-
"setup-hooks": "node scripts/link-hooks.js"
110+
"setup-hooks": "node scripts/link-hooks.js",
111+
"update-costly-tests": "node scripts/costly-tests.js"
110112
},
111113
"browser": {
112114
"fs": false,

scripts/build/options.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ module.exports = minimist(process.argv.slice(2), {
1414
"ru": "runners", "runner": "runners",
1515
"r": "reporter",
1616
"c": "colors", "color": "colors",
17+
"skip-percent": "skipPercent",
1718
"w": "workers",
1819
"f": "fix"
1920
},
@@ -69,4 +70,4 @@ if (module.exports.built) {
6970
*
7071
* @typedef {import("minimist").ParsedArgs & TypedOptions} CommandLineOptions
7172
*/
72-
void 0;
73+
void 0;

scripts/build/tests.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode,
3131
const inspect = cmdLineOptions.inspect;
3232
const runners = cmdLineOptions.runners;
3333
const light = cmdLineOptions.light;
34+
const skipPercent = process.env.CI === "true" ? 0 : cmdLineOptions.skipPercent;
3435
const stackTraceLimit = cmdLineOptions.stackTraceLimit;
3536
const testConfigFile = "test.config";
3637
const failed = cmdLineOptions.failed;
@@ -62,8 +63,8 @@ async function runConsoleTests(runJs, defaultReporter, runInParallel, watchMode,
6263
testTimeout = 400000;
6364
}
6465

65-
if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed) {
66-
writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout, keepFailed);
66+
if (tests || runners || light || testTimeout || taskConfigsFolder || keepFailed || skipPercent !== undefined) {
67+
writeTestConfigFile(tests, runners, light, skipPercent, taskConfigsFolder, workerCount, stackTraceLimit, testTimeout, keepFailed);
6768
}
6869

6970
const colors = cmdLineOptions.colors;
@@ -158,17 +159,19 @@ exports.cleanTestDirs = cleanTestDirs;
158159
* @param {string} tests
159160
* @param {string} runners
160161
* @param {boolean} light
162+
* @param {string} skipPercent
161163
* @param {string} [taskConfigsFolder]
162164
* @param {string | number} [workerCount]
163165
* @param {string} [stackTraceLimit]
164166
* @param {string | number} [timeout]
165167
* @param {boolean} [keepFailed]
166168
*/
167-
function writeTestConfigFile(tests, runners, light, taskConfigsFolder, workerCount, stackTraceLimit, timeout, keepFailed) {
169+
function writeTestConfigFile(tests, runners, light, skipPercent, taskConfigsFolder, workerCount, stackTraceLimit, timeout, keepFailed) {
168170
const testConfigContents = JSON.stringify({
169171
test: tests ? [tests] : undefined,
170172
runners: runners ? runners.split(",") : undefined,
171173
light,
174+
skipPercent,
172175
workerCount,
173176
stackTraceLimit,
174177
taskConfigsFolder,

scripts/costly-tests.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// @ts-check
2+
const fs = require("fs");
3+
const git = require('simple-git/promise')('.')
4+
const readline = require('readline')
5+
6+
/** @typedef {{ [s: string]: number}} Histogram */
7+
8+
async function main() {
9+
/** @type {Histogram} */
10+
const edits = Object.create(null)
11+
/** @type {Histogram} */
12+
const perf = JSON.parse(fs.readFileSync('.parallelperf.json', 'utf8'))
13+
14+
await collectCommits(git, "release-2.3", "master", /*author*/ undefined, files => fillMap(files, edits))
15+
16+
const totalTime = Object.values(perf).reduce((n,m) => n + m, 0)
17+
const untouched = Object.values(perf).length - Object.values(edits).length
18+
const totalEdits = Object.values(edits).reduce((n,m) => n + m, 0) + untouched + Object.values(edits).length
19+
20+
let i = 0
21+
/** @type {{ name: string, time: number, edits: number, cost: number }[]} */
22+
let data = []
23+
for (const k in perf) {
24+
const otherk = k.replace(/tsrunner-[a-z-]+?:\/\//, '')
25+
const percentTime = perf[k] / totalTime
26+
const percentHits = (1 + (edits[otherk] || 0)) / totalEdits
27+
const cost = 5 + Math.log(percentTime / percentHits)
28+
data.push({ name: otherk, time: perf[k], edits: 1 + (edits[otherk] || 0), cost})
29+
if (edits[otherk])
30+
i++
31+
}
32+
const output = {
33+
totalTime,
34+
totalEdits,
35+
data: data.sort((x,y) => y.cost - x.cost).map(x => ({ ...x, cost: x.cost.toFixed(2) }))
36+
}
37+
38+
fs.writeFileSync('tests/.test-cost.json', JSON.stringify(output), 'utf8')
39+
}
40+
41+
main().catch(e => {
42+
console.log(e);
43+
process.exit(1);
44+
})
45+
46+
/**
47+
* @param {string[]} files
48+
* @param {Histogram} histogram
49+
*/
50+
function fillMap(files, histogram) {
51+
// keep edits to test cases (but not /users), and not file moves
52+
const tests = files.filter(f => f.startsWith('tests/cases/') && !f.startsWith('tests/cases/user') && !/=>/.test(f))
53+
for (const test of tests) {
54+
histogram[test] = (histogram[test] || 0) + 1
55+
}
56+
}
57+
58+
/**
59+
* @param {string} s
60+
*/
61+
function isSquashMergeMessage(s) {
62+
return /\(#[0-9]+\)$/.test(s)
63+
}
64+
65+
/**
66+
* @param {string} s
67+
*/
68+
function isMergeCommit(s) {
69+
return /Merge pull request #[0-9]+/.test(s)
70+
}
71+
72+
/**
73+
* @param {string} s
74+
*/
75+
function parseFiles(s) {
76+
const lines = s.split('\n')
77+
// Note that slice(2) only works for merge commits, which have an empty newline after the title
78+
return lines.slice(2, lines.length - 2).map(line => line.split("|")[0].trim())
79+
}
80+
81+
/**
82+
* @param {import('simple-git/promise').SimpleGit} git
83+
* @param {string} from
84+
* @param {string} to
85+
* @param {string | undefined} author - only include commits from this author
86+
* @param {(files: string[]) => void} update
87+
*/
88+
async function collectCommits(git, from, to, author, update) {
89+
let i = 0
90+
for (const commit of (await git.log({ from, to })).all) {
91+
i++
92+
if ((!author || commit.author_name === author) && isMergeCommit(commit.message) || isSquashMergeMessage(commit.message)) {
93+
readline.clearLine(process.stdout, /*left*/ -1)
94+
readline.cursorTo(process.stdout, 0)
95+
process.stdout.write(i + ": " + commit.date)
96+
const files = parseFiles(await git.show([commit.hash, "--stat=1000,960,40", "--pretty=oneline"]))
97+
update(files)
98+
}
99+
}
100+
}
101+
102+
103+

scripts/update-experimental-branches.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ async function main() {
1818
if (!prnums.length) {
1919
return; // No enlisted PRs, nothing to update
2020
}
21-
if (!prnums.some(n => n === triggeredPR)) {
21+
if (triggeredPR && !prnums.some(n => n === triggeredPR)) {
2222
return; // Only have work to do for enlisted PRs
2323
}
2424
console.log(`Performing experimental branch updating and merging for pull requests ${prnums.join(", ")}`);
@@ -51,9 +51,8 @@ async function main() {
5151
issue_number: num,
5252
body: `This PR is configured as an experiment, and currently has rebase conflicts with master - please rebase onto master and fix the conflicts.`
5353
});
54-
throw new Error(`Rebase conflict detected in PR ${num} with master`);
5554
}
56-
return; // A PR is currently in conflict, give up
55+
throw new Error(`Rebase conflict detected in PR ${num} with master`); // A PR is currently in conflict, give up
5756
}
5857
runSequence([
5958
["git", ["fetch", "origin", `pull/${num}/head:${num}`]],

src/compiler/checker.ts

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10099,16 +10099,21 @@ namespace ts {
1009910099
return false;
1010010100
}
1010110101

10102-
function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, suppressNoImplicitAnyError: boolean, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) {
10102+
function getPropertyNameFromIndex(indexType: Type, accessNode: StringLiteral | Identifier | ObjectBindingPattern | ArrayBindingPattern | ComputedPropertyName | NumericLiteral | IndexedAccessTypeNode | ElementAccessExpression | SyntheticExpression | undefined) {
1010310103
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
10104-
const propName = isTypeUsableAsPropertyName(indexType) ?
10104+
return isTypeUsableAsPropertyName(indexType) ?
1010510105
getPropertyNameFromType(indexType) :
1010610106
accessExpression && checkThatExpressionIsProperSymbolReference(accessExpression.argumentExpression, indexType, /*reportError*/ false) ?
1010710107
getPropertyNameForKnownSymbolName(idText((<PropertyAccessExpression>accessExpression.argumentExpression).name)) :
1010810108
accessNode && isPropertyName(accessNode) ?
1010910109
// late bound names are handled in the first branch, so here we only need to handle normal names
1011010110
getPropertyNameForPropertyNameNode(accessNode) :
1011110111
undefined;
10112+
}
10113+
10114+
function getPropertyTypeForIndexType(originalObjectType: Type, objectType: Type, indexType: Type, fullIndexType: Type, suppressNoImplicitAnyError: boolean, accessNode: ElementAccessExpression | IndexedAccessTypeNode | PropertyName | BindingName | SyntheticExpression | undefined, accessFlags: AccessFlags) {
10115+
const accessExpression = accessNode && accessNode.kind === SyntaxKind.ElementAccessExpression ? accessNode : undefined;
10116+
const propName = getPropertyNameFromIndex(indexType, accessNode);
1011210117
if (propName !== undefined) {
1011310118
const prop = getPropertyOfType(objectType, propName);
1011410119
if (prop) {
@@ -13314,7 +13319,14 @@ namespace ts {
1331413319
}
1331513320
}
1331613321
else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) {
13317-
return isRelatedTo(getIndexTypeOfType(source, IndexKind.Number) || anyType, getIndexTypeOfType(target, IndexKind.Number) || anyType, reportErrors);
13322+
if (relation !== identityRelation) {
13323+
return isRelatedTo(getIndexTypeOfType(source, IndexKind.Number) || anyType, getIndexTypeOfType(target, IndexKind.Number) || anyType, reportErrors);
13324+
}
13325+
else {
13326+
// By flags alone, we know that the `target` is a readonly array while the source is a normal array or tuple
13327+
// or `target` is an array and source is a tuple - in both cases the types cannot be identical, by construction
13328+
return Ternary.False;
13329+
}
1331813330
}
1331913331
// Consider a fresh empty object literal type "closed" under the subtype relationship - this way `{} <- {[idx: string]: any} <- fresh({})`
1332013332
// and not `{} <- fresh({}) <- {[idx: string]: any}`
@@ -20018,6 +20030,7 @@ namespace ts {
2001820030
}
2001920031

2002020032
function checkJsxExpression(node: JsxExpression, checkMode?: CheckMode) {
20033+
checkGrammarJsxExpression(node);
2002120034
if (node.expression) {
2002220035
const type = checkExpression(node.expression, checkMode);
2002320036
if (node.dotDotDotToken && type !== anyType && !isArrayType(type)) {
@@ -22494,7 +22507,10 @@ namespace ts {
2249422507
case SyntaxKind.ElementAccessExpression:
2249522508
const expr = (<PropertyAccessExpression | ElementAccessExpression>node).expression;
2249622509
if (isIdentifier(expr)) {
22497-
const symbol = getSymbolAtLocation(expr);
22510+
let symbol = getSymbolAtLocation(expr);
22511+
if (symbol && symbol.flags & SymbolFlags.Alias) {
22512+
symbol = resolveAlias(symbol);
22513+
}
2249822514
return !!(symbol && (symbol.flags & SymbolFlags.Enum) && getEnumKind(symbol) === EnumKind.Literal);
2249922515
}
2250022516
}
@@ -25321,7 +25337,7 @@ namespace ts {
2532125337
forEach(node.types, checkSourceElement);
2532225338
}
2532325339

25324-
function checkIndexedAccessIndexType(type: Type, accessNode: Node) {
25340+
function checkIndexedAccessIndexType(type: Type, accessNode: IndexedAccessTypeNode | ElementAccessExpression) {
2532525341
if (!(type.flags & TypeFlags.IndexedAccess)) {
2532625342
return type;
2532725343
}
@@ -25337,9 +25353,20 @@ namespace ts {
2533725353
}
2533825354
// Check if we're indexing with a numeric type and if either object or index types
2533925355
// is a generic type with a constraint that has a numeric index signature.
25340-
if (getIndexInfoOfType(getApparentType(objectType), IndexKind.Number) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
25356+
const apparentObjectType = getApparentType(objectType);
25357+
if (getIndexInfoOfType(apparentObjectType, IndexKind.Number) && isTypeAssignableToKind(indexType, TypeFlags.NumberLike)) {
2534125358
return type;
2534225359
}
25360+
if (isGenericObjectType(objectType)) {
25361+
const propertyName = getPropertyNameFromIndex(indexType, accessNode);
25362+
if (propertyName) {
25363+
const propertySymbol = forEachType(apparentObjectType, t => getPropertyOfType(t, propertyName));
25364+
if (propertySymbol && getDeclarationModifierFlagsFromSymbol(propertySymbol) & ModifierFlags.NonPublicAccessibilityModifier) {
25365+
error(accessNode, Diagnostics.Private_or_protected_member_0_cannot_be_accessed_on_a_type_parameter, unescapeLeadingUnderscores(propertyName));
25366+
return errorType;
25367+
}
25368+
}
25369+
}
2534325370
error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType));
2534425371
return errorType;
2534525372
}
@@ -31889,6 +31916,12 @@ namespace ts {
3188931916
}
3189031917
}
3189131918

31919+
function checkGrammarJsxExpression(node: JsxExpression) {
31920+
if (node.expression && isCommaSequence(node.expression)) {
31921+
return grammarErrorOnNode(node.expression, Diagnostics.JSX_expressions_may_not_use_the_comma_operator_Did_you_mean_to_write_an_array);
31922+
}
31923+
}
31924+
3189231925
function checkGrammarForInOrForOfStatement(forInOrOfStatement: ForInOrOfStatement): boolean {
3189331926
if (checkGrammarStatementInAmbientContext(forInOrOfStatement)) {
3189431927
return true;

src/compiler/diagnosticMessages.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2963,6 +2963,10 @@
29632963
"category": "Error",
29642964
"code": 4104
29652965
},
2966+
"Private or protected member '{0}' cannot be accessed on a type parameter.": {
2967+
"category": "Error",
2968+
"code": 4105
2969+
},
29662970

29672971
"The current host does not support the '{0}' option.": {
29682972
"category": "Error",
@@ -5005,5 +5009,9 @@
50055009
"Classes may not have a field named 'constructor'.": {
50065010
"category": "Error",
50075011
"code": 18006
5012+
},
5013+
"JSX expressions may not use the comma operator. Did you mean to write an array?": {
5014+
"category": "Error",
5015+
"code": 18007
50085016
}
50095017
}

src/compiler/parser.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4430,14 +4430,18 @@ namespace ts {
44304430

44314431
if (token() !== SyntaxKind.CloseBraceToken) {
44324432
node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
4433-
node.expression = parseAssignmentExpressionOrHigher();
4433+
// Only an AssignmentExpression is valid here per the JSX spec,
4434+
// but we can unambiguously parse a comma sequence and provide
4435+
// a better error message in grammar checking.
4436+
node.expression = parseExpression();
44344437
}
44354438
if (inExpressionContext) {
44364439
parseExpected(SyntaxKind.CloseBraceToken);
44374440
}
44384441
else {
4439-
parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false);
4440-
scanJsxText();
4442+
if (parseExpected(SyntaxKind.CloseBraceToken, /*message*/ undefined, /*shouldAdvance*/ false)) {
4443+
scanJsxText();
4444+
}
44414445
}
44424446

44434447
return finishNode(node);

src/harness/fourslash.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,21 @@ namespace FourSlash {
583583
});
584584
}
585585

586+
public verifyErrorExistsAtRange(range: Range, code: number, expectedMessage?: string) {
587+
const span = ts.createTextSpanFromRange(range);
588+
const hasMatchingError = ts.some(
589+
this.getDiagnostics(range.fileName),
590+
({ code, messageText, start, length }) =>
591+
code === code &&
592+
(!expectedMessage || expectedMessage === messageText) &&
593+
ts.isNumber(start) && ts.isNumber(length) &&
594+
ts.textSpansEqual(span, { start, length }));
595+
596+
if (!hasMatchingError) {
597+
this.raiseError(`No error with code ${code} found at provided range.`);
598+
}
599+
}
600+
586601
public verifyNumberOfErrorsInCurrentFile(expected: number) {
587602
const errors = this.getDiagnostics(this.activeFile.fileName);
588603
const actual = errors.length;
@@ -4009,6 +4024,10 @@ namespace FourSlashInterface {
40094024
this.state.verifyNoErrors();
40104025
}
40114026

4027+
public errorExistsAtRange(range: FourSlash.Range, code: number, message?: string) {
4028+
this.state.verifyErrorExistsAtRange(range, code, message);
4029+
}
4030+
40124031
public numberOfErrorsInCurrentFile(expected: number) {
40134032
this.state.verifyNumberOfErrorsInCurrentFile(expected);
40144033
}

0 commit comments

Comments
 (0)