Merge branch 'master' into weakType

It's probably fine!
This commit is contained in:
Nathan Shively-Sanders
2017-05-19 08:54:25 -07:00
23327 changed files with 1401590 additions and 445289 deletions
+2780 -247
View File
File diff suppressed because it is too large Load Diff
+15564 -6452
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+429
View File
@@ -0,0 +1,429 @@
/// <reference path="sourcemap.ts" />
/* @internal */
namespace ts {
export interface CommentWriter {
reset(): void;
setSourceFile(sourceFile: SourceFile): void;
setWriter(writer: EmitTextWriter): void;
emitNodeWithComments(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void;
emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void): void;
emitTrailingCommentsOfPosition(pos: number): void;
emitLeadingCommentsOfPosition(pos: number): void;
}
export function createCommentWriter(printerOptions: PrinterOptions, emitPos: ((pos: number) => void) | undefined): CommentWriter {
const extendedDiagnostics = printerOptions.extendedDiagnostics;
const newLine = getNewLineCharacter(printerOptions);
let writer: EmitTextWriter;
let containerPos = -1;
let containerEnd = -1;
let declarationListContainerEnd = -1;
let currentSourceFile: SourceFile;
let currentText: string;
let currentLineMap: number[];
let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[];
let hasWrittenComment = false;
let disabled: boolean = printerOptions.removeComments;
return {
reset,
setWriter,
setSourceFile,
emitNodeWithComments,
emitBodyWithDetachedComments,
emitTrailingCommentsOfPosition,
emitLeadingCommentsOfPosition,
};
function emitNodeWithComments(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) {
if (disabled) {
emitCallback(hint, node);
return;
}
if (node) {
hasWrittenComment = false;
const emitNode = node.emitNode;
const emitFlags = emitNode && emitNode.flags;
const { pos, end } = emitNode && emitNode.commentRange || node;
if ((pos < 0 && end < 0) || (pos === end)) {
// Both pos and end are synthesized, so just emit the node without comments.
emitNodeWithSynthesizedComments(hint, node, emitNode, emitFlags, emitCallback);
}
else {
if (extendedDiagnostics) {
performance.mark("preEmitNodeWithComment");
}
const isEmittedNode = node.kind !== SyntaxKind.NotEmittedStatement;
const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0;
const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0;
// Emit leading comments if the position is not synthesized and the node
// has not opted out from emitting leading comments.
if (!skipLeadingComments) {
emitLeadingComments(pos, isEmittedNode);
}
// Save current container state on the stack.
const savedContainerPos = containerPos;
const savedContainerEnd = containerEnd;
const savedDeclarationListContainerEnd = declarationListContainerEnd;
if (!skipLeadingComments) {
containerPos = pos;
}
if (!skipTrailingComments) {
containerEnd = end;
// To avoid invalid comment emit in a down-level binding pattern, we
// keep track of the last declaration list container's end
if (node.kind === SyntaxKind.VariableDeclarationList) {
declarationListContainerEnd = end;
}
}
if (extendedDiagnostics) {
performance.measure("commentTime", "preEmitNodeWithComment");
}
emitNodeWithSynthesizedComments(hint, node, emitNode, emitFlags, emitCallback);
if (extendedDiagnostics) {
performance.mark("postEmitNodeWithComment");
}
// Restore previous container state.
containerPos = savedContainerPos;
containerEnd = savedContainerEnd;
declarationListContainerEnd = savedDeclarationListContainerEnd;
// Emit trailing comments if the position is not synthesized and the node
// has not opted out from emitting leading comments and is an emitted node.
if (!skipTrailingComments && isEmittedNode) {
emitTrailingComments(end);
}
if (extendedDiagnostics) {
performance.measure("commentTime", "postEmitNodeWithComment");
}
}
}
}
function emitNodeWithSynthesizedComments(hint: EmitHint, node: Node, emitNode: EmitNode, emitFlags: EmitFlags, emitCallback: (hint: EmitHint, node: Node) => void) {
const leadingComments = emitNode && emitNode.leadingComments;
if (some(leadingComments)) {
if (extendedDiagnostics) {
performance.mark("preEmitNodeWithSynthesizedComments");
}
forEach(leadingComments, emitLeadingSynthesizedComment);
if (extendedDiagnostics) {
performance.measure("commentTime", "preEmitNodeWithSynthesizedComments");
}
}
emitNodeWithNestedComments(hint, node, emitFlags, emitCallback);
const trailingComments = emitNode && emitNode.trailingComments;
if (some(trailingComments)) {
if (extendedDiagnostics) {
performance.mark("postEmitNodeWithSynthesizedComments");
}
forEach(trailingComments, emitTrailingSynthesizedComment);
if (extendedDiagnostics) {
performance.measure("commentTime", "postEmitNodeWithSynthesizedComments");
}
}
}
function emitLeadingSynthesizedComment(comment: SynthesizedComment) {
if (comment.kind === SyntaxKind.SingleLineCommentTrivia) {
writer.writeLine();
}
writeSynthesizedComment(comment);
if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) {
writer.writeLine();
}
else {
writer.write(" ");
}
}
function emitTrailingSynthesizedComment(comment: SynthesizedComment) {
if (!writer.isAtStartOfLine()) {
writer.write(" ");
}
writeSynthesizedComment(comment);
if (comment.hasTrailingNewLine) {
writer.writeLine();
}
}
function writeSynthesizedComment(comment: SynthesizedComment) {
const text = formatSynthesizedComment(comment);
const lineMap = comment.kind === SyntaxKind.MultiLineCommentTrivia ? computeLineStarts(text) : undefined;
writeCommentRange(text, lineMap, writer, 0, text.length, newLine);
}
function formatSynthesizedComment(comment: SynthesizedComment) {
return comment.kind === SyntaxKind.MultiLineCommentTrivia
? `/*${comment.text}*/`
: `//${comment.text}`;
}
function emitNodeWithNestedComments(hint: EmitHint, node: Node, emitFlags: EmitFlags, emitCallback: (hint: EmitHint, node: Node) => void) {
if (emitFlags & EmitFlags.NoNestedComments) {
disabled = true;
emitCallback(hint, node);
disabled = false;
}
else {
emitCallback(hint, node);
}
}
function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) {
if (extendedDiagnostics) {
performance.mark("preEmitBodyWithDetachedComments");
}
const { pos, end } = detachedRange;
const emitFlags = getEmitFlags(node);
const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0;
const skipTrailingComments = disabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0;
if (!skipLeadingComments) {
emitDetachedCommentsAndUpdateCommentsInfo(detachedRange);
}
if (extendedDiagnostics) {
performance.measure("commentTime", "preEmitBodyWithDetachedComments");
}
if (emitFlags & EmitFlags.NoNestedComments && !disabled) {
disabled = true;
emitCallback(node);
disabled = false;
}
else {
emitCallback(node);
}
if (extendedDiagnostics) {
performance.mark("beginEmitBodyWithDetachedCommetns");
}
if (!skipTrailingComments) {
emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true);
if (hasWrittenComment && !writer.isAtStartOfLine()) {
writer.writeLine();
}
}
if (extendedDiagnostics) {
performance.measure("commentTime", "beginEmitBodyWithDetachedCommetns");
}
}
function emitLeadingComments(pos: number, isEmittedNode: boolean) {
hasWrittenComment = false;
if (isEmittedNode) {
forEachLeadingCommentToEmit(pos, emitLeadingComment);
}
else if (pos === 0) {
// If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node,
// unless it is a triple slash comment at the top of the file.
// For Example:
// /// <reference-path ...>
// declare var x;
// /// <reference-path ...>
// interface F {}
// The first /// will NOT be removed while the second one will be removed even though both node will not be emitted
forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment);
}
}
function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) {
if (isTripleSlashComment(commentPos, commentEnd)) {
emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos);
}
}
function emitLeadingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) {
if (!hasWrittenComment) {
emitNewLineBeforeLeadingCommentOfPosition(currentLineMap, writer, rangePos, commentPos);
hasWrittenComment = true;
}
// Leading comments are emitted at /*leading comment1 */space/*leading comment*/space
if (emitPos) emitPos(commentPos);
writeCommentRange(currentText, currentLineMap, writer, commentPos, commentEnd, newLine);
if (emitPos) emitPos(commentEnd);
if (hasTrailingNewLine) {
writer.writeLine();
}
else {
writer.write(" ");
}
}
function emitLeadingCommentsOfPosition(pos: number) {
if (disabled || pos === -1) {
return;
}
emitLeadingComments(pos, /*isEmittedNode*/ true);
}
function emitTrailingComments(pos: number) {
forEachTrailingCommentToEmit(pos, emitTrailingComment);
}
function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) {
// trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/
if (!writer.isAtStartOfLine()) {
writer.write(" ");
}
if (emitPos) emitPos(commentPos);
writeCommentRange(currentText, currentLineMap, writer, commentPos, commentEnd, newLine);
if (emitPos) emitPos(commentEnd);
if (hasTrailingNewLine) {
writer.writeLine();
}
}
function emitTrailingCommentsOfPosition(pos: number) {
if (disabled) {
return;
}
if (extendedDiagnostics) {
performance.mark("beforeEmitTrailingCommentsOfPosition");
}
forEachTrailingCommentToEmit(pos, emitTrailingCommentOfPosition);
if (extendedDiagnostics) {
performance.measure("commentTime", "beforeEmitTrailingCommentsOfPosition");
}
}
function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) {
// trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space
if (emitPos) emitPos(commentPos);
writeCommentRange(currentText, currentLineMap, writer, commentPos, commentEnd, newLine);
if (emitPos) emitPos(commentEnd);
if (hasTrailingNewLine) {
writer.writeLine();
}
else {
writer.write(" ");
}
}
function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) {
// Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments
if (containerPos === -1 || pos !== containerPos) {
if (hasDetachedComments(pos)) {
forEachLeadingCommentWithoutDetachedComments(cb);
}
else {
forEachLeadingCommentRange(currentText, pos, cb, /*state*/ pos);
}
}
}
function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) {
// Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments
if (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd)) {
forEachTrailingCommentRange(currentText, end, cb);
}
}
function reset() {
currentSourceFile = undefined;
currentText = undefined;
currentLineMap = undefined;
detachedCommentsInfo = undefined;
}
function setWriter(output: EmitTextWriter): void {
writer = output;
}
function setSourceFile(sourceFile: SourceFile) {
currentSourceFile = sourceFile;
currentText = currentSourceFile.text;
currentLineMap = getLineStarts(currentSourceFile);
detachedCommentsInfo = undefined;
}
function hasDetachedComments(pos: number) {
return detachedCommentsInfo !== undefined && lastOrUndefined(detachedCommentsInfo).nodePos === pos;
}
function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) {
// get the leading comments from detachedPos
const pos = lastOrUndefined(detachedCommentsInfo).detachedCommentEndPos;
if (detachedCommentsInfo.length - 1) {
detachedCommentsInfo.pop();
}
else {
detachedCommentsInfo = undefined;
}
forEachLeadingCommentRange(currentText, pos, cb, /*state*/ pos);
}
function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) {
const currentDetachedCommentInfo = emitDetachedComments(currentText, currentLineMap, writer, writeComment, range, newLine, disabled);
if (currentDetachedCommentInfo) {
if (detachedCommentsInfo) {
detachedCommentsInfo.push(currentDetachedCommentInfo);
}
else {
detachedCommentsInfo = [currentDetachedCommentInfo];
}
}
}
function writeComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) {
if (emitPos) emitPos(commentPos);
writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine);
if (emitPos) emitPos(commentEnd);
}
/**
* Determine if the given comment is a triple-slash
*
* @return true if the comment is a triple-slash comment else false
*/
function isTripleSlashComment(commentPos: number, commentEnd: number) {
// Verify this is /// comment, but do the regexp match only when we first can find /// in the comment text
// so that we don't end up computing comment string and doing match for all // comments
if (currentText.charCodeAt(commentPos + 1) === CharacterCodes.slash &&
commentPos + 2 < commentEnd &&
currentText.charCodeAt(commentPos + 2) === CharacterCodes.slash) {
const textSubStr = currentText.substring(commentPos, commentEnd);
return textSubStr.match(fullTripleSlashReferencePathRegEx) ||
textSubStr.match(fullTripleSlashAMDReferencePathRegEx) ?
true : false;
}
return false;
}
}
}
+1865 -245
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+3002 -6676
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+2431 -1403
View File
File diff suppressed because it is too large Load Diff
+95
View File
@@ -0,0 +1,95 @@
/*@internal*/
namespace ts {
declare const performance: { now?(): number } | undefined;
/** Gets a timestamp with (at least) ms resolution */
export const timestamp = typeof performance !== "undefined" && performance.now ? () => performance.now() : Date.now ? Date.now : () => +(new Date());
}
/*@internal*/
/** Performance measurements for the compiler. */
namespace ts.performance {
declare const onProfilerEvent: { (markName: string): void; profiler: boolean; };
const profilerEvent = typeof onProfilerEvent === "function" && onProfilerEvent.profiler === true
? onProfilerEvent
: (_markName: string) => { };
let enabled = false;
let profilerStart = 0;
let counts: Map<number>;
let marks: Map<number>;
let measures: Map<number>;
/**
* Marks a performance event.
*
* @param markName The name of the mark.
*/
export function mark(markName: string) {
if (enabled) {
marks.set(markName, timestamp());
counts.set(markName, (counts.get(markName) || 0) + 1);
profilerEvent(markName);
}
}
/**
* Adds a performance measurement with the specified name.
*
* @param measureName The name of the performance measurement.
* @param startMarkName The name of the starting mark. If not supplied, the point at which the
* profiler was enabled is used.
* @param endMarkName The name of the ending mark. If not supplied, the current timestamp is
* used.
*/
export function measure(measureName: string, startMarkName?: string, endMarkName?: string) {
if (enabled) {
const end = endMarkName && marks.get(endMarkName) || timestamp();
const start = startMarkName && marks.get(startMarkName) || profilerStart;
measures.set(measureName, (measures.get(measureName) || 0) + (end - start));
}
}
/**
* Gets the number of times a marker was encountered.
*
* @param markName The name of the mark.
*/
export function getCount(markName: string) {
return counts && counts.get(markName) || 0;
}
/**
* Gets the total duration of all measurements with the supplied name.
*
* @param measureName The name of the measure whose durations should be accumulated.
*/
export function getDuration(measureName: string) {
return measures && measures.get(measureName) || 0;
}
/**
* Iterate over each measure, performing some action
*
* @param cb The action to perform for each measure
*/
export function forEachMeasure(cb: (measureName: string, duration: number) => void) {
measures.forEach((measure, key) => {
cb(key, measure);
});
}
/** Enables (and resets) performance measurements for the compiler. */
export function enable() {
counts = createMap<number>();
marks = createMap<number>();
measures = createMap<number>();
enabled = true;
profilerStart = timestamp();
}
/** Disables performance measurements for the compiler. */
export function disable() {
enabled = false;
}
}
+1587 -325
View File
File diff suppressed because it is too large Load Diff
+640 -302
View File
File diff suppressed because one or more lines are too long
+495
View File
@@ -0,0 +1,495 @@
/// <reference path="checker.ts"/>
/* @internal */
namespace ts {
export interface SourceMapWriter {
/**
* Initialize the SourceMapWriter for a new output file.
*
* @param filePath The path to the generated output file.
* @param sourceMapFilePath The path to the output source map file.
* @param sourceFileOrBundle The input source file or bundle for the program.
*/
initialize(filePath: string, sourceMapFilePath: string, sourceFileOrBundle: SourceFile | Bundle): void;
/**
* Reset the SourceMapWriter to an empty state.
*/
reset(): void;
/**
* Set the current source file.
*
* @param sourceFile The source file.
*/
setSourceFile(sourceFile: SourceFile): void;
/**
* Emits a mapping.
*
* If the position is synthetic (undefined or a negative value), no mapping will be
* created.
*
* @param pos The position.
*/
emitPos(pos: number): void;
/**
* Emits a node with possible leading and trailing source maps.
*
* @param hint The current emit context
* @param node The node to emit.
* @param emitCallback The callback used to emit the node.
*/
emitNodeWithSourceMap(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void;
/**
* Emits a token of a node node with possible leading and trailing source maps.
*
* @param node The node containing the token.
* @param token The token to emit.
* @param tokenStartPos The start pos of the token.
* @param emitCallback The callback used to emit the token.
*/
emitTokenWithSourceMap(node: Node, token: SyntaxKind, tokenStartPos: number, emitCallback: (token: SyntaxKind, tokenStartPos: number) => number): number;
/**
* Gets the text for the source map.
*/
getText(): string;
/**
* Gets the SourceMappingURL for the source map.
*/
getSourceMappingURL(): string;
/**
* Gets test data for source maps.
*/
getSourceMapData(): SourceMapData;
}
// Used for initialize lastEncodedSourceMapSpan and reset lastEncodedSourceMapSpan when updateLastEncodedAndRecordedSpans
const defaultLastEncodedSourceMapSpan: SourceMapSpan = {
emittedLine: 1,
emittedColumn: 1,
sourceLine: 1,
sourceColumn: 1,
sourceIndex: 0
};
export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter {
const compilerOptions = host.getCompilerOptions();
const extendedDiagnostics = compilerOptions.extendedDiagnostics;
let currentSourceFile: SourceFile;
let currentSourceText: string;
let sourceMapDir: string; // The directory in which sourcemap will be
// Current source map file and its index in the sources list
let sourceMapSourceIndex: number;
// Last recorded and encoded spans
let lastRecordedSourceMapSpan: SourceMapSpan;
let lastEncodedSourceMapSpan: SourceMapSpan;
let lastEncodedNameIndex: number;
// Source map data
let sourceMapData: SourceMapData;
let disabled: boolean = !(compilerOptions.sourceMap || compilerOptions.inlineSourceMap);
return {
initialize,
reset,
getSourceMapData: () => sourceMapData,
setSourceFile,
emitPos,
emitNodeWithSourceMap,
emitTokenWithSourceMap,
getText,
getSourceMappingURL,
};
/**
* Initialize the SourceMapWriter for a new output file.
*
* @param filePath The path to the generated output file.
* @param sourceMapFilePath The path to the output source map file.
* @param sourceFileOrBundle The input source file or bundle for the program.
*/
function initialize(filePath: string, sourceMapFilePath: string, sourceFileOrBundle: SourceFile | Bundle) {
if (disabled) {
return;
}
if (sourceMapData) {
reset();
}
currentSourceFile = undefined;
currentSourceText = undefined;
// Current source map file and its index in the sources list
sourceMapSourceIndex = -1;
// Last recorded and encoded spans
lastRecordedSourceMapSpan = undefined;
lastEncodedSourceMapSpan = defaultLastEncodedSourceMapSpan;
lastEncodedNameIndex = 0;
// Initialize source map data
sourceMapData = {
sourceMapFilePath: sourceMapFilePath,
jsSourceMappingURL: !compilerOptions.inlineSourceMap ? getBaseFileName(normalizeSlashes(sourceMapFilePath)) : undefined,
sourceMapFile: getBaseFileName(normalizeSlashes(filePath)),
sourceMapSourceRoot: compilerOptions.sourceRoot || "",
sourceMapSources: [],
inputSourceFileNames: [],
sourceMapNames: [],
sourceMapMappings: "",
sourceMapSourcesContent: compilerOptions.inlineSources ? [] : undefined,
sourceMapDecodedMappings: []
};
// Normalize source root and make sure it has trailing "/" so that it can be used to combine paths with the
// relative paths of the sources list in the sourcemap
sourceMapData.sourceMapSourceRoot = ts.normalizeSlashes(sourceMapData.sourceMapSourceRoot);
if (sourceMapData.sourceMapSourceRoot.length && sourceMapData.sourceMapSourceRoot.charCodeAt(sourceMapData.sourceMapSourceRoot.length - 1) !== CharacterCodes.slash) {
sourceMapData.sourceMapSourceRoot += directorySeparator;
}
if (compilerOptions.mapRoot) {
sourceMapDir = normalizeSlashes(compilerOptions.mapRoot);
if (sourceFileOrBundle.kind === SyntaxKind.SourceFile) { // emitting single module file
// For modules or multiple emit files the mapRoot will have directory structure like the sources
// So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map
sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFileOrBundle, host, sourceMapDir));
}
if (!isRootedDiskPath(sourceMapDir) && !isUrl(sourceMapDir)) {
// The relative paths are relative to the common directory
sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir);
sourceMapData.jsSourceMappingURL = getRelativePathToDirectoryOrUrl(
getDirectoryPath(normalizePath(filePath)), // get the relative sourceMapDir path based on jsFilePath
combinePaths(sourceMapDir, sourceMapData.jsSourceMappingURL), // this is where user expects to see sourceMap
host.getCurrentDirectory(),
host.getCanonicalFileName,
/*isAbsolutePathAnUrl*/ true);
}
else {
sourceMapData.jsSourceMappingURL = combinePaths(sourceMapDir, sourceMapData.jsSourceMappingURL);
}
}
else {
sourceMapDir = getDirectoryPath(normalizePath(filePath));
}
}
/**
* Reset the SourceMapWriter to an empty state.
*/
function reset() {
if (disabled) {
return;
}
currentSourceFile = undefined;
sourceMapDir = undefined;
sourceMapSourceIndex = undefined;
lastRecordedSourceMapSpan = undefined;
lastEncodedSourceMapSpan = undefined;
lastEncodedNameIndex = undefined;
sourceMapData = undefined;
}
// Encoding for sourcemap span
function encodeLastRecordedSourceMapSpan() {
if (!lastRecordedSourceMapSpan || lastRecordedSourceMapSpan === lastEncodedSourceMapSpan) {
return;
}
let prevEncodedEmittedColumn = lastEncodedSourceMapSpan.emittedColumn;
// Line/Comma delimiters
if (lastEncodedSourceMapSpan.emittedLine === lastRecordedSourceMapSpan.emittedLine) {
// Emit comma to separate the entry
if (sourceMapData.sourceMapMappings) {
sourceMapData.sourceMapMappings += ",";
}
}
else {
// Emit line delimiters
for (let encodedLine = lastEncodedSourceMapSpan.emittedLine; encodedLine < lastRecordedSourceMapSpan.emittedLine; encodedLine++) {
sourceMapData.sourceMapMappings += ";";
}
prevEncodedEmittedColumn = 1;
}
// 1. Relative Column 0 based
sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.emittedColumn - prevEncodedEmittedColumn);
// 2. Relative sourceIndex
sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.sourceIndex - lastEncodedSourceMapSpan.sourceIndex);
// 3. Relative sourceLine 0 based
sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.sourceLine - lastEncodedSourceMapSpan.sourceLine);
// 4. Relative sourceColumn 0 based
sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.sourceColumn - lastEncodedSourceMapSpan.sourceColumn);
// 5. Relative namePosition 0 based
if (lastRecordedSourceMapSpan.nameIndex >= 0) {
Debug.assert(false, "We do not support name index right now, Make sure to update updateLastEncodedAndRecordedSpans when we start using this");
sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.nameIndex - lastEncodedNameIndex);
lastEncodedNameIndex = lastRecordedSourceMapSpan.nameIndex;
}
lastEncodedSourceMapSpan = lastRecordedSourceMapSpan;
sourceMapData.sourceMapDecodedMappings.push(lastEncodedSourceMapSpan);
}
/**
* Emits a mapping.
*
* If the position is synthetic (undefined or a negative value), no mapping will be
* created.
*
* @param pos The position.
*/
function emitPos(pos: number) {
if (disabled || positionIsSynthesized(pos)) {
return;
}
if (extendedDiagnostics) {
performance.mark("beforeSourcemap");
}
const sourceLinePos = getLineAndCharacterOfPosition(currentSourceFile, pos);
// Convert the location to be one-based.
sourceLinePos.line++;
sourceLinePos.character++;
const emittedLine = writer.getLine();
const emittedColumn = writer.getColumn();
// If this location wasn't recorded or the location in source is going backwards, record the span
if (!lastRecordedSourceMapSpan ||
lastRecordedSourceMapSpan.emittedLine !== emittedLine ||
lastRecordedSourceMapSpan.emittedColumn !== emittedColumn ||
(lastRecordedSourceMapSpan.sourceIndex === sourceMapSourceIndex &&
(lastRecordedSourceMapSpan.sourceLine > sourceLinePos.line ||
(lastRecordedSourceMapSpan.sourceLine === sourceLinePos.line && lastRecordedSourceMapSpan.sourceColumn > sourceLinePos.character)))) {
// Encode the last recordedSpan before assigning new
encodeLastRecordedSourceMapSpan();
// New span
lastRecordedSourceMapSpan = {
emittedLine: emittedLine,
emittedColumn: emittedColumn,
sourceLine: sourceLinePos.line,
sourceColumn: sourceLinePos.character,
sourceIndex: sourceMapSourceIndex
};
}
else {
// Take the new pos instead since there is no change in emittedLine and column since last location
lastRecordedSourceMapSpan.sourceLine = sourceLinePos.line;
lastRecordedSourceMapSpan.sourceColumn = sourceLinePos.character;
lastRecordedSourceMapSpan.sourceIndex = sourceMapSourceIndex;
}
if (extendedDiagnostics) {
performance.mark("afterSourcemap");
performance.measure("Source Map", "beforeSourcemap", "afterSourcemap");
}
}
/**
* Emits a node with possible leading and trailing source maps.
*
* @param hint A hint as to the intended usage of the node.
* @param node The node to emit.
* @param emitCallback The callback used to emit the node.
*/
function emitNodeWithSourceMap(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) {
if (disabled) {
return emitCallback(hint, node);
}
if (node) {
const emitNode = node.emitNode;
const emitFlags = emitNode && emitNode.flags;
const { pos, end } = emitNode && emitNode.sourceMapRange || node;
if (node.kind !== SyntaxKind.NotEmittedStatement
&& (emitFlags & EmitFlags.NoLeadingSourceMap) === 0
&& pos >= 0) {
emitPos(skipTrivia(currentSourceText, pos));
}
if (emitFlags & EmitFlags.NoNestedSourceMaps) {
disabled = true;
emitCallback(hint, node);
disabled = false;
}
else {
emitCallback(hint, node);
}
if (node.kind !== SyntaxKind.NotEmittedStatement
&& (emitFlags & EmitFlags.NoTrailingSourceMap) === 0
&& end >= 0) {
emitPos(end);
}
}
}
/**
* Emits a token of a node with possible leading and trailing source maps.
*
* @param node The node containing the token.
* @param token The token to emit.
* @param tokenStartPos The start pos of the token.
* @param emitCallback The callback used to emit the token.
*/
function emitTokenWithSourceMap(node: Node, token: SyntaxKind, tokenPos: number, emitCallback: (token: SyntaxKind, tokenStartPos: number) => number) {
if (disabled) {
return emitCallback(token, tokenPos);
}
const emitNode = node && node.emitNode;
const emitFlags = emitNode && emitNode.flags;
const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token];
tokenPos = skipTrivia(currentSourceText, range ? range.pos : tokenPos);
if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) {
emitPos(tokenPos);
}
tokenPos = emitCallback(token, tokenPos);
if (range) tokenPos = range.end;
if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) {
emitPos(tokenPos);
}
return tokenPos;
}
/**
* Set the current source file.
*
* @param sourceFile The source file.
*/
function setSourceFile(sourceFile: SourceFile) {
if (disabled) {
return;
}
currentSourceFile = sourceFile;
currentSourceText = currentSourceFile.text;
// Add the file to tsFilePaths
// If sourceroot option: Use the relative path corresponding to the common directory path
// otherwise source locations relative to map file location
const sourcesDirectoryPath = compilerOptions.sourceRoot ? host.getCommonSourceDirectory() : sourceMapDir;
const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath,
currentSourceFile.fileName,
host.getCurrentDirectory(),
host.getCanonicalFileName,
/*isAbsolutePathAnUrl*/ true);
sourceMapSourceIndex = indexOf(sourceMapData.sourceMapSources, source);
if (sourceMapSourceIndex === -1) {
sourceMapSourceIndex = sourceMapData.sourceMapSources.length;
sourceMapData.sourceMapSources.push(source);
// The one that can be used from program to get the actual source file
sourceMapData.inputSourceFileNames.push(currentSourceFile.fileName);
if (compilerOptions.inlineSources) {
sourceMapData.sourceMapSourcesContent.push(currentSourceFile.text);
}
}
}
/**
* Gets the text for the source map.
*/
function getText() {
if (disabled) {
return;
}
encodeLastRecordedSourceMapSpan();
return JSON.stringify({
version: 3,
file: sourceMapData.sourceMapFile,
sourceRoot: sourceMapData.sourceMapSourceRoot,
sources: sourceMapData.sourceMapSources,
names: sourceMapData.sourceMapNames,
mappings: sourceMapData.sourceMapMappings,
sourcesContent: sourceMapData.sourceMapSourcesContent,
});
}
/**
* Gets the SourceMappingURL for the source map.
*/
function getSourceMappingURL() {
if (disabled) {
return;
}
if (compilerOptions.inlineSourceMap) {
// Encode the sourceMap into the sourceMap url
const base64SourceMapText = convertToBase64(getText());
return sourceMapData.jsSourceMappingURL = `data:application/json;base64,${base64SourceMapText}`;
}
else {
return sourceMapData.jsSourceMappingURL;
}
}
}
const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
function base64FormatEncode(inValue: number) {
if (inValue < 64) {
return base64Chars.charAt(inValue);
}
throw TypeError(inValue + ": not a 64 based value");
}
function base64VLQFormatEncode(inValue: number) {
// Add a new least significant bit that has the sign of the value.
// if negative number the least significant bit that gets added to the number has value 1
// else least significant bit value that gets added is 0
// eg. -1 changes to binary : 01 [1] => 3
// +1 changes to binary : 01 [0] => 2
if (inValue < 0) {
inValue = ((-inValue) << 1) + 1;
}
else {
inValue = inValue << 1;
}
// Encode 5 bits at a time starting from least significant bits
let encodedStr = "";
do {
let currentDigit = inValue & 31; // 11111
inValue = inValue >> 5;
if (inValue > 0) {
// There are still more digits to decode, set the msb (6th bit)
currentDigit = currentDigit | 32;
}
encodedStr = encodedStr + base64FormatEncode(currentDigit);
} while (inValue > 0);
return encodedStr;
}
}
+412 -226
View File
@@ -1,210 +1,210 @@
/// <reference path="core.ts"/>
declare function setTimeout(handler: (...args: any[]) => void, timeout: number): any;
declare function clearTimeout(handle: any): void;
namespace ts {
export type FileWatcherCallback = (fileName: string, removed?: boolean) => void;
export type DirectoryWatcherCallback = (fileName: string) => void;
export interface WatchedFile {
fileName: string;
callback: FileWatcherCallback;
mtime?: Date;
}
export interface System {
args: string[];
newLine: string;
useCaseSensitiveFileNames: boolean;
write(s: string): void;
readFile(path: string, encoding?: string): string;
getFileSize?(path: string): number;
writeFile(path: string, data: string, writeByteOrderMark?: boolean): void;
watchFile?(path: string, callback: (path: string) => void): FileWatcher;
/**
* @pollingInterval - this parameter is used in polling-based watchers and ignored in watchers that
* use native OS file watching
*/
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
resolvePath(path: string): string;
fileExists(path: string): boolean;
directoryExists(path: string): boolean;
createDirectory(path: string): void;
getExecutingFilePath(): string;
getCurrentDirectory(): string;
readDirectory(path: string, extension?: string, exclude?: string[]): string[];
getDirectories(path: string): string[];
readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[];
getModifiedTime?(path: string): Date;
createHash?(data: string): string;
getMemoryUsage?(): number;
exit(exitCode?: number): void;
realpath?(path: string): string;
/*@internal*/ getEnvironmentVariable(name: string): string;
/*@internal*/ tryEnableSourceMapsForHost?(): void;
setTimeout?(callback: (...args: any[]) => void, ms: number, ...args: any[]): any;
clearTimeout?(timeoutId: any): void;
}
export interface FileWatcher {
close(): void;
}
declare var require: any;
declare var module: any;
declare var process: any;
declare var global: any;
declare var __filename: string;
declare class Enumerator {
public atEnd(): boolean;
public moveNext(): boolean;
public item(): any;
constructor(o: any);
export interface DirectoryWatcher extends FileWatcher {
directoryName: string;
referenceCount: number;
}
export var sys: System = (function () {
declare const require: any;
declare const process: any;
declare const global: any;
declare const __filename: string;
function getWScriptSystem(): System {
var fso = new ActiveXObject("Scripting.FileSystemObject");
var fileStream = new ActiveXObject("ADODB.Stream");
fileStream.Type = 2 /*text*/;
var binaryStream = new ActiveXObject("ADODB.Stream");
binaryStream.Type = 1 /*binary*/;
var args: string[] = [];
for (var i = 0; i < WScript.Arguments.length; i++) {
args[i] = WScript.Arguments.Item(i);
}
function readFile(fileName: string, encoding?: string): string {
if (!fso.FileExists(fileName)) {
return undefined;
}
fileStream.Open();
try {
if (encoding) {
fileStream.Charset = encoding;
fileStream.LoadFromFile(fileName);
}
else {
// Load file and read the first two bytes into a string with no interpretation
fileStream.Charset = "x-ansi";
fileStream.LoadFromFile(fileName);
var bom = fileStream.ReadText(2) || "";
// Position must be at 0 before encoding can be changed
fileStream.Position = 0;
// [0xFF,0xFE] and [0xFE,0xFF] mean utf-16 (little or big endian), otherwise default to utf-8
fileStream.Charset = bom.length >= 2 && (bom.charCodeAt(0) === 0xFF && bom.charCodeAt(1) === 0xFE || bom.charCodeAt(0) === 0xFE && bom.charCodeAt(1) === 0xFF) ? "unicode" : "utf-8";
}
// ReadText method always strips byte order mark from resulting string
return fileStream.ReadText();
}
catch (e) {
throw e;
}
finally {
fileStream.Close();
}
}
function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void {
fileStream.Open();
binaryStream.Open();
try {
// Write characters in UTF-8 encoding
fileStream.Charset = "utf-8";
fileStream.WriteText(data);
// If we don't want the BOM, then skip it by setting the starting location to 3 (size of BOM).
// If not, start from position 0, as the BOM will be added automatically when charset==utf8.
if (writeByteOrderMark) {
fileStream.Position = 0;
}
else {
fileStream.Position = 3;
}
fileStream.CopyTo(binaryStream);
binaryStream.SaveToFile(fileName, 2 /*overwrite*/);
}
finally {
binaryStream.Close();
fileStream.Close();
}
}
function getCanonicalPath(path: string): string {
return path.toLowerCase();
}
function getNames(collection: any): string[]{
var result: string[] = [];
for (var e = new Enumerator(collection); !e.atEnd(); e.moveNext()) {
result.push(e.item().Name);
}
return result.sort();
}
function readDirectory(path: string, extension?: string, exclude?: string[]): string[] {
var result: string[] = [];
exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s)));
visitDirectory(path);
return result;
function visitDirectory(path: string) {
var folder = fso.GetFolder(path || ".");
var files = getNames(folder.files);
for (let current of files) {
let name = combinePaths(path, current);
if ((!extension || fileExtensionIs(name, extension)) && !contains(exclude, getCanonicalPath(name))) {
result.push(name);
}
}
var subfolders = getNames(folder.subfolders);
for (let current of subfolders) {
let name = combinePaths(path, current);
if (!contains(exclude, getCanonicalPath(name))) {
visitDirectory(name);
}
}
}
}
return {
args,
newLine: "\r\n",
useCaseSensitiveFileNames: false,
write(s: string): void {
WScript.StdOut.Write(s);
},
readFile,
writeFile,
resolvePath(path: string): string {
return fso.GetAbsolutePathName(path);
},
fileExists(path: string): boolean {
return fso.FileExists(path);
},
directoryExists(path: string) {
return fso.FolderExists(path);
},
createDirectory(directoryName: string) {
if (!this.directoryExists(directoryName)) {
fso.CreateFolder(directoryName);
}
},
getExecutingFilePath() {
return WScript.ScriptFullName;
},
getCurrentDirectory() {
return new ActiveXObject("WScript.Shell").CurrentDirectory;
},
readDirectory,
exit(exitCode?: number): void {
try {
WScript.Quit(exitCode);
}
catch (e) {
}
}
};
export function getNodeMajorVersion() {
if (typeof process === "undefined") {
return undefined;
}
const version: string = process.version;
if (!version) {
return undefined;
}
const dot = version.indexOf(".");
if (dot === -1) {
return undefined;
}
return parseInt(version.substring(1, dot));
}
declare const ChakraHost: {
args: string[];
currentDirectory: string;
executingFile: string;
newLine?: string;
useCaseSensitiveFileNames?: boolean;
echo(s: string): void;
quit(exitCode?: number): void;
fileExists(path: string): boolean;
directoryExists(path: string): boolean;
createDirectory(path: string): void;
resolvePath(path: string): string;
readFile(path: string): string;
writeFile(path: string, contents: string): void;
getDirectories(path: string): string[];
readDirectory(path: string, extensions?: string[], basePaths?: string[], excludeEx?: string, includeFileEx?: string, includeDirEx?: string): string[];
watchFile?(path: string, callback: FileWatcherCallback): FileWatcher;
watchDirectory?(path: string, callback: DirectoryWatcherCallback, recursive?: boolean): FileWatcher;
realpath(path: string): string;
getEnvironmentVariable?(name: string): string;
};
export let sys: System = (function() {
function getNodeSystem(): System {
var _fs = require("fs");
var _path = require("path");
var _os = require('os');
const _fs = require("fs");
const _path = require("path");
const _os = require("os");
const _crypto = require("crypto");
var platform: string = _os.platform();
// win32\win64 are case insensitive platforms, MacOS (darwin) by default is also case insensitive
var useCaseSensitiveFileNames = platform !== "win32" && platform !== "win64" && platform !== "darwin";
const useNonPollingWatchers = process.env["TSC_NONPOLLING_WATCHER"];
function readFile(fileName: string, encoding?: string): string {
if (!_fs.existsSync(fileName)) {
function createWatchedFileSet() {
const dirWatchers = createMap<DirectoryWatcher>();
// One file can have multiple watchers
const fileWatcherCallbacks = createMultiMap<FileWatcherCallback>();
return { addFile, removeFile };
function reduceDirWatcherRefCountForFile(fileName: string) {
const dirName = getDirectoryPath(fileName);
const watcher = dirWatchers.get(dirName);
if (watcher) {
watcher.referenceCount -= 1;
if (watcher.referenceCount <= 0) {
watcher.close();
dirWatchers.delete(dirName);
}
}
}
function addDirWatcher(dirPath: string): void {
let watcher = dirWatchers.get(dirPath);
if (watcher) {
watcher.referenceCount += 1;
return;
}
watcher = _fs.watch(
dirPath,
{ persistent: true },
(eventName: string, relativeFileName: string) => fileEventHandler(eventName, relativeFileName, dirPath)
);
watcher.referenceCount = 1;
dirWatchers.set(dirPath, watcher);
return;
}
function addFileWatcherCallback(filePath: string, callback: FileWatcherCallback): void {
fileWatcherCallbacks.add(filePath, callback);
}
function addFile(fileName: string, callback: FileWatcherCallback): WatchedFile {
addFileWatcherCallback(fileName, callback);
addDirWatcher(getDirectoryPath(fileName));
return { fileName, callback };
}
function removeFile(watchedFile: WatchedFile) {
removeFileWatcherCallback(watchedFile.fileName, watchedFile.callback);
reduceDirWatcherRefCountForFile(watchedFile.fileName);
}
function removeFileWatcherCallback(filePath: string, callback: FileWatcherCallback) {
fileWatcherCallbacks.remove(filePath, callback);
}
function fileEventHandler(eventName: string, relativeFileName: string, baseDirPath: string) {
// When files are deleted from disk, the triggered "rename" event would have a relativefileName of "undefined"
const fileName = typeof relativeFileName !== "string"
? undefined
: ts.getNormalizedAbsolutePath(relativeFileName, baseDirPath);
// Some applications save a working file via rename operations
if ((eventName === "change" || eventName === "rename")) {
const callbacks = fileWatcherCallbacks.get(fileName);
if (callbacks) {
for (const fileCallback of callbacks) {
fileCallback(fileName);
}
}
}
}
}
const watchedFileSet = createWatchedFileSet();
const nodeVersion = getNodeMajorVersion();
const isNode4OrLater = nodeVersion >= 4;
function isFileSystemCaseSensitive(): boolean {
// win32\win64 are case insensitive platforms
if (platform === "win32" || platform === "win64") {
return false;
}
// convert current file name to upper case / lower case and check if file exists
// (guards against cases when name is already all uppercase or lowercase)
return !fileExists(__filename.toUpperCase()) || !fileExists(__filename.toLowerCase());
}
const platform: string = _os.platform();
const useCaseSensitiveFileNames = isFileSystemCaseSensitive();
function readFile(fileName: string, _encoding?: string): string {
if (!fileExists(fileName)) {
return undefined;
}
var buffer = _fs.readFileSync(fileName);
var len = buffer.length;
const buffer = _fs.readFileSync(fileName);
let len = buffer.length;
if (len >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) {
// Big endian UTF-16 byte order mark detected. Since big endian is not supported by node.js,
// flip all byte pairs and treat as little endian.
len &= ~1;
for (var i = 0; i < len; i += 2) {
var temp = buffer[i];
len &= ~1; // Round down to a multiple of 2
for (let i = 0; i < len; i += 2) {
const temp = buffer[i];
buffer[i] = buffer[i + 1];
buffer[i + 1] = temp;
}
@@ -225,61 +225,114 @@ namespace ts {
function writeFile(fileName: string, data: string, writeByteOrderMark?: boolean): void {
// If a BOM is required, emit one
if (writeByteOrderMark) {
data = '\uFEFF' + data;
data = "\uFEFF" + data;
}
_fs.writeFileSync(fileName, data, "utf8");
let fd: number;
try {
fd = _fs.openSync(fileName, "w");
_fs.writeSync(fd, data, /*position*/ undefined, "utf8");
}
finally {
if (fd !== undefined) {
_fs.closeSync(fd);
}
}
}
function getCanonicalPath(path: string): string {
return useCaseSensitiveFileNames ? path.toLowerCase() : path;
}
function getAccessibleFileSystemEntries(path: string): FileSystemEntries {
try {
const entries = _fs.readdirSync(path || ".").sort();
const files: string[] = [];
const directories: string[] = [];
for (const entry of entries) {
// This is necessary because on some file system node fails to exclude
// "." and "..". See https://github.com/nodejs/node/issues/4002
if (entry === "." || entry === "..") {
continue;
}
const name = combinePaths(path, entry);
function readDirectory(path: string, extension?: string, exclude?: string[]): string[] {
var result: string[] = [];
exclude = map(exclude, s => getCanonicalPath(combinePaths(path, s)));
visitDirectory(path);
return result;
function visitDirectory(path: string) {
var files = _fs.readdirSync(path || ".").sort();
var directories: string[] = [];
for (let current of files) {
var name = combinePaths(path, current);
if (!contains(exclude, getCanonicalPath(name))) {
var stat = _fs.statSync(name);
if (stat.isFile()) {
if (!extension || fileExtensionIs(name, extension)) {
result.push(name);
}
}
else if (stat.isDirectory()) {
directories.push(name);
}
let stat: any;
try {
stat = _fs.statSync(name);
}
catch (e) {
continue;
}
if (stat.isFile()) {
files.push(entry);
}
else if (stat.isDirectory()) {
directories.push(entry);
}
}
for (let current of directories) {
visitDirectory(current);
}
return { files, directories };
}
catch (e) {
return { files: [], directories: [] };
}
}
return {
function readDirectory(path: string, extensions?: string[], excludes?: string[], includes?: string[]): string[] {
return matchFiles(path, extensions, excludes, includes, useCaseSensitiveFileNames, process.cwd(), getAccessibleFileSystemEntries);
}
const enum FileSystemEntryKind {
File,
Directory
}
function fileSystemEntryExists(path: string, entryKind: FileSystemEntryKind): boolean {
try {
const stat = _fs.statSync(path);
switch (entryKind) {
case FileSystemEntryKind.File: return stat.isFile();
case FileSystemEntryKind.Directory: return stat.isDirectory();
}
}
catch (e) {
return false;
}
}
function fileExists(path: string): boolean {
return fileSystemEntryExists(path, FileSystemEntryKind.File);
}
function directoryExists(path: string): boolean {
return fileSystemEntryExists(path, FileSystemEntryKind.Directory);
}
function getDirectories(path: string): string[] {
return filter<string>(_fs.readdirSync(path), dir => fileSystemEntryExists(combinePaths(path, dir), FileSystemEntryKind.Directory));
}
const noOpFileWatcher: FileWatcher = { close: noop };
const nodeSystem: System = {
args: process.argv.slice(2),
newLine: _os.EOL,
useCaseSensitiveFileNames: useCaseSensitiveFileNames,
write(s: string): void {
// 1 is a standard descriptor for stdout
_fs.writeSync(1, s);
process.stdout.write(s);
},
readFile,
writeFile,
watchFile: (fileName, callback) => {
// watchFile polls a file every 250ms, picking up file notifications.
_fs.watchFile(fileName, { persistent: true, interval: 250 }, fileChanged);
return {
close() { _fs.unwatchFile(fileName, fileChanged); }
};
watchFile: (fileName, callback, pollingInterval) => {
if (useNonPollingWatchers) {
const watchedFile = watchedFileSet.addFile(fileName, callback);
return {
close: () => watchedFileSet.removeFile(watchedFile)
};
}
else {
_fs.watchFile(fileName, { persistent: true, interval: pollingInterval || 250 }, fileChanged);
return {
close: () => _fs.unwatchFile(fileName, fileChanged)
};
}
function fileChanged(curr: any, prev: any) {
if (+curr.mtime <= +prev.mtime) {
@@ -287,19 +340,45 @@ namespace ts {
}
callback(fileName);
};
}
},
resolvePath: function (path: string): string {
watchDirectory: (directoryName, callback, recursive) => {
// Node 4.0 `fs.watch` function supports the "recursive" option on both OSX and Windows
// (ref: https://github.com/nodejs/node/pull/2649 and https://github.com/Microsoft/TypeScript/issues/4643)
let options: any;
if (!directoryExists(directoryName)) {
// do nothing if target folder does not exist
return noOpFileWatcher;
}
if (isNode4OrLater && (process.platform === "win32" || process.platform === "darwin")) {
options = { persistent: true, recursive: !!recursive };
}
else {
options = { persistent: true };
}
return _fs.watch(
directoryName,
options,
(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
// event name is "change")
if (eventName === "rename") {
// When deleting a file, the passed baseFileName is null
callback(!relativeFileName ? relativeFileName : normalizePath(combinePaths(directoryName, relativeFileName)));
}
}
);
},
resolvePath: function(path: string): string {
return _path.resolve(path);
},
fileExists(path: string): boolean {
return _fs.existsSync(path);
},
directoryExists(path: string) {
return _fs.existsSync(path) && _fs.statSync(path).isDirectory();
},
fileExists,
directoryExists,
createDirectory(directoryName: string) {
if (!this.directoryExists(directoryName)) {
if (!nodeSystem.directoryExists(directoryName)) {
_fs.mkdirSync(directoryName);
}
},
@@ -309,26 +388,133 @@ namespace ts {
getCurrentDirectory() {
return process.cwd();
},
getDirectories,
getEnvironmentVariable(name: string) {
return process.env[name] || "";
},
readDirectory,
getModifiedTime(path) {
try {
return _fs.statSync(path).mtime;
}
catch (e) {
return undefined;
}
},
createHash(data) {
const hash = _crypto.createHash("md5");
hash.update(data);
return hash.digest("hex");
},
getMemoryUsage() {
if (global.gc) {
global.gc();
}
return process.memoryUsage().heapUsed;
},
getFileSize(path) {
try {
const stat = _fs.statSync(path);
if (stat.isFile()) {
return stat.size;
}
}
catch (e) { }
return 0;
},
exit(exitCode?: number): void {
process.exit(exitCode);
}
},
realpath(path: string): string {
return _fs.realpathSync(path);
},
tryEnableSourceMapsForHost() {
try {
require("source-map-support").install();
}
catch (e) {
// Could not enable source maps.
}
},
setTimeout,
clearTimeout
};
return nodeSystem;
}
function getChakraSystem(): System {
const realpath = ChakraHost.realpath && ((path: string) => ChakraHost.realpath(path));
return {
newLine: ChakraHost.newLine || "\r\n",
args: ChakraHost.args,
useCaseSensitiveFileNames: !!ChakraHost.useCaseSensitiveFileNames,
write: ChakraHost.echo,
readFile(path: string, _encoding?: string) {
// encoding is automatically handled by the implementation in ChakraHost
return ChakraHost.readFile(path);
},
writeFile(path: string, data: string, writeByteOrderMark?: boolean) {
// If a BOM is required, emit one
if (writeByteOrderMark) {
data = "\uFEFF" + data;
}
ChakraHost.writeFile(path, data);
},
resolvePath: ChakraHost.resolvePath,
fileExists: ChakraHost.fileExists,
directoryExists: ChakraHost.directoryExists,
createDirectory: ChakraHost.createDirectory,
getExecutingFilePath: () => ChakraHost.executingFile,
getCurrentDirectory: () => ChakraHost.currentDirectory,
getDirectories: ChakraHost.getDirectories,
getEnvironmentVariable: ChakraHost.getEnvironmentVariable || (() => ""),
readDirectory: (path: string, extensions?: string[], excludes?: string[], includes?: string[]) => {
const pattern = getFileMatcherPatterns(path, excludes, includes, !!ChakraHost.useCaseSensitiveFileNames, ChakraHost.currentDirectory);
return ChakraHost.readDirectory(path, extensions, pattern.basePaths, pattern.excludePattern, pattern.includeFilePattern, pattern.includeDirectoryPattern);
},
exit: ChakraHost.quit,
realpath
};
}
if (typeof WScript !== "undefined" && typeof ActiveXObject === "function") {
return getWScriptSystem();
function recursiveCreateDirectory(directoryPath: string, sys: System) {
const basePath = getDirectoryPath(directoryPath);
const shouldCreateParent = directoryPath !== basePath && !sys.directoryExists(basePath);
if (shouldCreateParent) {
recursiveCreateDirectory(basePath, sys);
}
if (shouldCreateParent || !sys.directoryExists(directoryPath)) {
sys.createDirectory(directoryPath);
}
}
else if (typeof module !== "undefined" && module.exports) {
return getNodeSystem();
let sys: System;
if (typeof ChakraHost !== "undefined") {
sys = getChakraSystem();
}
else {
return undefined; // Unsupported host
else if (typeof process !== "undefined" && process.nextTick && !process.browser && typeof require !== "undefined") {
// process and process.nextTick checks if current environment is node-like
// process.browser check excludes webpack and browserify
sys = getNodeSystem();
}
if (sys) {
// patch writefile to create folder before writing the file
const originalWriteFile = sys.writeFile;
sys.writeFile = function(path, data, writeBom) {
const directoryPath = getDirectoryPath(normalizeSlashes(path));
if (directoryPath && !sys.directoryExists(directoryPath)) {
recursiveCreateDirectory(directoryPath, sys);
}
originalWriteFile.call(sys, path, data, writeBom);
};
}
return sys;
})();
}
if (sys && sys.getEnvironmentVariable) {
Debug.currentAssertionLevel = /^development$/i.test(sys.getEnvironmentVariable("NODE_ENV"))
? AssertionLevel.Normal
: AssertionLevel.None;
}
}
+377
View File
@@ -0,0 +1,377 @@
/// <reference path="visitor.ts" />
/// <reference path="transformers/ts.ts" />
/// <reference path="transformers/jsx.ts" />
/// <reference path="transformers/esnext.ts" />
/// <reference path="transformers/es2017.ts" />
/// <reference path="transformers/es2016.ts" />
/// <reference path="transformers/es2015.ts" />
/// <reference path="transformers/generators.ts" />
/// <reference path="transformers/es5.ts" />
/// <reference path="transformers/module/module.ts" />
/// <reference path="transformers/module/system.ts" />
/// <reference path="transformers/module/es2015.ts" />
/* @internal */
namespace ts {
function getModuleTransformer(moduleKind: ModuleKind): TransformerFactory<SourceFile> {
switch (moduleKind) {
case ModuleKind.ES2015:
return transformES2015Module;
case ModuleKind.System:
return transformSystemModule;
default:
return transformModule;
}
}
const enum TransformationState {
Uninitialized,
Initialized,
Completed,
Disposed
}
const enum SyntaxKindFeatureFlags {
Substitution = 1 << 0,
EmitNotifications = 1 << 1,
}
export function getTransformers(compilerOptions: CompilerOptions, customTransformers?: CustomTransformers) {
const jsx = compilerOptions.jsx;
const languageVersion = getEmitScriptTarget(compilerOptions);
const moduleKind = getEmitModuleKind(compilerOptions);
const transformers: TransformerFactory<SourceFile>[] = [];
addRange(transformers, customTransformers && customTransformers.before);
transformers.push(transformTypeScript);
if (jsx === JsxEmit.React) {
transformers.push(transformJsx);
}
if (languageVersion < ScriptTarget.ESNext) {
transformers.push(transformESNext);
}
if (languageVersion < ScriptTarget.ES2017) {
transformers.push(transformES2017);
}
if (languageVersion < ScriptTarget.ES2016) {
transformers.push(transformES2016);
}
if (languageVersion < ScriptTarget.ES2015) {
transformers.push(transformES2015);
transformers.push(transformGenerators);
}
transformers.push(getModuleTransformer(moduleKind));
// The ES5 transformer is last so that it can substitute expressions like `exports.default`
// for ES3.
if (languageVersion < ScriptTarget.ES5) {
transformers.push(transformES5);
}
addRange(transformers, customTransformers && customTransformers.after);
return transformers;
}
/**
* Transforms an array of SourceFiles by passing them through each transformer.
*
* @param resolver The emit resolver provided by the checker.
* @param host The emit host object used to interact with the file system.
* @param options Compiler options to surface in the `TransformationContext`.
* @param nodes An array of nodes to transform.
* @param transforms An array of `TransformerFactory` callbacks.
* @param allowDtsFiles A value indicating whether to allow the transformation of .d.ts files.
*/
export function transformNodes<T extends Node>(resolver: EmitResolver, host: EmitHost, options: CompilerOptions, nodes: T[], transformers: TransformerFactory<T>[], allowDtsFiles: boolean): TransformationResult<T> {
const enabledSyntaxKindFeatures = new Array<SyntaxKindFeatureFlags>(SyntaxKind.Count);
let lexicalEnvironmentVariableDeclarations: VariableDeclaration[];
let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[];
let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = [];
let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = [];
let lexicalEnvironmentStackOffset = 0;
let lexicalEnvironmentSuspended = false;
let emitHelpers: EmitHelper[];
let onSubstituteNode: TransformationContext["onSubstituteNode"] = (_, node) => node;
let onEmitNode: TransformationContext["onEmitNode"] = (hint, node, callback) => callback(hint, node);
let state = TransformationState.Uninitialized;
// The transformation context is provided to each transformer as part of transformer
// initialization.
const context: TransformationContext = {
getCompilerOptions: () => options,
getEmitResolver: () => resolver,
getEmitHost: () => host,
startLexicalEnvironment,
suspendLexicalEnvironment,
resumeLexicalEnvironment,
endLexicalEnvironment,
hoistVariableDeclaration,
hoistFunctionDeclaration,
requestEmitHelper,
readEmitHelpers,
enableSubstitution,
enableEmitNotification,
isSubstitutionEnabled,
isEmitNotificationEnabled,
get onSubstituteNode() { return onSubstituteNode; },
set onSubstituteNode(value) {
Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed.");
Debug.assert(value !== undefined, "Value must not be 'undefined'");
onSubstituteNode = value;
},
get onEmitNode() { return onEmitNode; },
set onEmitNode(value) {
Debug.assert(state < TransformationState.Initialized, "Cannot modify transformation hooks after initialization has completed.");
Debug.assert(value !== undefined, "Value must not be 'undefined'");
onEmitNode = value;
}
};
// Ensure the parse tree is clean before applying transformations
for (const node of nodes) {
disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node)));
}
performance.mark("beforeTransform");
// Chain together and initialize each transformer.
const transformation = chain(...transformers)(context);
// prevent modification of transformation hooks.
state = TransformationState.Initialized;
// Transform each node.
const transformed = map(nodes, allowDtsFiles ? transformation : transformRoot);
// prevent modification of the lexical environment.
state = TransformationState.Completed;
performance.mark("afterTransform");
performance.measure("transformTime", "beforeTransform", "afterTransform");
return {
transformed,
substituteNode,
emitNodeWithNotification,
dispose
};
function transformRoot(node: T) {
return node && (!isSourceFile(node) || !node.isDeclarationFile) ? transformation(node) : node;
}
/**
* Enables expression substitutions in the pretty printer for the provided SyntaxKind.
*/
function enableSubstitution(kind: SyntaxKind) {
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.Substitution;
}
/**
* Determines whether expression substitutions are enabled for the provided node.
*/
function isSubstitutionEnabled(node: Node) {
return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.Substitution) !== 0
&& (getEmitFlags(node) & EmitFlags.NoSubstitution) === 0;
}
/**
* Emits a node with possible substitution.
*
* @param hint A hint as to the intended usage of the node.
* @param node The node to emit.
* @param emitCallback The callback used to emit the node or its substitute.
*/
function substituteNode(hint: EmitHint, node: Node) {
Debug.assert(state < TransformationState.Disposed, "Cannot substitute a node after the result is disposed.");
return node && isSubstitutionEnabled(node) && onSubstituteNode(hint, node) || node;
}
/**
* Enables before/after emit notifications in the pretty printer for the provided SyntaxKind.
*/
function enableEmitNotification(kind: SyntaxKind) {
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
enabledSyntaxKindFeatures[kind] |= SyntaxKindFeatureFlags.EmitNotifications;
}
/**
* Determines whether before/after emit notifications should be raised in the pretty
* printer when it emits a node.
*/
function isEmitNotificationEnabled(node: Node) {
return (enabledSyntaxKindFeatures[node.kind] & SyntaxKindFeatureFlags.EmitNotifications) !== 0
|| (getEmitFlags(node) & EmitFlags.AdviseOnEmitNode) !== 0;
}
/**
* Emits a node with possible emit notification.
*
* @param hint A hint as to the intended usage of the node.
* @param node The node to emit.
* @param emitCallback The callback used to emit the node.
*/
function emitNodeWithNotification(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) {
Debug.assert(state < TransformationState.Disposed, "Cannot invoke TransformationResult callbacks after the result is disposed.");
if (node) {
if (isEmitNotificationEnabled(node)) {
onEmitNode(hint, node, emitCallback);
}
else {
emitCallback(hint, node);
}
}
}
/**
* Records a hoisted variable declaration for the provided name within a lexical environment.
*/
function hoistVariableDeclaration(name: Identifier): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
const decl = createVariableDeclaration(name);
if (!lexicalEnvironmentVariableDeclarations) {
lexicalEnvironmentVariableDeclarations = [decl];
}
else {
lexicalEnvironmentVariableDeclarations.push(decl);
}
}
/**
* Records a hoisted function declaration within a lexical environment.
*/
function hoistFunctionDeclaration(func: FunctionDeclaration): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
if (!lexicalEnvironmentFunctionDeclarations) {
lexicalEnvironmentFunctionDeclarations = [func];
}
else {
lexicalEnvironmentFunctionDeclarations.push(func);
}
}
/**
* Starts a new lexical environment. Any existing hoisted variable or function declarations
* are pushed onto a stack, and the related storage variables are reset.
*/
function startLexicalEnvironment(): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended.");
// Save the current lexical environment. Rather than resizing the array we adjust the
// stack size variable. This allows us to reuse existing array slots we've
// already allocated between transformations to avoid allocation and GC overhead during
// transformation.
lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations;
lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations;
lexicalEnvironmentStackOffset++;
lexicalEnvironmentVariableDeclarations = undefined;
lexicalEnvironmentFunctionDeclarations = undefined;
}
/** Suspends the current lexical environment, usually after visiting a parameter list. */
function suspendLexicalEnvironment(): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended.");
lexicalEnvironmentSuspended = true;
}
/** Resumes a suspended lexical environment, usually before visiting a function body. */
function resumeLexicalEnvironment(): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended.");
lexicalEnvironmentSuspended = false;
}
/**
* Ends a lexical environment. The previous set of hoisted declarations are restored and
* any hoisted declarations added in this environment are returned.
*/
function endLexicalEnvironment(): Statement[] {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the lexical environment during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the lexical environment after transformation has completed.");
Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended.");
let statements: Statement[];
if (lexicalEnvironmentVariableDeclarations || lexicalEnvironmentFunctionDeclarations) {
if (lexicalEnvironmentFunctionDeclarations) {
statements = [...lexicalEnvironmentFunctionDeclarations];
}
if (lexicalEnvironmentVariableDeclarations) {
const statement = createVariableStatement(
/*modifiers*/ undefined,
createVariableDeclarationList(lexicalEnvironmentVariableDeclarations)
);
if (!statements) {
statements = [statement];
}
else {
statements.push(statement);
}
}
}
// Restore the previous lexical environment.
lexicalEnvironmentStackOffset--;
lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset];
lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset];
if (lexicalEnvironmentStackOffset === 0) {
lexicalEnvironmentVariableDeclarationsStack = [];
lexicalEnvironmentFunctionDeclarationsStack = [];
}
return statements;
}
function requestEmitHelper(helper: EmitHelper): void {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
Debug.assert(!helper.scoped, "Cannot request a scoped emit helper.");
emitHelpers = append(emitHelpers, helper);
}
function readEmitHelpers(): EmitHelper[] | undefined {
Debug.assert(state > TransformationState.Uninitialized, "Cannot modify the transformation context during initialization.");
Debug.assert(state < TransformationState.Completed, "Cannot modify the transformation context after transformation has completed.");
const helpers = emitHelpers;
emitHelpers = undefined;
return helpers;
}
function dispose() {
if (state < TransformationState.Disposed) {
// Clean up emit nodes on parse tree
for (const node of nodes) {
disposeEmitNodes(getSourceFileOfNode(getParseTreeNode(node)));
}
// Release references to external entries for GC purposes.
lexicalEnvironmentVariableDeclarations = undefined;
lexicalEnvironmentVariableDeclarationsStack = undefined;
lexicalEnvironmentFunctionDeclarations = undefined;
lexicalEnvironmentFunctionDeclarationsStack = undefined;
onSubstituteNode = undefined;
onEmitNode = undefined;
emitHelpers = undefined;
// Prevent further use of the transformation result.
state = TransformationState.Disposed;
}
}
}
}
+530
View File
@@ -0,0 +1,530 @@
/// <reference path="../factory.ts" />
/// <reference path="../visitor.ts" />
/*@internal*/
namespace ts {
interface FlattenContext {
context: TransformationContext;
level: FlattenLevel;
downlevelIteration: boolean;
hoistTempVariables: boolean;
emitExpression: (value: Expression) => void;
emitBindingOrAssignment: (target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node) => void;
createArrayBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ArrayBindingOrAssignmentPattern;
createObjectBindingOrAssignmentPattern: (elements: BindingOrAssignmentElement[]) => ObjectBindingOrAssignmentPattern;
createArrayBindingOrAssignmentElement: (node: Identifier) => BindingOrAssignmentElement;
visitor?: (node: Node) => VisitResult<Node>;
}
export const enum FlattenLevel {
All,
ObjectRest,
}
/**
* Flattens a DestructuringAssignment or a VariableDeclaration to an expression.
*
* @param node The node to flatten.
* @param visitor An optional visitor used to visit initializers.
* @param context The transformation context.
* @param level Indicates the extent to which flattening should occur.
* @param needsValue An optional value indicating whether the value from the right-hand-side of
* the destructuring assignment is needed as part of a larger expression.
* @param createAssignmentCallback An optional callback used to create the assignment expression.
*/
export function flattenDestructuringAssignment(
node: VariableDeclaration | DestructuringAssignment,
visitor: ((node: Node) => VisitResult<Node>) | undefined,
context: TransformationContext,
level: FlattenLevel,
needsValue?: boolean,
createAssignmentCallback?: (name: Identifier, value: Expression, location?: TextRange) => Expression): Expression {
let location: TextRange = node;
let value: Expression;
if (isDestructuringAssignment(node)) {
value = node.right;
while (isEmptyArrayLiteral(node.left) || isEmptyObjectLiteral(node.left)) {
if (isDestructuringAssignment(value)) {
location = node = value;
value = node.right;
}
else {
return value;
}
}
}
let expressions: Expression[];
const flattenContext: FlattenContext = {
context,
level,
downlevelIteration: context.getCompilerOptions().downlevelIteration,
hoistTempVariables: true,
emitExpression,
emitBindingOrAssignment,
createArrayBindingOrAssignmentPattern: makeArrayAssignmentPattern,
createObjectBindingOrAssignmentPattern: makeObjectAssignmentPattern,
createArrayBindingOrAssignmentElement: makeAssignmentElement,
visitor
};
if (value) {
value = visitNode(value, visitor, isExpression);
if (needsValue) {
// If the right-hand value of the destructuring assignment needs to be preserved (as
// is the case when the destructuring assignment is part of a larger expression),
// then we need to cache the right-hand value.
//
// The source map location for the assignment should point to the entire binary
// expression.
value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location);
}
else if (nodeIsSynthesized(node)) {
// Generally, the source map location for a destructuring assignment is the root
// expression.
//
// However, if the root expression is synthesized (as in the case
// of the initializer when transforming a ForOfStatement), then the source map
// location should point to the right-hand value of the expression.
location = value;
}
}
flattenBindingOrAssignmentElement(flattenContext, node, value, location, /*skipInitializer*/ isDestructuringAssignment(node));
if (value && needsValue) {
if (!some(expressions)) {
return value;
}
expressions.push(value);
}
return aggregateTransformFlags(inlineExpressions(expressions)) || createOmittedExpression();
function emitExpression(expression: Expression) {
// NOTE: this completely disables source maps, but aligns with the behavior of
// `emitAssignment` in the old emitter.
setEmitFlags(expression, EmitFlags.NoNestedSourceMaps);
aggregateTransformFlags(expression);
expressions = append(expressions, expression);
}
function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node) {
Debug.assertNode(target, createAssignmentCallback ? isIdentifier : isExpression);
const expression = createAssignmentCallback
? createAssignmentCallback(<Identifier>target, value, location)
: setTextRange(
createAssignment(visitNode(<Expression>target, visitor, isExpression), value),
location
);
expression.original = original;
emitExpression(expression);
}
}
/**
* Flattens a VariableDeclaration or ParameterDeclaration to one or more variable declarations.
*
* @param node The node to flatten.
* @param visitor An optional visitor used to visit initializers.
* @param context The transformation context.
* @param boundValue The value bound to the declaration.
* @param skipInitializer A value indicating whether to ignore the initializer of `node`.
* @param hoistTempVariables Indicates whether temporary variables should not be recorded in-line.
* @param level Indicates the extent to which flattening should occur.
*/
export function flattenDestructuringBinding(
node: VariableDeclaration | ParameterDeclaration,
visitor: (node: Node) => VisitResult<Node>,
context: TransformationContext,
level: FlattenLevel,
rval?: Expression,
hoistTempVariables?: boolean,
skipInitializer?: boolean): VariableDeclaration[] {
let pendingExpressions: Expression[];
const pendingDeclarations: { pendingExpressions?: Expression[], name: BindingName, value: Expression, location?: TextRange, original?: Node; }[] = [];
const declarations: VariableDeclaration[] = [];
const flattenContext: FlattenContext = {
context,
level,
downlevelIteration: context.getCompilerOptions().downlevelIteration,
hoistTempVariables,
emitExpression,
emitBindingOrAssignment,
createArrayBindingOrAssignmentPattern: makeArrayBindingPattern,
createObjectBindingOrAssignmentPattern: makeObjectBindingPattern,
createArrayBindingOrAssignmentElement: makeBindingElement,
visitor
};
flattenBindingOrAssignmentElement(flattenContext, node, rval, node, skipInitializer);
if (pendingExpressions) {
const temp = createTempVariable(/*recordTempVariable*/ undefined);
if (hoistTempVariables) {
const value = inlineExpressions(pendingExpressions);
pendingExpressions = undefined;
emitBindingOrAssignment(temp, value, /*location*/ undefined, /*original*/ undefined);
}
else {
context.hoistVariableDeclaration(temp);
const pendingDeclaration = lastOrUndefined(pendingDeclarations);
pendingDeclaration.pendingExpressions = append(
pendingDeclaration.pendingExpressions,
createAssignment(temp, pendingDeclaration.value)
);
addRange(pendingDeclaration.pendingExpressions, pendingExpressions);
pendingDeclaration.value = temp;
}
}
for (const { pendingExpressions, name, value, location, original } of pendingDeclarations) {
const variable = createVariableDeclaration(
name,
/*type*/ undefined,
pendingExpressions ? inlineExpressions(append(pendingExpressions, value)) : value
);
variable.original = original;
setTextRange(variable, location);
if (isIdentifier(name)) {
setEmitFlags(variable, EmitFlags.NoNestedSourceMaps);
}
aggregateTransformFlags(variable);
declarations.push(variable);
}
return declarations;
function emitExpression(value: Expression) {
pendingExpressions = append(pendingExpressions, value);
}
function emitBindingOrAssignment(target: BindingOrAssignmentElementTarget, value: Expression, location: TextRange, original: Node) {
Debug.assertNode(target, isBindingName);
if (pendingExpressions) {
value = inlineExpressions(append(pendingExpressions, value));
pendingExpressions = undefined;
}
pendingDeclarations.push({ pendingExpressions, name: <BindingName>target, value, location, original });
}
}
/**
* Flattens a BindingOrAssignmentElement into zero or more bindings or assignments.
*
* @param flattenContext Options used to control flattening.
* @param element The element to flatten.
* @param value The current RHS value to assign to the element.
* @param location The location to use for source maps and comments.
* @param skipInitializer An optional value indicating whether to include the initializer
* for the element.
*/
function flattenBindingOrAssignmentElement(
flattenContext: FlattenContext,
element: BindingOrAssignmentElement,
value: Expression | undefined,
location: TextRange,
skipInitializer?: boolean) {
if (!skipInitializer) {
const initializer = visitNode(getInitializerOfBindingOrAssignmentElement(element), flattenContext.visitor, isExpression);
if (initializer) {
// Combine value and initializer
value = value ? createDefaultValueCheck(flattenContext, value, initializer, location) : initializer;
}
else if (!value) {
// Use 'void 0' in absence of value and initializer
value = createVoidZero();
}
}
const bindingTarget = getTargetOfBindingOrAssignmentElement(element);
if (isObjectBindingOrAssignmentPattern(bindingTarget)) {
flattenObjectBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value, location);
}
else if (isArrayBindingOrAssignmentPattern(bindingTarget)) {
flattenArrayBindingOrAssignmentPattern(flattenContext, element, bindingTarget, value, location);
}
else {
flattenContext.emitBindingOrAssignment(bindingTarget, value, location, /*original*/ element);
}
}
/**
* Flattens an ObjectBindingOrAssignmentPattern into zero or more bindings or assignments.
*
* @param flattenContext Options used to control flattening.
* @param parent The parent element of the pattern.
* @param pattern The ObjectBindingOrAssignmentPattern to flatten.
* @param value The current RHS value to assign to the element.
* @param location The location to use for source maps and comments.
*/
function flattenObjectBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ObjectBindingOrAssignmentPattern, value: Expression, location: TextRange) {
const elements = getElementsOfBindingOrAssignmentPattern(pattern);
const numElements = elements.length;
if (numElements !== 1) {
// For anything other than a single-element destructuring we need to generate a temporary
// to ensure value is evaluated exactly once. Additionally, if we have zero elements
// we need to emit *something* to ensure that in case a 'var' keyword was already emitted,
// so in that case, we'll intentionally create that temporary.
const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0;
value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location);
}
let bindingElements: BindingOrAssignmentElement[];
let computedTempVariables: Expression[];
for (let i = 0; i < numElements; i++) {
const element = elements[i];
if (!getRestIndicatorOfBindingOrAssignmentElement(element)) {
const propertyName = getPropertyNameOfBindingOrAssignmentElement(element);
if (flattenContext.level >= FlattenLevel.ObjectRest
&& !(element.transformFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest))
&& !(getTargetOfBindingOrAssignmentElement(element).transformFlags & (TransformFlags.ContainsRest | TransformFlags.ContainsObjectRest))
&& !isComputedPropertyName(propertyName)) {
bindingElements = append(bindingElements, element);
}
else {
if (bindingElements) {
flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern);
bindingElements = undefined;
}
const rhsValue = createDestructuringPropertyAccess(flattenContext, value, propertyName);
if (isComputedPropertyName(propertyName)) {
computedTempVariables = append(computedTempVariables, (rhsValue as ElementAccessExpression).argumentExpression);
}
flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element);
}
}
else if (i === numElements - 1) {
if (bindingElements) {
flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern);
bindingElements = undefined;
}
const rhsValue = createRestCall(flattenContext.context, value, elements, computedTempVariables, pattern);
flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, element);
}
}
if (bindingElements) {
flattenContext.emitBindingOrAssignment(flattenContext.createObjectBindingOrAssignmentPattern(bindingElements), value, location, pattern);
}
}
/**
* Flattens an ArrayBindingOrAssignmentPattern into zero or more bindings or assignments.
*
* @param flattenContext Options used to control flattening.
* @param parent The parent element of the pattern.
* @param pattern The ArrayBindingOrAssignmentPattern to flatten.
* @param value The current RHS value to assign to the element.
* @param location The location to use for source maps and comments.
*/
function flattenArrayBindingOrAssignmentPattern(flattenContext: FlattenContext, parent: BindingOrAssignmentElement, pattern: ArrayBindingOrAssignmentPattern, value: Expression, location: TextRange) {
const elements = getElementsOfBindingOrAssignmentPattern(pattern);
const numElements = elements.length;
if (flattenContext.level < FlattenLevel.ObjectRest && flattenContext.downlevelIteration) {
// Read the elements of the iterable into an array
value = ensureIdentifier(
flattenContext,
createReadHelper(
flattenContext.context,
value,
numElements > 0 && getRestIndicatorOfBindingOrAssignmentElement(elements[numElements - 1])
? undefined
: numElements,
location
),
/*reuseIdentifierExpressions*/ false,
location
);
}
else if (numElements !== 1 && (flattenContext.level < FlattenLevel.ObjectRest || numElements === 0)) {
// For anything other than a single-element destructuring we need to generate a temporary
// to ensure value is evaluated exactly once. Additionally, if we have zero elements
// we need to emit *something* to ensure that in case a 'var' keyword was already emitted,
// so in that case, we'll intentionally create that temporary.
const reuseIdentifierExpressions = !isDeclarationBindingElement(parent) || numElements !== 0;
value = ensureIdentifier(flattenContext, value, reuseIdentifierExpressions, location);
}
let bindingElements: BindingOrAssignmentElement[];
let restContainingElements: [Identifier, BindingOrAssignmentElement][];
for (let i = 0; i < numElements; i++) {
const element = elements[i];
if (flattenContext.level >= FlattenLevel.ObjectRest) {
// If an array pattern contains an ObjectRest, we must cache the result so that we
// can perform the ObjectRest destructuring in a different declaration
if (element.transformFlags & TransformFlags.ContainsObjectRest) {
const temp = createTempVariable(/*recordTempVariable*/ undefined);
if (flattenContext.hoistTempVariables) {
flattenContext.context.hoistVariableDeclaration(temp);
}
restContainingElements = append(restContainingElements, <[Identifier, BindingOrAssignmentElement]>[temp, element]);
bindingElements = append(bindingElements, flattenContext.createArrayBindingOrAssignmentElement(temp));
}
else {
bindingElements = append(bindingElements, element);
}
}
else if (isOmittedExpression(element)) {
continue;
}
else if (!getRestIndicatorOfBindingOrAssignmentElement(element)) {
const rhsValue = createElementAccess(value, i);
flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element);
}
else if (i === numElements - 1) {
const rhsValue = createArraySlice(value, i);
flattenBindingOrAssignmentElement(flattenContext, element, rhsValue, /*location*/ element);
}
}
if (bindingElements) {
flattenContext.emitBindingOrAssignment(flattenContext.createArrayBindingOrAssignmentPattern(bindingElements), value, location, pattern);
}
if (restContainingElements) {
for (const [id, element] of restContainingElements) {
flattenBindingOrAssignmentElement(flattenContext, element, id, element);
}
}
}
/**
* Creates an expression used to provide a default value if a value is `undefined` at runtime.
*
* @param flattenContext Options used to control flattening.
* @param value The RHS value to test.
* @param defaultValue The default value to use if `value` is `undefined` at runtime.
* @param location The location to use for source maps and comments.
*/
function createDefaultValueCheck(flattenContext: FlattenContext, value: Expression, defaultValue: Expression, location: TextRange): Expression {
value = ensureIdentifier(flattenContext, value, /*reuseIdentifierExpressions*/ true, location);
return createConditional(createTypeCheck(value, "undefined"), defaultValue, value);
}
/**
* Creates either a PropertyAccessExpression or an ElementAccessExpression for the
* right-hand side of a transformed destructuring assignment.
*
* @link https://tc39.github.io/ecma262/#sec-runtime-semantics-keyeddestructuringassignmentevaluation
*
* @param flattenContext Options used to control flattening.
* @param value The RHS value that is the source of the property.
* @param propertyName The destructuring property name.
*/
function createDestructuringPropertyAccess(flattenContext: FlattenContext, value: Expression, propertyName: PropertyName): LeftHandSideExpression {
if (isComputedPropertyName(propertyName)) {
const argumentExpression = ensureIdentifier(flattenContext, propertyName.expression, /*reuseIdentifierExpressions*/ false, /*location*/ propertyName);
return createElementAccess(value, argumentExpression);
}
else if (isStringOrNumericLiteral(propertyName)) {
const argumentExpression = getSynthesizedClone(propertyName);
argumentExpression.text = unescapeIdentifier(argumentExpression.text);
return createElementAccess(value, argumentExpression);
}
else {
const name = createIdentifier(unescapeIdentifier(propertyName.text));
return createPropertyAccess(value, name);
}
}
/**
* Ensures that there exists a declared identifier whose value holds the given expression.
* This function is useful to ensure that the expression's value can be read from in subsequent expressions.
* Unless 'reuseIdentifierExpressions' is false, 'value' will be returned if it is just an identifier.
*
* @param flattenContext Options used to control flattening.
* @param value the expression whose value needs to be bound.
* @param reuseIdentifierExpressions true if identifier expressions can simply be returned;
* false if it is necessary to always emit an identifier.
* @param location The location to use for source maps and comments.
*/
function ensureIdentifier(flattenContext: FlattenContext, value: Expression, reuseIdentifierExpressions: boolean, location: TextRange) {
if (isIdentifier(value) && reuseIdentifierExpressions) {
return value;
}
else {
const temp = createTempVariable(/*recordTempVariable*/ undefined);
if (flattenContext.hoistTempVariables) {
flattenContext.context.hoistVariableDeclaration(temp);
flattenContext.emitExpression(setTextRange(createAssignment(temp, value), location));
}
else {
flattenContext.emitBindingOrAssignment(temp, value, location, /*original*/ undefined);
}
return temp;
}
}
function makeArrayBindingPattern(elements: BindingOrAssignmentElement[]) {
Debug.assertEachNode(elements, isArrayBindingElement);
return createArrayBindingPattern(<ArrayBindingElement[]>elements);
}
function makeArrayAssignmentPattern(elements: BindingOrAssignmentElement[]) {
return createArrayLiteral(map(elements, convertToArrayAssignmentElement));
}
function makeObjectBindingPattern(elements: BindingOrAssignmentElement[]) {
Debug.assertEachNode(elements, isBindingElement);
return createObjectBindingPattern(<BindingElement[]>elements);
}
function makeObjectAssignmentPattern(elements: BindingOrAssignmentElement[]) {
return createObjectLiteral(map(elements, convertToObjectAssignmentElement));
}
function makeBindingElement(name: Identifier) {
return createBindingElement(/*dotDotDotToken*/ undefined, /*propertyName*/ undefined, name);
}
function makeAssignmentElement(name: Identifier) {
return name;
}
const restHelper: EmitHelper = {
name: "typescript:rest",
scoped: false,
text: `
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
t[p[i]] = s[p[i]];
return t;
};`
};
/** Given value: o, propName: p, pattern: { a, b, ...p } from the original statement
* `{ a, b, ...p } = o`, create `p = __rest(o, ["a", "b"]);`
*/
function createRestCall(context: TransformationContext, value: Expression, elements: BindingOrAssignmentElement[], computedTempVariables: Expression[], location: TextRange): Expression {
context.requestEmitHelper(restHelper);
const propertyNames: Expression[] = [];
let computedTempVariableOffset = 0;
for (let i = 0; i < elements.length - 1; i++) {
const propertyName = getPropertyNameOfBindingOrAssignmentElement(elements[i]);
if (propertyName) {
if (isComputedPropertyName(propertyName)) {
const temp = computedTempVariables[computedTempVariableOffset];
computedTempVariableOffset++;
// typeof _tmp === "symbol" ? _tmp : _tmp + ""
propertyNames.push(
createConditional(
createTypeCheck(temp, "symbol"),
temp,
createAdd(temp, createLiteral(""))
)
);
}
else {
propertyNames.push(createLiteral(propertyName));
}
}
}
return createCall(
getHelperName("__rest"),
/*typeArguments*/ undefined,
[
value,
setTextRange(
createArrayLiteral(propertyNames),
location
)
]);
}
}
File diff suppressed because it is too large Load Diff
+105
View File
@@ -0,0 +1,105 @@
/// <reference path="../factory.ts" />
/// <reference path="../visitor.ts" />
/*@internal*/
namespace ts {
export function transformES2016(context: TransformationContext) {
const { hoistVariableDeclaration } = context;
return transformSourceFile;
function transformSourceFile(node: SourceFile) {
if (node.isDeclarationFile) {
return node;
}
return visitEachChild(node, visitor, context);
}
function visitor(node: Node): VisitResult<Node> {
if ((node.transformFlags & TransformFlags.ContainsES2016) === 0) {
return node;
}
switch (node.kind) {
case SyntaxKind.BinaryExpression:
return visitBinaryExpression(<BinaryExpression>node);
default:
return visitEachChild(node, visitor, context);
}
}
function visitBinaryExpression(node: BinaryExpression): Expression {
switch (node.operatorToken.kind) {
case SyntaxKind.AsteriskAsteriskEqualsToken:
return visitExponentiationAssignmentExpression(node);
case SyntaxKind.AsteriskAsteriskToken:
return visitExponentiationExpression(node);
default:
return visitEachChild(node, visitor, context);
}
}
function visitExponentiationAssignmentExpression(node: BinaryExpression) {
let target: Expression;
let value: Expression;
const left = visitNode(node.left, visitor, isExpression);
const right = visitNode(node.right, visitor, isExpression);
if (isElementAccessExpression(left)) {
// Transforms `a[x] **= b` into `(_a = a)[_x = x] = Math.pow(_a[_x], b)`
const expressionTemp = createTempVariable(hoistVariableDeclaration);
const argumentExpressionTemp = createTempVariable(hoistVariableDeclaration);
target = setTextRange(
createElementAccess(
setTextRange(createAssignment(expressionTemp, left.expression), left.expression),
setTextRange(createAssignment(argumentExpressionTemp, left.argumentExpression), left.argumentExpression)
),
left
);
value = setTextRange(
createElementAccess(
expressionTemp,
argumentExpressionTemp
),
left
);
}
else if (isPropertyAccessExpression(left)) {
// Transforms `a.x **= b` into `(_a = a).x = Math.pow(_a.x, b)`
const expressionTemp = createTempVariable(hoistVariableDeclaration);
target = setTextRange(
createPropertyAccess(
setTextRange(createAssignment(expressionTemp, left.expression), left.expression),
left.name
),
left
);
value = setTextRange(
createPropertyAccess(
expressionTemp,
left.name
),
left
);
}
else {
// Transforms `a **= b` into `a = Math.pow(a, b)`
target = left;
value = left;
}
return setTextRange(
createAssignment(
target,
createMathPow(value, right, /*location*/ node)
),
node
);
}
function visitExponentiationExpression(node: BinaryExpression) {
// Transforms `a ** b` into `Math.pow(a, b)`
const left = visitNode(node.left, visitor, isExpression);
const right = visitNode(node.right, visitor, isExpression);
return createMathPow(left, right, /*location*/ node);
}
}
}
+504
View File
@@ -0,0 +1,504 @@
/// <reference path="../factory.ts" />
/// <reference path="../visitor.ts" />
/*@internal*/
namespace ts {
type SuperContainer = ClassDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | ConstructorDeclaration;
const enum ES2017SubstitutionFlags {
/** Enables substitutions for async methods with `super` calls. */
AsyncMethodsWithSuper = 1 << 0
}
export function transformES2017(context: TransformationContext) {
const {
startLexicalEnvironment,
resumeLexicalEnvironment,
endLexicalEnvironment
} = context;
const resolver = context.getEmitResolver();
const compilerOptions = context.getCompilerOptions();
const languageVersion = getEmitScriptTarget(compilerOptions);
// These variables contain state that changes as we descend into the tree.
let currentSourceFile: SourceFile;
/**
* Keeps track of whether expression substitution has been enabled for specific edge cases.
* They are persisted between each SourceFile transformation and should not be reset.
*/
let enabledSubstitutions: ES2017SubstitutionFlags;
/**
* This keeps track of containers where `super` is valid, for use with
* just-in-time substitution for `super` expressions inside of async methods.
*/
let enclosingSuperContainerFlags: NodeCheckFlags = 0;
// Save the previous transformation hooks.
const previousOnEmitNode = context.onEmitNode;
const previousOnSubstituteNode = context.onSubstituteNode;
// Set new transformation hooks.
context.onEmitNode = onEmitNode;
context.onSubstituteNode = onSubstituteNode;
return transformSourceFile;
function transformSourceFile(node: SourceFile) {
if (node.isDeclarationFile) {
return node;
}
currentSourceFile = node;
const visited = visitEachChild(node, visitor, context);
addEmitHelpers(visited, context.readEmitHelpers());
currentSourceFile = undefined;
return visited;
}
function visitor(node: Node): VisitResult<Node> {
if ((node.transformFlags & TransformFlags.ContainsES2017) === 0) {
return node;
}
switch (node.kind) {
case SyntaxKind.AsyncKeyword:
// ES2017 async modifier should be elided for targets < ES2017
return undefined;
case SyntaxKind.AwaitExpression:
return visitAwaitExpression(<AwaitExpression>node);
case SyntaxKind.MethodDeclaration:
return visitMethodDeclaration(<MethodDeclaration>node);
case SyntaxKind.FunctionDeclaration:
return visitFunctionDeclaration(<FunctionDeclaration>node);
case SyntaxKind.FunctionExpression:
return visitFunctionExpression(<FunctionExpression>node);
case SyntaxKind.ArrowFunction:
return visitArrowFunction(<ArrowFunction>node);
default:
return visitEachChild(node, visitor, context);
}
}
/**
* Visits an AwaitExpression node.
*
* This function will be called any time a ES2017 await expression is encountered.
*
* @param node The node to visit.
*/
function visitAwaitExpression(node: AwaitExpression): Expression {
return setOriginalNode(
setTextRange(
createYield(
/*asteriskToken*/ undefined,
visitNode(node.expression, visitor, isExpression)
),
node
),
node
);
}
/**
* Visits a MethodDeclaration node.
*
* This function will be called when one of the following conditions are met:
* - The node is marked as async
*
* @param node The node to visit.
*/
function visitMethodDeclaration(node: MethodDeclaration) {
return updateMethod(
node,
/*decorators*/ undefined,
visitNodes(node.modifiers, visitor, isModifier),
node.asteriskToken,
node.name,
/*questionToken*/ undefined,
/*typeParameters*/ undefined,
visitParameterList(node.parameters, visitor, context),
/*type*/ undefined,
getFunctionFlags(node) & FunctionFlags.Async
? transformAsyncFunctionBody(node)
: visitFunctionBody(node.body, visitor, context)
);
}
/**
* Visits a FunctionDeclaration node.
*
* This function will be called when one of the following conditions are met:
* - The node is marked async
*
* @param node The node to visit.
*/
function visitFunctionDeclaration(node: FunctionDeclaration): VisitResult<Statement> {
return updateFunctionDeclaration(
node,
/*decorators*/ undefined,
visitNodes(node.modifiers, visitor, isModifier),
node.asteriskToken,
node.name,
/*typeParameters*/ undefined,
visitParameterList(node.parameters, visitor, context),
/*type*/ undefined,
getFunctionFlags(node) & FunctionFlags.Async
? transformAsyncFunctionBody(node)
: visitFunctionBody(node.body, visitor, context)
);
}
/**
* Visits a FunctionExpression node.
*
* This function will be called when one of the following conditions are met:
* - The node is marked async
*
* @param node The node to visit.
*/
function visitFunctionExpression(node: FunctionExpression): Expression {
return updateFunctionExpression(
node,
visitNodes(node.modifiers, visitor, isModifier),
node.asteriskToken,
node.name,
/*typeParameters*/ undefined,
visitParameterList(node.parameters, visitor, context),
/*type*/ undefined,
getFunctionFlags(node) & FunctionFlags.Async
? transformAsyncFunctionBody(node)
: visitFunctionBody(node.body, visitor, context)
);
}
/**
* Visits an ArrowFunction.
*
* This function will be called when one of the following conditions are met:
* - The node is marked async
*
* @param node The node to visit.
*/
function visitArrowFunction(node: ArrowFunction) {
return updateArrowFunction(
node,
visitNodes(node.modifiers, visitor, isModifier),
/*typeParameters*/ undefined,
visitParameterList(node.parameters, visitor, context),
/*type*/ undefined,
getFunctionFlags(node) & FunctionFlags.Async
? transformAsyncFunctionBody(node)
: visitFunctionBody(node.body, visitor, context)
);
}
function transformAsyncFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody;
function transformAsyncFunctionBody(node: ArrowFunction): ConciseBody;
function transformAsyncFunctionBody(node: FunctionLikeDeclaration): ConciseBody {
resumeLexicalEnvironment();
const original = getOriginalNode(node, isFunctionLike);
const nodeType = original.type;
const promiseConstructor = languageVersion < ScriptTarget.ES2015 ? getPromiseConstructor(nodeType) : undefined;
const isArrowFunction = node.kind === SyntaxKind.ArrowFunction;
const hasLexicalArguments = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.CaptureArguments) !== 0;
// An async function is emit as an outer function that calls an inner
// generator function. To preserve lexical bindings, we pass the current
// `this` and `arguments` objects to `__awaiter`. The generator function
// passed to `__awaiter` is executed inside of the callback to the
// promise constructor.
if (!isArrowFunction) {
const statements: Statement[] = [];
const statementOffset = addPrologue(statements, (<Block>node.body).statements, /*ensureUseStrict*/ false, visitor);
statements.push(
createReturn(
createAwaiterHelper(
context,
hasLexicalArguments,
promiseConstructor,
transformFunctionBodyWorker(<Block>node.body, statementOffset)
)
)
);
addRange(statements, endLexicalEnvironment());
const block = createBlock(statements, /*multiLine*/ true);
setTextRange(block, node.body);
// Minor optimization, emit `_super` helper to capture `super` access in an arrow.
// This step isn't needed if we eventually transform this to ES5.
if (languageVersion >= ScriptTarget.ES2015) {
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) {
enableSubstitutionForAsyncMethodsWithSuper();
addEmitHelper(block, advancedAsyncSuperHelper);
}
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) {
enableSubstitutionForAsyncMethodsWithSuper();
addEmitHelper(block, asyncSuperHelper);
}
}
return block;
}
else {
const expression = createAwaiterHelper(
context,
hasLexicalArguments,
promiseConstructor,
transformFunctionBodyWorker(node.body)
);
const declarations = endLexicalEnvironment();
if (some(declarations)) {
const block = convertToFunctionBody(expression);
return updateBlock(block, setTextRange(createNodeArray(concatenate(block.statements, declarations)), block.statements));
}
return expression;
}
}
function transformFunctionBodyWorker(body: ConciseBody, start?: number) {
if (isBlock(body)) {
return updateBlock(body, visitLexicalEnvironment(body.statements, visitor, context, start));
}
else {
startLexicalEnvironment();
const visited = convertToFunctionBody(visitNode(body, visitor, isConciseBody));
const declarations = endLexicalEnvironment();
return updateBlock(visited, setTextRange(createNodeArray(concatenate(visited.statements, declarations)), visited.statements));
}
}
function getPromiseConstructor(type: TypeNode) {
const typeName = type && getEntityNameFromTypeNode(type);
if (typeName && isEntityName(typeName)) {
const serializationKind = resolver.getTypeReferenceSerializationKind(typeName);
if (serializationKind === TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue
|| serializationKind === TypeReferenceSerializationKind.Unknown) {
return typeName;
}
}
return undefined;
}
function enableSubstitutionForAsyncMethodsWithSuper() {
if ((enabledSubstitutions & ES2017SubstitutionFlags.AsyncMethodsWithSuper) === 0) {
enabledSubstitutions |= ES2017SubstitutionFlags.AsyncMethodsWithSuper;
// We need to enable substitutions for call, property access, and element access
// if we need to rewrite super calls.
context.enableSubstitution(SyntaxKind.CallExpression);
context.enableSubstitution(SyntaxKind.PropertyAccessExpression);
context.enableSubstitution(SyntaxKind.ElementAccessExpression);
// We need to be notified when entering and exiting declarations that bind super.
context.enableEmitNotification(SyntaxKind.ClassDeclaration);
context.enableEmitNotification(SyntaxKind.MethodDeclaration);
context.enableEmitNotification(SyntaxKind.GetAccessor);
context.enableEmitNotification(SyntaxKind.SetAccessor);
context.enableEmitNotification(SyntaxKind.Constructor);
}
}
/**
* Hook for node emit.
*
* @param hint A hint as to the intended usage of the node.
* @param node The node to emit.
* @param emit A callback used to emit the node in the printer.
*/
function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void {
// If we need to support substitutions for `super` in an async method,
// we should track it here.
if (enabledSubstitutions & ES2017SubstitutionFlags.AsyncMethodsWithSuper && isSuperContainer(node)) {
const superContainerFlags = resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuper | NodeCheckFlags.AsyncMethodWithSuperBinding);
if (superContainerFlags !== enclosingSuperContainerFlags) {
const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags;
enclosingSuperContainerFlags = superContainerFlags;
previousOnEmitNode(hint, node, emitCallback);
enclosingSuperContainerFlags = savedEnclosingSuperContainerFlags;
return;
}
}
previousOnEmitNode(hint, node, emitCallback);
}
/**
* Hooks node substitutions.
*
* @param hint A hint as to the intended usage of the node.
* @param node The node to substitute.
*/
function onSubstituteNode(hint: EmitHint, node: Node) {
node = previousOnSubstituteNode(hint, node);
if (hint === EmitHint.Expression && enclosingSuperContainerFlags) {
return substituteExpression(<Expression>node);
}
return node;
}
function substituteExpression(node: Expression) {
switch (node.kind) {
case SyntaxKind.PropertyAccessExpression:
return substitutePropertyAccessExpression(<PropertyAccessExpression>node);
case SyntaxKind.ElementAccessExpression:
return substituteElementAccessExpression(<ElementAccessExpression>node);
case SyntaxKind.CallExpression:
return substituteCallExpression(<CallExpression>node);
}
return node;
}
function substitutePropertyAccessExpression(node: PropertyAccessExpression) {
if (node.expression.kind === SyntaxKind.SuperKeyword) {
return createSuperAccessInAsyncMethod(
createLiteral(node.name.text),
node
);
}
return node;
}
function substituteElementAccessExpression(node: ElementAccessExpression) {
if (node.expression.kind === SyntaxKind.SuperKeyword) {
return createSuperAccessInAsyncMethod(
node.argumentExpression,
node
);
}
return node;
}
function substituteCallExpression(node: CallExpression): Expression {
const expression = node.expression;
if (isSuperProperty(expression)) {
const argumentExpression = isPropertyAccessExpression(expression)
? substitutePropertyAccessExpression(expression)
: substituteElementAccessExpression(expression);
return createCall(
createPropertyAccess(argumentExpression, "call"),
/*typeArguments*/ undefined,
[
createThis(),
...node.arguments
]
);
}
return node;
}
function isSuperContainer(node: Node): node is SuperContainer {
const kind = node.kind;
return kind === SyntaxKind.ClassDeclaration
|| kind === SyntaxKind.Constructor
|| kind === SyntaxKind.MethodDeclaration
|| kind === SyntaxKind.GetAccessor
|| kind === SyntaxKind.SetAccessor;
}
function createSuperAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
if (enclosingSuperContainerFlags & NodeCheckFlags.AsyncMethodWithSuperBinding) {
return setTextRange(
createPropertyAccess(
createCall(
createIdentifier("_super"),
/*typeArguments*/ undefined,
[argumentExpression]
),
"value"
),
location
);
}
else {
return setTextRange(
createCall(
createIdentifier("_super"),
/*typeArguments*/ undefined,
[argumentExpression]
),
location
);
}
}
}
const awaiterHelper: EmitHelper = {
name: "typescript:awaiter",
scoped: false,
priority: 5,
text: `
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};`
};
function createAwaiterHelper(context: TransformationContext, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression, body: Block) {
context.requestEmitHelper(awaiterHelper);
const generatorFunc = createFunctionExpression(
/*modifiers*/ undefined,
createToken(SyntaxKind.AsteriskToken),
/*name*/ undefined,
/*typeParameters*/ undefined,
/*parameters*/ [],
/*type*/ undefined,
body
);
// Mark this node as originally an async function
(generatorFunc.emitNode || (generatorFunc.emitNode = {})).flags |= EmitFlags.AsyncFunctionBody;
return createCall(
getHelperName("__awaiter"),
/*typeArguments*/ undefined,
[
createThis(),
hasLexicalArguments ? createIdentifier("arguments") : createVoidZero(),
promiseConstructor ? createExpressionFromEntityName(promiseConstructor) : createVoidZero(),
generatorFunc
]
);
}
export const asyncSuperHelper: EmitHelper = {
name: "typescript:async-super",
scoped: true,
text: `
const _super = name => super[name];
`
};
export const advancedAsyncSuperHelper: EmitHelper = {
name: "typescript:advanced-async-super",
scoped: true,
text: `
const _super = (function (geti, seti) {
const cache = Object.create(null);
return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } });
})(name => super[name], (name, value) => super[name] = value);
`
};
}
+121
View File
@@ -0,0 +1,121 @@
/// <reference path="../factory.ts" />
/// <reference path="../visitor.ts" />
/*@internal*/
namespace ts {
/**
* Transforms ES5 syntax into ES3 syntax.
*
* @param context Context and state information for the transformation.
*/
export function transformES5(context: TransformationContext) {
const compilerOptions = context.getCompilerOptions();
// enable emit notification only if using --jsx preserve or react-native
let previousOnEmitNode: (hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) => void;
let noSubstitution: boolean[];
if (compilerOptions.jsx === JsxEmit.Preserve || compilerOptions.jsx === JsxEmit.ReactNative) {
previousOnEmitNode = context.onEmitNode;
context.onEmitNode = onEmitNode;
context.enableEmitNotification(SyntaxKind.JsxOpeningElement);
context.enableEmitNotification(SyntaxKind.JsxClosingElement);
context.enableEmitNotification(SyntaxKind.JsxSelfClosingElement);
noSubstitution = [];
}
const previousOnSubstituteNode = context.onSubstituteNode;
context.onSubstituteNode = onSubstituteNode;
context.enableSubstitution(SyntaxKind.PropertyAccessExpression);
context.enableSubstitution(SyntaxKind.PropertyAssignment);
return transformSourceFile;
/**
* Transforms an ES5 source file to ES3.
*
* @param node A SourceFile
*/
function transformSourceFile(node: SourceFile) {
return node;
}
/**
* Called by the printer just before a node is printed.
*
* @param hint A hint as to the intended usage of the node.
* @param node The node to emit.
* @param emitCallback A callback used to emit the node.
*/
function onEmitNode(hint: EmitHint, node: Node, emitCallback: (emitContext: EmitHint, node: Node) => void) {
switch (node.kind) {
case SyntaxKind.JsxOpeningElement:
case SyntaxKind.JsxClosingElement:
case SyntaxKind.JsxSelfClosingElement:
const tagName = (<JsxOpeningElement | JsxClosingElement | JsxSelfClosingElement>node).tagName;
noSubstitution[getOriginalNodeId(tagName)] = true;
break;
}
previousOnEmitNode(hint, node, emitCallback);
}
/**
* Hooks node substitutions.
*
* @param hint A hint as to the intended usage of the node.
* @param node The node to substitute.
*/
function onSubstituteNode(hint: EmitHint, node: Node) {
if (node.id && noSubstitution && noSubstitution[node.id]) {
return previousOnSubstituteNode(hint, node);
}
node = previousOnSubstituteNode(hint, node);
if (isPropertyAccessExpression(node)) {
return substitutePropertyAccessExpression(node);
}
else if (isPropertyAssignment(node)) {
return substitutePropertyAssignment(node);
}
return node;
}
/**
* Substitutes a PropertyAccessExpression whose name is a reserved word.
*
* @param node A PropertyAccessExpression
*/
function substitutePropertyAccessExpression(node: PropertyAccessExpression): Expression {
const literalName = trySubstituteReservedName(node.name);
if (literalName) {
return setTextRange(createElementAccess(node.expression, literalName), node);
}
return node;
}
/**
* Substitutes a PropertyAssignment whose name is a reserved word.
*
* @param node A PropertyAssignment
*/
function substitutePropertyAssignment(node: PropertyAssignment): PropertyAssignment {
const literalName = isIdentifier(node.name) && trySubstituteReservedName(node.name);
if (literalName) {
return updatePropertyAssignment(node, literalName, node.initializer);
}
return node;
}
/**
* If an identifier name is a reserved word, returns a string literal for the name.
*
* @param name An Identifier
*/
function trySubstituteReservedName(name: Identifier) {
const token = name.originalKeywordKind || (nodeIsSynthesized(name) ? stringToToken(name.text) : undefined);
if (token >= SyntaxKind.FirstReservedWord && token <= SyntaxKind.LastReservedWord) {
return setTextRange(createLiteral(name), name);
}
return undefined;
}
}
}
+974
View File
@@ -0,0 +1,974 @@
/// <reference path="../factory.ts" />
/// <reference path="../visitor.ts" />
/// <reference path="es2017.ts" />
/*@internal*/
namespace ts {
const enum ESNextSubstitutionFlags {
/** Enables substitutions for async methods with `super` calls. */
AsyncMethodsWithSuper = 1 << 0
}
export function transformESNext(context: TransformationContext) {
const {
resumeLexicalEnvironment,
endLexicalEnvironment,
hoistVariableDeclaration
} = context;
const resolver = context.getEmitResolver();
const compilerOptions = context.getCompilerOptions();
const languageVersion = getEmitScriptTarget(compilerOptions);
const previousOnEmitNode = context.onEmitNode;
context.onEmitNode = onEmitNode;
const previousOnSubstituteNode = context.onSubstituteNode;
context.onSubstituteNode = onSubstituteNode;
let enabledSubstitutions: ESNextSubstitutionFlags;
let enclosingFunctionFlags: FunctionFlags;
let enclosingSuperContainerFlags: NodeCheckFlags = 0;
return transformSourceFile;
function transformSourceFile(node: SourceFile) {
if (node.isDeclarationFile) {
return node;
}
const visited = visitEachChild(node, visitor, context);
addEmitHelpers(visited, context.readEmitHelpers());
return visited;
}
function visitor(node: Node): VisitResult<Node> {
return visitorWorker(node, /*noDestructuringValue*/ false);
}
function visitorNoDestructuringValue(node: Node): VisitResult<Node> {
return visitorWorker(node, /*noDestructuringValue*/ true);
}
function visitorNoAsyncModifier(node: Node): VisitResult<Node> {
if (node.kind === SyntaxKind.AsyncKeyword) {
return undefined;
}
return node;
}
function visitorWorker(node: Node, noDestructuringValue: boolean): VisitResult<Node> {
if ((node.transformFlags & TransformFlags.ContainsESNext) === 0) {
return node;
}
switch (node.kind) {
case SyntaxKind.AwaitExpression:
return visitAwaitExpression(node as AwaitExpression);
case SyntaxKind.YieldExpression:
return visitYieldExpression(node as YieldExpression);
case SyntaxKind.LabeledStatement:
return visitLabeledStatement(node as LabeledStatement);
case SyntaxKind.ObjectLiteralExpression:
return visitObjectLiteralExpression(node as ObjectLiteralExpression);
case SyntaxKind.BinaryExpression:
return visitBinaryExpression(node as BinaryExpression, noDestructuringValue);
case SyntaxKind.VariableDeclaration:
return visitVariableDeclaration(node as VariableDeclaration);
case SyntaxKind.ForOfStatement:
return visitForOfStatement(node as ForOfStatement, /*outermostLabeledStatement*/ undefined);
case SyntaxKind.ForStatement:
return visitForStatement(node as ForStatement);
case SyntaxKind.VoidExpression:
return visitVoidExpression(node as VoidExpression);
case SyntaxKind.Constructor:
return visitConstructorDeclaration(node as ConstructorDeclaration);
case SyntaxKind.MethodDeclaration:
return visitMethodDeclaration(node as MethodDeclaration);
case SyntaxKind.GetAccessor:
return visitGetAccessorDeclaration(node as GetAccessorDeclaration);
case SyntaxKind.SetAccessor:
return visitSetAccessorDeclaration(node as SetAccessorDeclaration);
case SyntaxKind.FunctionDeclaration:
return visitFunctionDeclaration(node as FunctionDeclaration);
case SyntaxKind.FunctionExpression:
return visitFunctionExpression(node as FunctionExpression);
case SyntaxKind.ArrowFunction:
return visitArrowFunction(node as ArrowFunction);
case SyntaxKind.Parameter:
return visitParameter(node as ParameterDeclaration);
case SyntaxKind.ExpressionStatement:
return visitExpressionStatement(node as ExpressionStatement);
case SyntaxKind.ParenthesizedExpression:
return visitParenthesizedExpression(node as ParenthesizedExpression, noDestructuringValue);
default:
return visitEachChild(node, visitor, context);
}
}
function visitAwaitExpression(node: AwaitExpression): Expression {
if (enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator) {
return setOriginalNode(
setTextRange(
createYield(createAwaitHelper(context, visitNode(node.expression, visitor, isExpression))),
/*location*/ node
),
node
);
}
return visitEachChild(node, visitor, context);
}
function visitYieldExpression(node: YieldExpression) {
if (enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator && node.asteriskToken) {
const expression = visitNode(node.expression, visitor, isExpression);
return setOriginalNode(
setTextRange(
createYield(
createAwaitHelper(context,
updateYield(
node,
node.asteriskToken,
createAsyncDelegatorHelper(
context,
createAsyncValuesHelper(context, expression, expression),
expression
)
)
)
),
node
),
node
);
}
return visitEachChild(node, visitor, context);
}
function visitLabeledStatement(node: LabeledStatement) {
if (enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator) {
const statement = unwrapInnermostStatementOfLabel(node);
if (statement.kind === SyntaxKind.ForOfStatement && (<ForOfStatement>statement).awaitModifier) {
return visitForOfStatement(<ForOfStatement>statement, node);
}
return restoreEnclosingLabel(visitEachChild(node, visitor, context), node);
}
return visitEachChild(node, visitor, context);
}
function chunkObjectLiteralElements(elements: ObjectLiteralElement[]): Expression[] {
let chunkObject: (ShorthandPropertyAssignment | PropertyAssignment)[];
const objects: Expression[] = [];
for (const e of elements) {
if (e.kind === SyntaxKind.SpreadAssignment) {
if (chunkObject) {
objects.push(createObjectLiteral(chunkObject));
chunkObject = undefined;
}
const target = (e as SpreadAssignment).expression;
objects.push(visitNode(target, visitor, isExpression));
}
else {
if (!chunkObject) {
chunkObject = [];
}
if (e.kind === SyntaxKind.PropertyAssignment) {
const p = e as PropertyAssignment;
chunkObject.push(createPropertyAssignment(p.name, visitNode(p.initializer, visitor, isExpression)));
}
else {
chunkObject.push(e as ShorthandPropertyAssignment);
}
}
}
if (chunkObject) {
objects.push(createObjectLiteral(chunkObject));
}
return objects;
}
function visitObjectLiteralExpression(node: ObjectLiteralExpression): Expression {
if (node.transformFlags & TransformFlags.ContainsObjectSpread) {
// spread elements emit like so:
// non-spread elements are chunked together into object literals, and then all are passed to __assign:
// { a, ...o, b } => __assign({a}, o, {b});
// If the first element is a spread element, then the first argument to __assign is {}:
// { ...o, a, b, ...o2 } => __assign({}, o, {a, b}, o2)
const objects = chunkObjectLiteralElements(node.properties);
if (objects.length && objects[0].kind !== SyntaxKind.ObjectLiteralExpression) {
objects.unshift(createObjectLiteral());
}
return createAssignHelper(context, objects);
}
return visitEachChild(node, visitor, context);
}
function visitExpressionStatement(node: ExpressionStatement): ExpressionStatement {
return visitEachChild(node, visitorNoDestructuringValue, context);
}
function visitParenthesizedExpression(node: ParenthesizedExpression, noDestructuringValue: boolean): ParenthesizedExpression {
return visitEachChild(node, noDestructuringValue ? visitorNoDestructuringValue : visitor, context);
}
/**
* Visits a BinaryExpression that contains a destructuring assignment.
*
* @param node A BinaryExpression node.
*/
function visitBinaryExpression(node: BinaryExpression, noDestructuringValue: boolean): Expression {
if (isDestructuringAssignment(node) && node.left.transformFlags & TransformFlags.ContainsObjectRest) {
return flattenDestructuringAssignment(
node,
visitor,
context,
FlattenLevel.ObjectRest,
!noDestructuringValue
);
}
else if (node.operatorToken.kind === SyntaxKind.CommaToken) {
return updateBinary(
node,
visitNode(node.left, visitorNoDestructuringValue, isExpression),
visitNode(node.right, noDestructuringValue ? visitorNoDestructuringValue : visitor, isExpression)
);
}
return visitEachChild(node, visitor, context);
}
/**
* Visits a VariableDeclaration node with a binding pattern.
*
* @param node A VariableDeclaration node.
*/
function visitVariableDeclaration(node: VariableDeclaration): VisitResult<VariableDeclaration> {
// If we are here it is because the name contains a binding pattern with a rest somewhere in it.
if (isBindingPattern(node.name) && node.name.transformFlags & TransformFlags.ContainsObjectRest) {
return flattenDestructuringBinding(
node,
visitor,
context,
FlattenLevel.ObjectRest
);
}
return visitEachChild(node, visitor, context);
}
function visitForStatement(node: ForStatement): VisitResult<Statement> {
return updateFor(
node,
visitNode(node.initializer, visitorNoDestructuringValue, isForInitializer),
visitNode(node.condition, visitor, isExpression),
visitNode(node.incrementor, visitor, isExpression),
visitNode(node.statement, visitor, isStatement)
);
}
function visitVoidExpression(node: VoidExpression) {
return visitEachChild(node, visitorNoDestructuringValue, context);
}
/**
* Visits a ForOfStatement and converts it into a ES2015-compatible ForOfStatement.
*
* @param node A ForOfStatement.
*/
function visitForOfStatement(node: ForOfStatement, outermostLabeledStatement: LabeledStatement): VisitResult<Statement> {
if (node.initializer.transformFlags & TransformFlags.ContainsObjectRest) {
node = transformForOfStatementWithObjectRest(node);
}
if (node.awaitModifier) {
return transformForAwaitOfStatement(node, outermostLabeledStatement);
}
else {
return restoreEnclosingLabel(visitEachChild(node, visitor, context), outermostLabeledStatement);
}
}
function transformForOfStatementWithObjectRest(node: ForOfStatement) {
const initializerWithoutParens = skipParentheses(node.initializer) as ForInitializer;
if (isVariableDeclarationList(initializerWithoutParens) || isAssignmentPattern(initializerWithoutParens)) {
let bodyLocation: TextRange;
let statementsLocation: TextRange;
const temp = createTempVariable(/*recordTempVariable*/ undefined);
const statements: Statement[] = [createForOfBindingStatement(initializerWithoutParens, temp)];
if (isBlock(node.statement)) {
addRange(statements, node.statement.statements);
bodyLocation = node.statement;
statementsLocation = node.statement.statements;
}
return updateForOf(
node,
node.awaitModifier,
setTextRange(
createVariableDeclarationList(
[
setTextRange(createVariableDeclaration(temp), node.initializer)
],
NodeFlags.Let
),
node.initializer
),
node.expression,
setTextRange(
createBlock(
setTextRange(createNodeArray(statements), statementsLocation),
/*multiLine*/ true
),
bodyLocation
)
);
}
return node;
}
function convertForOfStatementHead(node: ForOfStatement, boundValue: Expression) {
const binding = createForOfBindingStatement(node.initializer, boundValue);
let bodyLocation: TextRange;
let statementsLocation: TextRange;
const statements: Statement[] = [visitNode(binding, visitor, isStatement)];
const statement = visitNode(node.statement, visitor, isStatement);
if (isBlock(statement)) {
addRange(statements, statement.statements);
bodyLocation = statement;
statementsLocation = statement.statements;
}
else {
statements.push(statement);
}
return setEmitFlags(
setTextRange(
createBlock(
setTextRange(createNodeArray(statements), statementsLocation),
/*multiLine*/ true
),
bodyLocation
),
EmitFlags.NoSourceMap | EmitFlags.NoTokenSourceMaps
);
}
function awaitAsYield(expression: Expression) {
return createYield(/*asteriskToken*/ undefined, enclosingFunctionFlags & FunctionFlags.Generator ? createAwaitHelper(context, expression) : expression);
}
function transformForAwaitOfStatement(node: ForOfStatement, outermostLabeledStatement: LabeledStatement) {
const expression = visitNode(node.expression, visitor, isExpression);
const iterator = isIdentifier(expression) ? getGeneratedNameForNode(expression) : createTempVariable(/*recordTempVariable*/ undefined);
const result = isIdentifier(expression) ? getGeneratedNameForNode(iterator) : createTempVariable(/*recordTempVariable*/ undefined);
const errorRecord = createUniqueName("e");
const catchVariable = getGeneratedNameForNode(errorRecord);
const returnMethod = createTempVariable(/*recordTempVariable*/ undefined);
const callValues = createAsyncValuesHelper(context, expression, /*location*/ node.expression);
const callNext = createCall(createPropertyAccess(iterator, "next" ), /*typeArguments*/ undefined, []);
const getDone = createPropertyAccess(result, "done");
const getValue = createPropertyAccess(result, "value");
const callReturn = createFunctionCall(returnMethod, iterator, []);
hoistVariableDeclaration(errorRecord);
hoistVariableDeclaration(returnMethod);
const forStatement = setEmitFlags(
setTextRange(
createFor(
/*initializer*/ setEmitFlags(
setTextRange(
createVariableDeclarationList([
setTextRange(createVariableDeclaration(iterator, /*type*/ undefined, callValues), node.expression),
createVariableDeclaration(result)
]),
node.expression
),
EmitFlags.NoHoisting
),
/*condition*/ createComma(
createAssignment(result, awaitAsYield(callNext)),
createLogicalNot(getDone)
),
/*incrementor*/ undefined,
/*statement*/ convertForOfStatementHead(node, awaitAsYield(getValue))
),
/*location*/ node
),
EmitFlags.NoTokenTrailingSourceMaps
);
return createTry(
createBlock([
restoreEnclosingLabel(
forStatement,
outermostLabeledStatement
)
]),
createCatchClause(
createVariableDeclaration(catchVariable),
setEmitFlags(
createBlock([
createStatement(
createAssignment(
errorRecord,
createObjectLiteral([
createPropertyAssignment("error", catchVariable)
])
)
)
]),
EmitFlags.SingleLine
)
),
createBlock([
createTry(
/*tryBlock*/ createBlock([
setEmitFlags(
createIf(
createLogicalAnd(
createLogicalAnd(
result,
createLogicalNot(getDone)
),
createAssignment(
returnMethod,
createPropertyAccess(iterator, "return")
)
),
createStatement(awaitAsYield(callReturn))
),
EmitFlags.SingleLine
)
]),
/*catchClause*/ undefined,
/*finallyBlock*/ setEmitFlags(
createBlock([
setEmitFlags(
createIf(
errorRecord,
createThrow(
createPropertyAccess(errorRecord, "error")
)
),
EmitFlags.SingleLine
)
]),
EmitFlags.SingleLine
)
)
])
);
}
function visitParameter(node: ParameterDeclaration): ParameterDeclaration {
if (node.transformFlags & TransformFlags.ContainsObjectRest) {
// Binding patterns are converted into a generated name and are
// evaluated inside the function body.
return updateParameter(
node,
/*decorators*/ undefined,
/*modifiers*/ undefined,
node.dotDotDotToken,
getGeneratedNameForNode(node),
/*questionToken*/ undefined,
/*type*/ undefined,
visitNode(node.initializer, visitor, isExpression)
);
}
return visitEachChild(node, visitor, context);
}
function visitConstructorDeclaration(node: ConstructorDeclaration) {
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
enclosingFunctionFlags = FunctionFlags.Normal;
const updated = updateConstructor(
node,
/*decorators*/ undefined,
node.modifiers,
visitParameterList(node.parameters, visitor, context),
transformFunctionBody(node)
);
enclosingFunctionFlags = savedEnclosingFunctionFlags;
return updated;
}
function visitGetAccessorDeclaration(node: GetAccessorDeclaration) {
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
enclosingFunctionFlags = FunctionFlags.Normal;
const updated = updateGetAccessor(
node,
/*decorators*/ undefined,
node.modifiers,
visitNode(node.name, visitor, isPropertyName),
visitParameterList(node.parameters, visitor, context),
/*type*/ undefined,
transformFunctionBody(node)
);
enclosingFunctionFlags = savedEnclosingFunctionFlags;
return updated;
}
function visitSetAccessorDeclaration(node: SetAccessorDeclaration) {
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
enclosingFunctionFlags = FunctionFlags.Normal;
const updated = updateSetAccessor(
node,
/*decorators*/ undefined,
node.modifiers,
visitNode(node.name, visitor, isPropertyName),
visitParameterList(node.parameters, visitor, context),
transformFunctionBody(node)
);
enclosingFunctionFlags = savedEnclosingFunctionFlags;
return updated;
}
function visitMethodDeclaration(node: MethodDeclaration) {
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
enclosingFunctionFlags = getFunctionFlags(node);
const updated = updateMethod(
node,
/*decorators*/ undefined,
enclosingFunctionFlags & FunctionFlags.Generator
? visitNodes(node.modifiers, visitorNoAsyncModifier, isModifier)
: node.modifiers,
enclosingFunctionFlags & FunctionFlags.Async
? undefined
: node.asteriskToken,
visitNode(node.name, visitor, isPropertyName),
visitNode(/*questionToken*/ undefined, visitor, isToken),
/*typeParameters*/ undefined,
visitParameterList(node.parameters, visitor, context),
/*type*/ undefined,
enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator
? transformAsyncGeneratorFunctionBody(node)
: transformFunctionBody(node)
);
enclosingFunctionFlags = savedEnclosingFunctionFlags;
return updated;
}
function visitFunctionDeclaration(node: FunctionDeclaration) {
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
enclosingFunctionFlags = getFunctionFlags(node);
const updated = updateFunctionDeclaration(
node,
/*decorators*/ undefined,
enclosingFunctionFlags & FunctionFlags.Generator
? visitNodes(node.modifiers, visitorNoAsyncModifier, isModifier)
: node.modifiers,
enclosingFunctionFlags & FunctionFlags.Async
? undefined
: node.asteriskToken,
node.name,
/*typeParameters*/ undefined,
visitParameterList(node.parameters, visitor, context),
/*type*/ undefined,
enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator
? transformAsyncGeneratorFunctionBody(node)
: transformFunctionBody(node)
);
enclosingFunctionFlags = savedEnclosingFunctionFlags;
return updated;
}
function visitArrowFunction(node: ArrowFunction) {
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
enclosingFunctionFlags = getFunctionFlags(node);
const updated = updateArrowFunction(
node,
node.modifiers,
/*typeParameters*/ undefined,
visitParameterList(node.parameters, visitor, context),
/*type*/ undefined,
transformFunctionBody(node)
);
enclosingFunctionFlags = savedEnclosingFunctionFlags;
return updated;
}
function visitFunctionExpression(node: FunctionExpression) {
const savedEnclosingFunctionFlags = enclosingFunctionFlags;
enclosingFunctionFlags = getFunctionFlags(node);
const updated = updateFunctionExpression(
node,
enclosingFunctionFlags & FunctionFlags.Generator
? visitNodes(node.modifiers, visitorNoAsyncModifier, isModifier)
: node.modifiers,
enclosingFunctionFlags & FunctionFlags.Async
? undefined
: node.asteriskToken,
node.name,
/*typeParameters*/ undefined,
visitParameterList(node.parameters, visitor, context),
/*type*/ undefined,
enclosingFunctionFlags & FunctionFlags.Async && enclosingFunctionFlags & FunctionFlags.Generator
? transformAsyncGeneratorFunctionBody(node)
: transformFunctionBody(node)
);
enclosingFunctionFlags = savedEnclosingFunctionFlags;
return updated;
}
function transformAsyncGeneratorFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody {
resumeLexicalEnvironment();
const statements: Statement[] = [];
const statementOffset = addPrologue(statements, node.body.statements, /*ensureUseStrict*/ false, visitor);
appendObjectRestAssignmentsIfNeeded(statements, node);
statements.push(
createReturn(
createAsyncGeneratorHelper(
context,
createFunctionExpression(
/*modifiers*/ undefined,
createToken(SyntaxKind.AsteriskToken),
node.name && getGeneratedNameForNode(node.name),
/*typeParameters*/ undefined,
/*parameters*/ [],
/*type*/ undefined,
updateBlock(
node.body,
visitLexicalEnvironment(node.body.statements, visitor, context, statementOffset)
)
)
)
)
);
addRange(statements, endLexicalEnvironment());
const block = updateBlock(node.body, statements);
// Minor optimization, emit `_super` helper to capture `super` access in an arrow.
// This step isn't needed if we eventually transform this to ES5.
if (languageVersion >= ScriptTarget.ES2015) {
if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) {
enableSubstitutionForAsyncMethodsWithSuper();
addEmitHelper(block, advancedAsyncSuperHelper);
}
else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) {
enableSubstitutionForAsyncMethodsWithSuper();
addEmitHelper(block, asyncSuperHelper);
}
}
return block;
}
function transformFunctionBody(node: FunctionDeclaration | FunctionExpression | ConstructorDeclaration | MethodDeclaration | AccessorDeclaration): FunctionBody;
function transformFunctionBody(node: ArrowFunction): ConciseBody;
function transformFunctionBody(node: FunctionLikeDeclaration): ConciseBody {
resumeLexicalEnvironment();
let statementOffset = 0;
const statements: Statement[] = [];
const body = visitNode(node.body, visitor, isConciseBody);
if (isBlock(body)) {
statementOffset = addPrologue(statements, body.statements, /*ensureUseStrict*/ false, visitor);
}
addRange(statements, appendObjectRestAssignmentsIfNeeded(/*statements*/ undefined, node));
const trailingStatements = endLexicalEnvironment();
if (statementOffset > 0 || some(statements) || some(trailingStatements)) {
const block = convertToFunctionBody(body, /*multiLine*/ true);
addRange(statements, block.statements.slice(statementOffset));
addRange(statements, trailingStatements);
return updateBlock(block, setTextRange(createNodeArray(statements), block.statements));
}
return body;
}
function appendObjectRestAssignmentsIfNeeded(statements: Statement[], node: FunctionLikeDeclaration): Statement[] {
for (const parameter of node.parameters) {
if (parameter.transformFlags & TransformFlags.ContainsObjectRest) {
const temp = getGeneratedNameForNode(parameter);
const declarations = flattenDestructuringBinding(
parameter,
visitor,
context,
FlattenLevel.ObjectRest,
temp,
/*doNotRecordTempVariablesInLine*/ false,
/*skipInitializer*/ true,
);
if (some(declarations)) {
const statement = createVariableStatement(
/*modifiers*/ undefined,
createVariableDeclarationList(
declarations
)
);
setEmitFlags(statement, EmitFlags.CustomPrologue);
statements = append(statements, statement);
}
}
}
return statements;
}
function enableSubstitutionForAsyncMethodsWithSuper() {
if ((enabledSubstitutions & ESNextSubstitutionFlags.AsyncMethodsWithSuper) === 0) {
enabledSubstitutions |= ESNextSubstitutionFlags.AsyncMethodsWithSuper;
// We need to enable substitutions for call, property access, and element access
// if we need to rewrite super calls.
context.enableSubstitution(SyntaxKind.CallExpression);
context.enableSubstitution(SyntaxKind.PropertyAccessExpression);
context.enableSubstitution(SyntaxKind.ElementAccessExpression);
// We need to be notified when entering and exiting declarations that bind super.
context.enableEmitNotification(SyntaxKind.ClassDeclaration);
context.enableEmitNotification(SyntaxKind.MethodDeclaration);
context.enableEmitNotification(SyntaxKind.GetAccessor);
context.enableEmitNotification(SyntaxKind.SetAccessor);
context.enableEmitNotification(SyntaxKind.Constructor);
}
}
/**
* Called by the printer just before a node is printed.
*
* @param hint A hint as to the intended usage of the node.
* @param node The node to be printed.
* @param emitCallback The callback used to emit the node.
*/
function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) {
// If we need to support substitutions for `super` in an async method,
// we should track it here.
if (enabledSubstitutions & ESNextSubstitutionFlags.AsyncMethodsWithSuper && isSuperContainer(node)) {
const superContainerFlags = resolver.getNodeCheckFlags(node) & (NodeCheckFlags.AsyncMethodWithSuper | NodeCheckFlags.AsyncMethodWithSuperBinding);
if (superContainerFlags !== enclosingSuperContainerFlags) {
const savedEnclosingSuperContainerFlags = enclosingSuperContainerFlags;
enclosingSuperContainerFlags = superContainerFlags;
previousOnEmitNode(hint, node, emitCallback);
enclosingSuperContainerFlags = savedEnclosingSuperContainerFlags;
return;
}
}
previousOnEmitNode(hint, node, emitCallback);
}
/**
* Hooks node substitutions.
*
* @param hint The context for the emitter.
* @param node The node to substitute.
*/
function onSubstituteNode(hint: EmitHint, node: Node) {
node = previousOnSubstituteNode(hint, node);
if (hint === EmitHint.Expression && enclosingSuperContainerFlags) {
return substituteExpression(<Expression>node);
}
return node;
}
function substituteExpression(node: Expression) {
switch (node.kind) {
case SyntaxKind.PropertyAccessExpression:
return substitutePropertyAccessExpression(<PropertyAccessExpression>node);
case SyntaxKind.ElementAccessExpression:
return substituteElementAccessExpression(<ElementAccessExpression>node);
case SyntaxKind.CallExpression:
return substituteCallExpression(<CallExpression>node);
}
return node;
}
function substitutePropertyAccessExpression(node: PropertyAccessExpression) {
if (node.expression.kind === SyntaxKind.SuperKeyword) {
return createSuperAccessInAsyncMethod(
createLiteral(node.name.text),
node
);
}
return node;
}
function substituteElementAccessExpression(node: ElementAccessExpression) {
if (node.expression.kind === SyntaxKind.SuperKeyword) {
return createSuperAccessInAsyncMethod(
node.argumentExpression,
node
);
}
return node;
}
function substituteCallExpression(node: CallExpression): Expression {
const expression = node.expression;
if (isSuperProperty(expression)) {
const argumentExpression = isPropertyAccessExpression(expression)
? substitutePropertyAccessExpression(expression)
: substituteElementAccessExpression(expression);
return createCall(
createPropertyAccess(argumentExpression, "call"),
/*typeArguments*/ undefined,
[
createThis(),
...node.arguments
]
);
}
return node;
}
function isSuperContainer(node: Node) {
const kind = node.kind;
return kind === SyntaxKind.ClassDeclaration
|| kind === SyntaxKind.Constructor
|| kind === SyntaxKind.MethodDeclaration
|| kind === SyntaxKind.GetAccessor
|| kind === SyntaxKind.SetAccessor;
}
function createSuperAccessInAsyncMethod(argumentExpression: Expression, location: TextRange): LeftHandSideExpression {
if (enclosingSuperContainerFlags & NodeCheckFlags.AsyncMethodWithSuperBinding) {
return setTextRange(
createPropertyAccess(
createCall(
createIdentifier("_super"),
/*typeArguments*/ undefined,
[argumentExpression]
),
"value"
),
location
);
}
else {
return setTextRange(
createCall(
createIdentifier("_super"),
/*typeArguments*/ undefined,
[argumentExpression]
),
location
);
}
}
}
const assignHelper: EmitHelper = {
name: "typescript:assign",
scoped: false,
priority: 1,
text: `
var __assign = (this && this.__assign) || Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};`
};
export function createAssignHelper(context: TransformationContext, attributesSegments: Expression[]) {
if (context.getCompilerOptions().target >= ScriptTarget.ES2015) {
return createCall(createPropertyAccess(createIdentifier("Object"), "assign"),
/*typeArguments*/ undefined,
attributesSegments);
}
context.requestEmitHelper(assignHelper);
return createCall(
getHelperName("__assign"),
/*typeArguments*/ undefined,
attributesSegments
);
}
const awaitHelper: EmitHelper = {
name: "typescript:await",
scoped: false,
text: `
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
`
};
function createAwaitHelper(context: TransformationContext, expression: Expression) {
context.requestEmitHelper(awaitHelper);
return createCall(getHelperName("__await"), /*typeArguments*/ undefined, [expression]);
}
const asyncGeneratorHelper: EmitHelper = {
name: "typescript:asyncGenerator",
scoped: false,
text: `
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var g = generator.apply(thisArg, _arguments || []), i, q = [];
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
function fulfill(value) { resume("next", value); }
function reject(value) { resume("throw", value); }
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
};
`
};
function createAsyncGeneratorHelper(context: TransformationContext, generatorFunc: FunctionExpression) {
context.requestEmitHelper(awaitHelper);
context.requestEmitHelper(asyncGeneratorHelper);
// Mark this node as originally an async function
(generatorFunc.emitNode || (generatorFunc.emitNode = {})).flags |= EmitFlags.AsyncFunctionBody;
return createCall(
getHelperName("__asyncGenerator"),
/*typeArguments*/ undefined,
[
createThis(),
createIdentifier("arguments"),
generatorFunc
]
);
}
const asyncDelegator: EmitHelper = {
name: "typescript:asyncDelegator",
scoped: false,
text: `
var __asyncDelegator = (this && this.__asyncDelegator) || function (o) {
var i, p;
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
function verb(n, f) { if (o[n]) i[n] = function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; }; }
};
`
};
function createAsyncDelegatorHelper(context: TransformationContext, expression: Expression, location?: TextRange) {
context.requestEmitHelper(awaitHelper);
context.requestEmitHelper(asyncDelegator);
return setTextRange(
createCall(
getHelperName("__asyncDelegator"),
/*typeArguments*/ undefined,
[expression]
),
location
);
}
const asyncValues: EmitHelper = {
name: "typescript:asyncValues",
scoped: false,
text: `
var __asyncValues = (this && this.__asyncIterator) || function (o) {
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
var m = o[Symbol.asyncIterator];
return m ? m.call(o) : typeof __values === "function" ? __values(o) : o[Symbol.iterator]();
};
`
};
function createAsyncValuesHelper(context: TransformationContext, expression: Expression, location?: TextRange) {
context.requestEmitHelper(asyncValues);
return setTextRange(
createCall(
getHelperName("__asyncValues"),
/*typeArguments*/ undefined,
[expression]
),
location
);
}
}
File diff suppressed because it is too large Load Diff
+539
View File
@@ -0,0 +1,539 @@
/// <reference path="../factory.ts" />
/// <reference path="../visitor.ts" />
/// <reference path="./esnext.ts" />
/*@internal*/
namespace ts {
export function transformJsx(context: TransformationContext) {
const compilerOptions = context.getCompilerOptions();
return transformSourceFile;
/**
* Transform JSX-specific syntax in a SourceFile.
*
* @param node A SourceFile node.
*/
function transformSourceFile(node: SourceFile) {
if (node.isDeclarationFile) {
return node;
}
const visited = visitEachChild(node, visitor, context);
addEmitHelpers(visited, context.readEmitHelpers());
return visited;
}
function visitor(node: Node): VisitResult<Node> {
if (node.transformFlags & TransformFlags.ContainsJsx) {
return visitorWorker(node);
}
else {
return node;
}
}
function visitorWorker(node: Node): VisitResult<Node> {
switch (node.kind) {
case SyntaxKind.JsxElement:
return visitJsxElement(<JsxElement>node, /*isChild*/ false);
case SyntaxKind.JsxSelfClosingElement:
return visitJsxSelfClosingElement(<JsxSelfClosingElement>node, /*isChild*/ false);
case SyntaxKind.JsxExpression:
return visitJsxExpression(<JsxExpression>node);
default:
return visitEachChild(node, visitor, context);
}
}
function transformJsxChildToExpression(node: JsxChild): Expression {
switch (node.kind) {
case SyntaxKind.JsxText:
return visitJsxText(<JsxText>node);
case SyntaxKind.JsxExpression:
return visitJsxExpression(<JsxExpression>node);
case SyntaxKind.JsxElement:
return visitJsxElement(<JsxElement>node, /*isChild*/ true);
case SyntaxKind.JsxSelfClosingElement:
return visitJsxSelfClosingElement(<JsxSelfClosingElement>node, /*isChild*/ true);
default:
Debug.failBadSyntaxKind(node);
return undefined;
}
}
function visitJsxElement(node: JsxElement, isChild: boolean) {
return visitJsxOpeningLikeElement(node.openingElement, node.children, isChild, /*location*/ node);
}
function visitJsxSelfClosingElement(node: JsxSelfClosingElement, isChild: boolean) {
return visitJsxOpeningLikeElement(node, /*children*/ undefined, isChild, /*location*/ node);
}
function visitJsxOpeningLikeElement(node: JsxOpeningLikeElement, children: JsxChild[], isChild: boolean, location: TextRange) {
const tagName = getTagName(node);
let objectProperties: Expression;
const attrs = node.attributes.properties;
if (attrs.length === 0) {
// When there are no attributes, React wants "null"
objectProperties = createNull();
}
else {
// Map spans of JsxAttribute nodes into object literals and spans
// of JsxSpreadAttribute nodes into expressions.
const segments = flatten(
spanMap(attrs, isJsxSpreadAttribute, (attrs, isSpread) => isSpread
? map(attrs, transformJsxSpreadAttributeToExpression)
: createObjectLiteral(map(attrs, transformJsxAttributeToObjectLiteralElement))
)
);
if (isJsxSpreadAttribute(attrs[0])) {
// We must always emit at least one object literal before a spread
// argument.
segments.unshift(createObjectLiteral());
}
// Either emit one big object literal (no spread attribs), or
// a call to the __assign helper.
objectProperties = singleOrUndefined(segments);
if (!objectProperties) {
objectProperties = createAssignHelper(context, segments);
}
}
const element = createExpressionForJsxElement(
context.getEmitResolver().getJsxFactoryEntity(),
compilerOptions.reactNamespace,
tagName,
objectProperties,
filter(map(children, transformJsxChildToExpression), isDefined),
node,
location
);
if (isChild) {
startOnNewLine(element);
}
return element;
}
function transformJsxSpreadAttributeToExpression(node: JsxSpreadAttribute) {
return visitNode(node.expression, visitor, isExpression);
}
function transformJsxAttributeToObjectLiteralElement(node: JsxAttribute) {
const name = getAttributeName(node);
const expression = transformJsxAttributeInitializer(node.initializer);
return createPropertyAssignment(name, expression);
}
function transformJsxAttributeInitializer(node: StringLiteral | JsxExpression) {
if (node === undefined) {
return createTrue();
}
else if (node.kind === SyntaxKind.StringLiteral) {
const decoded = tryDecodeEntities((<StringLiteral>node).text);
return decoded ? setTextRange(createLiteral(decoded), node) : node;
}
else if (node.kind === SyntaxKind.JsxExpression) {
if (node.expression === undefined) {
return createTrue();
}
return visitJsxExpression(<JsxExpression>node);
}
else {
Debug.failBadSyntaxKind(node);
}
}
function visitJsxText(node: JsxText): StringLiteral | undefined {
const fixed = fixupWhitespaceAndDecodeEntities(getTextOfNode(node, /*includeTrivia*/ true));
return fixed === undefined ? undefined : createLiteral(fixed);
}
/**
* JSX trims whitespace at the end and beginning of lines, except that the
* start/end of a tag is considered a start/end of a line only if that line is
* on the same line as the closing tag. See examples in
* tests/cases/conformance/jsx/tsxReactEmitWhitespace.tsx
* See also https://www.w3.org/TR/html4/struct/text.html#h-9.1 and https://www.w3.org/TR/CSS2/text.html#white-space-model
*
* An equivalent algorithm would be:
* - If there is only one line, return it.
* - If there is only whitespace (but multiple lines), return `undefined`.
* - Split the text into lines.
* - 'trimRight' the first line, 'trimLeft' the last line, 'trim' middle lines.
* - Decode entities on each line (individually).
* - Remove empty lines and join the rest with " ".
*/
function fixupWhitespaceAndDecodeEntities(text: string): string | undefined {
let acc: string | undefined;
// First non-whitespace character on this line.
let firstNonWhitespace = 0;
// Last non-whitespace character on this line.
let lastNonWhitespace = -1;
// These initial values are special because the first line is:
// firstNonWhitespace = 0 to indicate that we want leading whitsepace,
// but lastNonWhitespace = -1 as a special flag to indicate that we *don't* include the line if it's all whitespace.
for (let i = 0; i < text.length; i++) {
const c = text.charCodeAt(i);
if (isLineBreak(c)) {
// If we've seen any non-whitespace characters on this line, add the 'trim' of the line.
// (lastNonWhitespace === -1 is a special flag to detect whether the first line is all whitespace.)
if (firstNonWhitespace !== -1 && lastNonWhitespace !== -1) {
acc = addLineOfJsxText(acc, text.substr(firstNonWhitespace, lastNonWhitespace - firstNonWhitespace + 1));
}
// Reset firstNonWhitespace for the next line.
// Don't bother to reset lastNonWhitespace because we ignore it if firstNonWhitespace = -1.
firstNonWhitespace = -1;
}
else if (!isWhiteSpaceSingleLine(c)) {
lastNonWhitespace = i;
if (firstNonWhitespace === -1) {
firstNonWhitespace = i;
}
}
}
return firstNonWhitespace !== -1
// Last line had a non-whitespace character. Emit the 'trimLeft', meaning keep trailing whitespace.
? addLineOfJsxText(acc, text.substr(firstNonWhitespace))
// Last line was all whitespace, so ignore it
: acc;
}
function addLineOfJsxText(acc: string | undefined, trimmedLine: string): string {
// We do not escape the string here as that is handled by the printer
// when it emits the literal. We do, however, need to decode JSX entities.
const decoded = decodeEntities(trimmedLine);
return acc === undefined ? decoded : acc + " " + decoded;
}
/**
* Replace entities like "&nbsp;", "&#123;", and "&#xDEADBEEF;" with the characters they encode.
* See https://en.wikipedia.org/wiki/List_of_XML_and_HTML_character_entity_references
*/
function decodeEntities(text: string): string {
return text.replace(/&((#((\d+)|x([\da-fA-F]+)))|(\w+));/g, (match, _all, _number, _digits, decimal, hex, word) => {
if (decimal) {
return String.fromCharCode(parseInt(decimal, 10));
}
else if (hex) {
return String.fromCharCode(parseInt(hex, 16));
}
else {
const ch = entities.get(word);
// If this is not a valid entity, then just use `match` (replace it with itself, i.e. don't replace)
return ch ? String.fromCharCode(ch) : match;
}
});
}
/** Like `decodeEntities` but returns `undefined` if there were no entities to decode. */
function tryDecodeEntities(text: string): string | undefined {
const decoded = decodeEntities(text);
return decoded === text ? undefined : decoded;
}
function getTagName(node: JsxElement | JsxOpeningLikeElement): Expression {
if (node.kind === SyntaxKind.JsxElement) {
return getTagName((<JsxElement>node).openingElement);
}
else {
const name = (<JsxOpeningLikeElement>node).tagName;
if (isIdentifier(name) && isIntrinsicJsxName(name.text)) {
return createLiteral(name.text);
}
else {
return createExpressionFromEntityName(name);
}
}
}
/**
* Emit an attribute name, which is quoted if it needs to be quoted. Because
* these emit into an object literal property name, we don't need to be worried
* about keywords, just non-identifier characters
*/
function getAttributeName(node: JsxAttribute): StringLiteral | Identifier {
const name = node.name;
if (/^[A-Za-z_]\w*$/.test(name.text)) {
return name;
}
else {
return createLiteral(name.text);
}
}
function visitJsxExpression(node: JsxExpression) {
return visitNode(node.expression, visitor, isExpression);
}
}
const entities = createMapFromTemplate<number>({
"quot": 0x0022,
"amp": 0x0026,
"apos": 0x0027,
"lt": 0x003C,
"gt": 0x003E,
"nbsp": 0x00A0,
"iexcl": 0x00A1,
"cent": 0x00A2,
"pound": 0x00A3,
"curren": 0x00A4,
"yen": 0x00A5,
"brvbar": 0x00A6,
"sect": 0x00A7,
"uml": 0x00A8,
"copy": 0x00A9,
"ordf": 0x00AA,
"laquo": 0x00AB,
"not": 0x00AC,
"shy": 0x00AD,
"reg": 0x00AE,
"macr": 0x00AF,
"deg": 0x00B0,
"plusmn": 0x00B1,
"sup2": 0x00B2,
"sup3": 0x00B3,
"acute": 0x00B4,
"micro": 0x00B5,
"para": 0x00B6,
"middot": 0x00B7,
"cedil": 0x00B8,
"sup1": 0x00B9,
"ordm": 0x00BA,
"raquo": 0x00BB,
"frac14": 0x00BC,
"frac12": 0x00BD,
"frac34": 0x00BE,
"iquest": 0x00BF,
"Agrave": 0x00C0,
"Aacute": 0x00C1,
"Acirc": 0x00C2,
"Atilde": 0x00C3,
"Auml": 0x00C4,
"Aring": 0x00C5,
"AElig": 0x00C6,
"Ccedil": 0x00C7,
"Egrave": 0x00C8,
"Eacute": 0x00C9,
"Ecirc": 0x00CA,
"Euml": 0x00CB,
"Igrave": 0x00CC,
"Iacute": 0x00CD,
"Icirc": 0x00CE,
"Iuml": 0x00CF,
"ETH": 0x00D0,
"Ntilde": 0x00D1,
"Ograve": 0x00D2,
"Oacute": 0x00D3,
"Ocirc": 0x00D4,
"Otilde": 0x00D5,
"Ouml": 0x00D6,
"times": 0x00D7,
"Oslash": 0x00D8,
"Ugrave": 0x00D9,
"Uacute": 0x00DA,
"Ucirc": 0x00DB,
"Uuml": 0x00DC,
"Yacute": 0x00DD,
"THORN": 0x00DE,
"szlig": 0x00DF,
"agrave": 0x00E0,
"aacute": 0x00E1,
"acirc": 0x00E2,
"atilde": 0x00E3,
"auml": 0x00E4,
"aring": 0x00E5,
"aelig": 0x00E6,
"ccedil": 0x00E7,
"egrave": 0x00E8,
"eacute": 0x00E9,
"ecirc": 0x00EA,
"euml": 0x00EB,
"igrave": 0x00EC,
"iacute": 0x00ED,
"icirc": 0x00EE,
"iuml": 0x00EF,
"eth": 0x00F0,
"ntilde": 0x00F1,
"ograve": 0x00F2,
"oacute": 0x00F3,
"ocirc": 0x00F4,
"otilde": 0x00F5,
"ouml": 0x00F6,
"divide": 0x00F7,
"oslash": 0x00F8,
"ugrave": 0x00F9,
"uacute": 0x00FA,
"ucirc": 0x00FB,
"uuml": 0x00FC,
"yacute": 0x00FD,
"thorn": 0x00FE,
"yuml": 0x00FF,
"OElig": 0x0152,
"oelig": 0x0153,
"Scaron": 0x0160,
"scaron": 0x0161,
"Yuml": 0x0178,
"fnof": 0x0192,
"circ": 0x02C6,
"tilde": 0x02DC,
"Alpha": 0x0391,
"Beta": 0x0392,
"Gamma": 0x0393,
"Delta": 0x0394,
"Epsilon": 0x0395,
"Zeta": 0x0396,
"Eta": 0x0397,
"Theta": 0x0398,
"Iota": 0x0399,
"Kappa": 0x039A,
"Lambda": 0x039B,
"Mu": 0x039C,
"Nu": 0x039D,
"Xi": 0x039E,
"Omicron": 0x039F,
"Pi": 0x03A0,
"Rho": 0x03A1,
"Sigma": 0x03A3,
"Tau": 0x03A4,
"Upsilon": 0x03A5,
"Phi": 0x03A6,
"Chi": 0x03A7,
"Psi": 0x03A8,
"Omega": 0x03A9,
"alpha": 0x03B1,
"beta": 0x03B2,
"gamma": 0x03B3,
"delta": 0x03B4,
"epsilon": 0x03B5,
"zeta": 0x03B6,
"eta": 0x03B7,
"theta": 0x03B8,
"iota": 0x03B9,
"kappa": 0x03BA,
"lambda": 0x03BB,
"mu": 0x03BC,
"nu": 0x03BD,
"xi": 0x03BE,
"omicron": 0x03BF,
"pi": 0x03C0,
"rho": 0x03C1,
"sigmaf": 0x03C2,
"sigma": 0x03C3,
"tau": 0x03C4,
"upsilon": 0x03C5,
"phi": 0x03C6,
"chi": 0x03C7,
"psi": 0x03C8,
"omega": 0x03C9,
"thetasym": 0x03D1,
"upsih": 0x03D2,
"piv": 0x03D6,
"ensp": 0x2002,
"emsp": 0x2003,
"thinsp": 0x2009,
"zwnj": 0x200C,
"zwj": 0x200D,
"lrm": 0x200E,
"rlm": 0x200F,
"ndash": 0x2013,
"mdash": 0x2014,
"lsquo": 0x2018,
"rsquo": 0x2019,
"sbquo": 0x201A,
"ldquo": 0x201C,
"rdquo": 0x201D,
"bdquo": 0x201E,
"dagger": 0x2020,
"Dagger": 0x2021,
"bull": 0x2022,
"hellip": 0x2026,
"permil": 0x2030,
"prime": 0x2032,
"Prime": 0x2033,
"lsaquo": 0x2039,
"rsaquo": 0x203A,
"oline": 0x203E,
"frasl": 0x2044,
"euro": 0x20AC,
"image": 0x2111,
"weierp": 0x2118,
"real": 0x211C,
"trade": 0x2122,
"alefsym": 0x2135,
"larr": 0x2190,
"uarr": 0x2191,
"rarr": 0x2192,
"darr": 0x2193,
"harr": 0x2194,
"crarr": 0x21B5,
"lArr": 0x21D0,
"uArr": 0x21D1,
"rArr": 0x21D2,
"dArr": 0x21D3,
"hArr": 0x21D4,
"forall": 0x2200,
"part": 0x2202,
"exist": 0x2203,
"empty": 0x2205,
"nabla": 0x2207,
"isin": 0x2208,
"notin": 0x2209,
"ni": 0x220B,
"prod": 0x220F,
"sum": 0x2211,
"minus": 0x2212,
"lowast": 0x2217,
"radic": 0x221A,
"prop": 0x221D,
"infin": 0x221E,
"ang": 0x2220,
"and": 0x2227,
"or": 0x2228,
"cap": 0x2229,
"cup": 0x222A,
"int": 0x222B,
"there4": 0x2234,
"sim": 0x223C,
"cong": 0x2245,
"asymp": 0x2248,
"ne": 0x2260,
"equiv": 0x2261,
"le": 0x2264,
"ge": 0x2265,
"sub": 0x2282,
"sup": 0x2283,
"nsub": 0x2284,
"sube": 0x2286,
"supe": 0x2287,
"oplus": 0x2295,
"otimes": 0x2297,
"perp": 0x22A5,
"sdot": 0x22C5,
"lceil": 0x2308,
"rceil": 0x2309,
"lfloor": 0x230A,
"rfloor": 0x230B,
"lang": 0x2329,
"rang": 0x232A,
"loz": 0x25CA,
"spades": 0x2660,
"clubs": 0x2663,
"hearts": 0x2665,
"diams": 0x2666
});
}
+118
View File
@@ -0,0 +1,118 @@
/// <reference path="../../factory.ts" />
/// <reference path="../../visitor.ts" />
/*@internal*/
namespace ts {
export function transformES2015Module(context: TransformationContext) {
const compilerOptions = context.getCompilerOptions();
const previousOnEmitNode = context.onEmitNode;
const previousOnSubstituteNode = context.onSubstituteNode;
context.onEmitNode = onEmitNode;
context.onSubstituteNode = onSubstituteNode;
context.enableEmitNotification(SyntaxKind.SourceFile);
context.enableSubstitution(SyntaxKind.Identifier);
let currentSourceFile: SourceFile;
return transformSourceFile;
function transformSourceFile(node: SourceFile) {
if (node.isDeclarationFile) {
return node;
}
if (isExternalModule(node) || compilerOptions.isolatedModules) {
const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(node, compilerOptions);
if (externalHelpersModuleName) {
const statements: Statement[] = [];
const statementOffset = addPrologue(statements, node.statements);
append(statements,
createImportDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createImportClause(/*name*/ undefined, createNamespaceImport(externalHelpersModuleName)),
createLiteral(externalHelpersModuleNameText)
)
);
addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset));
return updateSourceFileNode(
node,
setTextRange(createNodeArray(statements), node.statements));
}
else {
return visitEachChild(node, visitor, context);
}
}
return node;
}
function visitor(node: Node): VisitResult<Node> {
switch (node.kind) {
case SyntaxKind.ImportEqualsDeclaration:
// Elide `import=` as it is not legal with --module ES6
return undefined;
case SyntaxKind.ExportAssignment:
return visitExportAssignment(<ExportAssignment>node);
}
return node;
}
function visitExportAssignment(node: ExportAssignment): VisitResult<ExportAssignment> {
// Elide `export=` as it is not legal with --module ES6
return node.isExportEquals ? undefined : node;
}
//
// Emit Notification
//
/**
* Hook for node emit.
*
* @param hint A hint as to the intended usage of the node.
* @param node The node to emit.
* @param emit A callback used to emit the node in the printer.
*/
function onEmitNode(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void {
if (isSourceFile(node)) {
currentSourceFile = node;
previousOnEmitNode(hint, node, emitCallback);
currentSourceFile = undefined;
}
else {
previousOnEmitNode(hint, node, emitCallback);
}
}
//
// Substitutions
//
/**
* Hooks node substitutions.
*
* @param hint A hint as to the intended usage of the node.
* @param node The node to substitute.
*/
function onSubstituteNode(hint: EmitHint, node: Node) {
node = previousOnSubstituteNode(hint, node);
if (isIdentifier(node) && hint === EmitHint.Expression) {
return substituteExpressionIdentifier(node);
}
return node;
}
function substituteExpressionIdentifier(node: Identifier): Expression {
if (getEmitFlags(node) & EmitFlags.HelperName) {
const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile);
if (externalHelpersModuleName) {
return createPropertyAccess(externalHelpersModuleName, node);
}
}
return node;
}
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+386 -209
View File
@@ -3,104 +3,80 @@
namespace ts {
export interface SourceFile {
fileWatcher: FileWatcher;
fileWatcher?: FileWatcher;
}
/**
* Checks to see if the locale is in the appropriate format,
* and if it is, attempts to set the appropriate language.
*/
function validateLocaleAndSetLanguage(locale: string, errors: Diagnostic[]): boolean {
var matchResult = /^([a-z]+)([_\-]([a-z]+))?$/.exec(locale.toLowerCase());
if (!matchResult) {
errors.push(createCompilerDiagnostic(Diagnostics.Locale_must_be_of_the_form_language_or_language_territory_For_example_0_or_1, 'en', 'ja-jp'));
return false;
}
var language = matchResult[1];
var territory = matchResult[3];
// First try the entire locale, then fall back to just language if that's all we have.
if (!trySetLanguageAndTerritory(language, territory, errors) &&
!trySetLanguageAndTerritory(language, undefined, errors)) {
errors.push(createCompilerDiagnostic(Diagnostics.Unsupported_locale_0, locale));
return false;
}
return true;
interface Statistic {
name: string;
value: string;
}
function trySetLanguageAndTerritory(language: string, territory: string, errors: Diagnostic[]): boolean {
var compilerFilePath = normalizePath(sys.getExecutingFilePath());
var containingDirectoryPath = getDirectoryPath(compilerFilePath);
const defaultFormatDiagnosticsHost: FormatDiagnosticsHost = {
getCurrentDirectory: () => sys.getCurrentDirectory(),
getNewLine: () => sys.newLine,
getCanonicalFileName: createGetCanonicalFileName(sys.useCaseSensitiveFileNames)
};
var filePath = combinePaths(containingDirectoryPath, language);
let reportDiagnosticWorker = reportDiagnosticSimply;
if (territory) {
filePath = filePath + "-" + territory;
function reportDiagnostic(diagnostic: Diagnostic, host: FormatDiagnosticsHost) {
reportDiagnosticWorker(diagnostic, host || defaultFormatDiagnosticsHost);
}
function reportDiagnostics(diagnostics: Diagnostic[], host: FormatDiagnosticsHost): void {
for (const diagnostic of diagnostics) {
reportDiagnostic(diagnostic, host);
}
}
function reportEmittedFiles(files: string[]): void {
if (!files || files.length === 0) {
return;
}
filePath = sys.resolvePath(combinePaths(filePath, "diagnosticMessages.generated.json"));
const currentDir = sys.getCurrentDirectory();
if (!sys.fileExists(filePath)) {
return false;
}
for (const file of files) {
const filepath = getNormalizedAbsolutePath(file, currentDir);
// TODO: Add codePage support for readFile?
try {
var fileContents = sys.readFile(filePath);
sys.write(`TSFILE: ${filepath}${sys.newLine}`);
}
catch (e) {
errors.push(createCompilerDiagnostic(Diagnostics.Unable_to_open_file_0, filePath));
return false;
}
try {
ts.localizedDiagnosticMessages = JSON.parse(fileContents);
}
catch (e) {
errors.push(createCompilerDiagnostic(Diagnostics.Corrupted_locale_file_0, filePath));
return false;
}
return true;
}
function countLines(program: Program): number {
var count = 0;
let count = 0;
forEach(program.getSourceFiles(), file => {
count += getLineStarts(file).length;
});
return count;
}
function getDiagnosticText(message: DiagnosticMessage, ...args: any[]): string {
var diagnostic = createCompilerDiagnostic.apply(undefined, arguments);
function getDiagnosticText(_message: DiagnosticMessage, ..._args: any[]): string {
const diagnostic = createCompilerDiagnostic.apply(undefined, arguments);
return <string>diagnostic.messageText;
}
function reportDiagnostic(diagnostic: Diagnostic) {
var output = "";
if (diagnostic.file) {
var loc = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
function reportDiagnosticSimply(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void {
sys.write(ts.formatDiagnostics([diagnostic], host));
}
function reportDiagnosticWithColorAndContext(diagnostic: Diagnostic, host: FormatDiagnosticsHost): void {
sys.write(ts.formatDiagnosticsWithColorAndContext([diagnostic], host) + sys.newLine + sys.newLine);
}
function reportWatchDiagnostic(diagnostic: Diagnostic) {
let output = new Date().toLocaleTimeString() + " - ";
if (diagnostic.file) {
const loc = getLineAndCharacterOfPosition(diagnostic.file, diagnostic.start);
output += `${ diagnostic.file.fileName }(${ loc.line + 1 },${ loc.character + 1 }): `;
}
var category = DiagnosticCategory[diagnostic.category].toLowerCase();
output += `${ category } TS${ diagnostic.code }: ${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine }`;
output += `${ flattenDiagnosticMessageText(diagnostic.messageText, sys.newLine) }${ sys.newLine + sys.newLine + sys.newLine }`;
sys.write(output);
}
function reportDiagnostics(diagnostics: Diagnostic[]) {
for (var i = 0; i < diagnostics.length; i++) {
reportDiagnostic(diagnostics[i]);
}
}
function padLeft(s: string, length: number) {
while (s.length < length) {
s = " " + s;
@@ -116,114 +92,178 @@ namespace ts {
return s;
}
function reportStatisticalValue(name: string, value: string) {
sys.write(padRight(name + ":", 12) + padLeft(value.toString(), 10) + sys.newLine);
}
function reportCountStatistic(name: string, count: number) {
reportStatisticalValue(name, "" + count);
}
function reportTimeStatistic(name: string, time: number) {
reportStatisticalValue(name, (time / 1000).toFixed(2) + "s");
}
function isJSONSupported() {
return typeof JSON === "object" && typeof JSON.parse === "function";
}
export function executeCommandLine(args: string[]): void {
var commandLine = parseCommandLine(args);
var configFileName: string; // Configuration file name (if any)
var configFileWatcher: FileWatcher; // Configuration file watcher
var cachedProgram: Program; // Program cached from last compilation
var rootFileNames: string[]; // Root fileNames for compilation
var compilerOptions: CompilerOptions; // Compiler options for compilation
var compilerHost: CompilerHost; // Compiler host
var hostGetSourceFile: typeof compilerHost.getSourceFile; // getSourceFile method from default host
var timerHandle: number; // Handle for 0.25s wait timer
const commandLine = parseCommandLine(args);
let configFileName: string; // Configuration file name (if any)
let cachedConfigFileText: string; // Cached configuration file text, used for reparsing (if any)
let configFileWatcher: FileWatcher; // Configuration file watcher
let directoryWatcher: FileWatcher; // Directory watcher to monitor source file addition/removal
let cachedProgram: Program; // Program cached from last compilation
let rootFileNames: string[]; // Root fileNames for compilation
let compilerOptions: CompilerOptions; // Compiler options for compilation
let compilerHost: CompilerHost; // Compiler host
let hostGetSourceFile: typeof compilerHost.getSourceFile; // getSourceFile method from default host
let timerHandleForRecompilation: any; // Handle for 0.25s wait timer to trigger recompilation
let timerHandleForDirectoryChanges: any; // Handle for 0.25s wait timer to trigger directory change handler
// This map stores and reuses results of fileExists check that happen inside 'createProgram'
// This allows to save time in module resolution heavy scenarios when existence of the same file might be checked multiple times.
let cachedExistingFiles: Map<boolean>;
let hostFileExists: typeof compilerHost.fileExists;
if (commandLine.options.locale) {
if (!isJSONSupported()) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale"));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--locale"), /* host */ undefined);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
validateLocaleAndSetLanguage(commandLine.options.locale, commandLine.errors);
validateLocaleAndSetLanguage(commandLine.options.locale, sys, commandLine.errors);
}
// If there are any errors due to command line parsing and/or
// setting up localization, report them and quit.
if (commandLine.errors.length > 0) {
reportDiagnostics(commandLine.errors);
reportDiagnostics(commandLine.errors, compilerHost);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
if (commandLine.options.version) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Version_0, ts.version));
if (commandLine.options.init) {
writeConfigFile(commandLine.options, commandLine.fileNames);
return sys.exit(ExitStatus.Success);
}
if (commandLine.options.help) {
if (commandLine.options.version) {
printVersion();
printHelp();
return sys.exit(ExitStatus.Success);
}
if (commandLine.options.help || commandLine.options.all) {
printVersion();
printHelp(commandLine.options.all);
return sys.exit(ExitStatus.Success);
}
if (commandLine.options.project) {
if (!isJSONSupported()) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project"));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--project"), /* host */ undefined);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
configFileName = normalizePath(combinePaths(commandLine.options.project, "tsconfig.json"));
if (commandLine.fileNames.length !== 0) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Option_project_cannot_be_mixed_with_source_files_on_a_command_line), /* host */ undefined);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
const fileOrDirectory = normalizePath(commandLine.options.project);
if (!fileOrDirectory /* current directory "." */ || sys.directoryExists(fileOrDirectory)) {
configFileName = combinePaths(fileOrDirectory, "tsconfig.json");
if (!sys.fileExists(configFileName)) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Cannot_find_a_tsconfig_json_file_at_the_specified_directory_Colon_0, commandLine.options.project), /* host */ undefined);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
}
else {
configFileName = fileOrDirectory;
if (!sys.fileExists(configFileName)) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_specified_path_does_not_exist_Colon_0, commandLine.options.project), /* host */ undefined);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
}
}
else if (commandLine.fileNames.length === 0 && isJSONSupported()) {
var searchPath = normalizePath(sys.getCurrentDirectory());
configFileName = findConfigFile(searchPath);
const searchPath = normalizePath(sys.getCurrentDirectory());
configFileName = findConfigFile(searchPath, sys.fileExists);
}
if (commandLine.fileNames.length === 0 && !configFileName) {
printVersion();
printHelp();
printHelp(commandLine.options.all);
return sys.exit(ExitStatus.Success);
}
// Firefox has Object.prototype.watch
if (commandLine.options.watch && commandLine.options.hasOwnProperty("watch")) {
if (isWatchSet(commandLine.options)) {
if (!sys.watchFile) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
if (configFileName) {
configFileWatcher = sys.watchFile(configFileName, configFileChanged);
}
if (sys.watchDirectory && configFileName) {
const directory = ts.getDirectoryPath(configFileName);
directoryWatcher = sys.watchDirectory(
// When the configFileName is just "tsconfig.json", the watched directory should be
// the current directory; if there is a given "project" parameter, then the configFileName
// is an absolute file name.
directory === "" ? "." : directory,
watchedDirectoryChanged, /*recursive*/ true);
}
}
performCompilation();
function parseConfigFile(): ParsedCommandLine {
if (!cachedConfigFileText) {
try {
cachedConfigFileText = sys.readFile(configFileName);
}
catch (e) {
const error = createCompilerDiagnostic(Diagnostics.Cannot_read_file_0_Colon_1, configFileName, e.message);
reportWatchDiagnostic(error);
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
return;
}
}
if (!cachedConfigFileText) {
const error = createCompilerDiagnostic(Diagnostics.File_0_not_found, configFileName);
reportDiagnostics([error], /* compilerHost */ undefined);
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
return;
}
const result = parseConfigFileTextToJson(configFileName, cachedConfigFileText);
const configObject = result.config;
if (!configObject) {
reportDiagnostics([result.error], /* compilerHost */ undefined);
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
return;
}
const cwd = sys.getCurrentDirectory();
const configParseResult = parseJsonConfigFileContent(configObject, sys, getNormalizedAbsolutePath(getDirectoryPath(configFileName), cwd), commandLine.options, getNormalizedAbsolutePath(configFileName, cwd));
if (configParseResult.errors.length > 0) {
reportDiagnostics(configParseResult.errors, /* compilerHost */ undefined);
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
return;
}
if (isWatchSet(configParseResult.options)) {
if (!sys.watchFile) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.The_current_host_does_not_support_the_0_option, "--watch"), /* host */ undefined);
sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
if (!directoryWatcher && sys.watchDirectory && configFileName) {
const directory = ts.getDirectoryPath(configFileName);
directoryWatcher = sys.watchDirectory(
// When the configFileName is just "tsconfig.json", the watched directory should be
// the current directory; if there is a given "project" parameter, then the configFileName
// is an absolute file name.
directory === "" ? "." : directory,
watchedDirectoryChanged, /*recursive*/ true);
}
}
return configParseResult;
}
// Invoked to perform initial compilation or re-compilation in watch mode
function performCompilation() {
if (!cachedProgram) {
if (configFileName) {
let result = readConfigFile(configFileName);
if (result.error) {
reportDiagnostic(result.error);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
let configObject = result.config;
let configParseResult = parseConfigFile(configObject, sys, getDirectoryPath(configFileName));
if (configParseResult.errors.length > 0) {
reportDiagnostics(configParseResult.errors);
return sys.exit(ExitStatus.DiagnosticsPresent_OutputsSkipped);
}
const configParseResult = parseConfigFile();
rootFileNames = configParseResult.fileNames;
compilerOptions = extend(commandLine.options, configParseResult.options);
compilerOptions = configParseResult.options;
}
else {
rootFileNames = commandLine.fileNames;
@@ -232,32 +272,50 @@ namespace ts {
compilerHost = createCompilerHost(compilerOptions);
hostGetSourceFile = compilerHost.getSourceFile;
compilerHost.getSourceFile = getSourceFile;
hostFileExists = compilerHost.fileExists;
compilerHost.fileExists = cachedFileExists;
}
let compileResult = compile(rootFileNames, compilerOptions, compilerHost);
if (compilerOptions.pretty) {
reportDiagnosticWorker = reportDiagnosticWithColorAndContext;
}
if (!compilerOptions.watch) {
// reset the cache of existing files
cachedExistingFiles = createMap<boolean>();
const compileResult = compile(rootFileNames, compilerOptions, compilerHost);
if (!isWatchSet(compilerOptions)) {
return sys.exit(compileResult.exitStatus);
}
setCachedProgram(compileResult.program);
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.Compilation_complete_Watching_for_file_changes));
}
function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError ?: (message: string) => void) {
function cachedFileExists(fileName: string): boolean {
let fileExists = cachedExistingFiles.get(fileName);
if (fileExists === undefined) {
cachedExistingFiles.set(fileName, fileExists = hostFileExists(fileName));
}
return fileExists;
}
function getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void) {
// Return existing SourceFile object if one is available
if (cachedProgram) {
var sourceFile = cachedProgram.getSourceFile(fileName);
const sourceFile = cachedProgram.getSourceFile(fileName);
// A modified source file has no watcher and should not be reused
if (sourceFile && sourceFile.fileWatcher) {
return sourceFile;
}
}
// Use default host function
var sourceFile = hostGetSourceFile(fileName, languageVersion, onError);
if (sourceFile && compilerOptions.watch) {
const sourceFile = hostGetSourceFile(fileName, languageVersion, onError);
if (sourceFile && isWatchSet(compilerOptions) && sys.watchFile) {
// Attach a file watcher
sourceFile.fileWatcher = sys.watchFile(sourceFile.fileName, () => sourceFileChanged(sourceFile));
sourceFile.fileWatcher = sys.watchFile(sourceFile.fileName, (_fileName: string, removed?: boolean) => sourceFileChanged(sourceFile, removed));
}
return sourceFile;
}
@@ -265,7 +323,7 @@ namespace ts {
// Change cached program to the given program
function setCachedProgram(program: Program) {
if (cachedProgram) {
var newSourceFiles = program ? program.getSourceFiles() : undefined;
const newSourceFiles = program ? program.getSourceFiles() : undefined;
forEach(cachedProgram.getSourceFiles(), sourceFile => {
if (!(newSourceFiles && contains(newSourceFiles, sourceFile))) {
if (sourceFile.fileWatcher) {
@@ -279,45 +337,84 @@ namespace ts {
}
// If a source file changes, mark it as unwatched and start the recompilation timer
function sourceFileChanged(sourceFile: SourceFile) {
function sourceFileChanged(sourceFile: SourceFile, removed?: boolean) {
sourceFile.fileWatcher.close();
sourceFile.fileWatcher = undefined;
startTimer();
if (removed) {
unorderedRemoveItem(rootFileNames, sourceFile.fileName);
}
startTimerForRecompilation();
}
// If the configuration file changes, forget cached program and start the recompilation timer
function configFileChanged() {
setCachedProgram(undefined);
startTimer();
cachedConfigFileText = undefined;
startTimerForRecompilation();
}
function watchedDirectoryChanged(fileName: string) {
if (fileName && !ts.isSupportedSourceFileName(fileName, compilerOptions)) {
return;
}
startTimerForHandlingDirectoryChanges();
}
function startTimerForHandlingDirectoryChanges() {
if (!sys.setTimeout || !sys.clearTimeout) {
return;
}
if (timerHandleForDirectoryChanges) {
sys.clearTimeout(timerHandleForDirectoryChanges);
}
timerHandleForDirectoryChanges = sys.setTimeout(directoryChangeHandler, 250);
}
function directoryChangeHandler() {
const parsedCommandLine = parseConfigFile();
const newFileNames = ts.map(parsedCommandLine.fileNames, compilerHost.getCanonicalFileName);
const 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 && newFileNames.sort(), canonicalRootFileNames && canonicalRootFileNames.sort())) {
setCachedProgram(undefined);
startTimerForRecompilation();
}
}
// Upon detecting a file change, wait for 250ms and then perform a recompilation. This gives batch
// operations (such as saving all modified files in an editor) a chance to complete before we kick
// off a new compilation.
function startTimer() {
if (timerHandle) {
clearTimeout(timerHandle);
function startTimerForRecompilation() {
if (!sys.setTimeout || !sys.clearTimeout) {
return;
}
timerHandle = setTimeout(recompile, 250);
if (timerHandleForRecompilation) {
sys.clearTimeout(timerHandleForRecompilation);
}
timerHandleForRecompilation = sys.setTimeout(recompile, 250);
}
function recompile() {
timerHandle = undefined;
reportDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
timerHandleForRecompilation = undefined;
reportWatchDiagnostic(createCompilerDiagnostic(Diagnostics.File_change_detected_Starting_incremental_compilation));
performCompilation();
}
}
function compile(fileNames: string[], compilerOptions: CompilerOptions, compilerHost: CompilerHost) {
ioReadTime = 0;
ioWriteTime = 0;
programTime = 0;
bindTime = 0;
checkTime = 0;
emitTime = 0;
const hasDiagnostics = compilerOptions.diagnostics || compilerOptions.extendedDiagnostics;
let statistics: Statistic[];
if (hasDiagnostics) {
performance.enable();
statistics = [];
}
var program = createProgram(fileNames, compilerOptions, compilerHost);
var exitStatus = compileProgram();
const program = createProgram(fileNames, compilerOptions, compilerHost);
const exitStatus = compileProgram();
if (compilerOptions.listFiles) {
forEach(program.getSourceFiles(), file => {
@@ -325,8 +422,8 @@ namespace ts {
});
}
if (compilerOptions.diagnostics) {
var memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1;
if (hasDiagnostics) {
const memoryUsed = sys.getMemoryUsage ? sys.getMemoryUsage() : -1;
reportCountStatistic("Files", program.getSourceFiles().length);
reportCountStatistic("Lines", countLines(program));
reportCountStatistic("Nodes", program.getNodeCount());
@@ -338,104 +435,143 @@ namespace ts {
reportStatisticalValue("Memory used", Math.round(memoryUsed / 1000) + "K");
}
// Individual component times.
// Note: To match the behavior of previous versions of the compiler, the reported parse time includes
// I/O read time and processing time for triple-slash references and module imports, and the reported
// emit time includes I/O write time. We preserve this behavior so we can accurately compare times.
reportTimeStatistic("I/O read", ioReadTime);
reportTimeStatistic("I/O write", ioWriteTime);
reportTimeStatistic("Parse time", programTime);
reportTimeStatistic("Bind time", bindTime);
reportTimeStatistic("Check time", checkTime);
reportTimeStatistic("Emit time", emitTime);
const programTime = performance.getDuration("Program");
const bindTime = performance.getDuration("Bind");
const checkTime = performance.getDuration("Check");
const emitTime = performance.getDuration("Emit");
if (compilerOptions.extendedDiagnostics) {
performance.forEachMeasure((name, duration) => reportTimeStatistic(`${name} time`, duration));
}
else {
// Individual component times.
// Note: To match the behavior of previous versions of the compiler, the reported parse time includes
// I/O read time and processing time for triple-slash references and module imports, and the reported
// emit time includes I/O write time. We preserve this behavior so we can accurately compare times.
reportTimeStatistic("I/O read", performance.getDuration("I/O Read"));
reportTimeStatistic("I/O write", performance.getDuration("I/O Write"));
reportTimeStatistic("Parse time", programTime);
reportTimeStatistic("Bind time", bindTime);
reportTimeStatistic("Check time", checkTime);
reportTimeStatistic("Emit time", emitTime);
}
reportTimeStatistic("Total time", programTime + bindTime + checkTime + emitTime);
reportStatistics();
performance.disable();
}
return { program, exitStatus };
function compileProgram(): ExitStatus {
// First get any syntactic errors.
var diagnostics = program.getSyntacticDiagnostics();
reportDiagnostics(diagnostics);
let diagnostics: Diagnostic[];
// If we didn't have any syntactic errors, then also try getting the global and
// First get and report any syntactic errors.
diagnostics = program.getSyntacticDiagnostics();
// If we didn't have any syntactic errors, then also try getting the global and
// semantic errors.
if (diagnostics.length === 0) {
var diagnostics = program.getGlobalDiagnostics();
reportDiagnostics(diagnostics);
diagnostics = program.getOptionsDiagnostics().concat(program.getGlobalDiagnostics());
if (diagnostics.length === 0) {
var diagnostics = program.getSemanticDiagnostics();
reportDiagnostics(diagnostics);
diagnostics = program.getSemanticDiagnostics();
}
}
// If the user doesn't want us to emit, then we're done at this point.
if (compilerOptions.noEmit) {
return diagnostics.length
? ExitStatus.DiagnosticsPresent_OutputsSkipped
: ExitStatus.Success;
}
// Otherwise, emit and report any errors we ran into.
var emitOutput = program.emit();
reportDiagnostics(emitOutput.diagnostics);
const emitOutput = program.emit();
diagnostics = diagnostics.concat(emitOutput.diagnostics);
// If the emitter didn't emit anything, then pass that value along.
if (emitOutput.emitSkipped) {
reportDiagnostics(sortAndDeduplicateDiagnostics(diagnostics), compilerHost);
reportEmittedFiles(emitOutput.emittedFiles);
if (emitOutput.emitSkipped && diagnostics.length > 0) {
// If the emitter didn't emit anything, then pass that value along.
return ExitStatus.DiagnosticsPresent_OutputsSkipped;
}
// The emitter emitted something, inform the caller if that happened in the presence
// of diagnostics or not.
if (diagnostics.length > 0 || emitOutput.diagnostics.length > 0) {
else if (diagnostics.length > 0) {
// The emitter emitted something, inform the caller if that happened in the presence
// of diagnostics or not.
return ExitStatus.DiagnosticsPresent_OutputsGenerated;
}
return ExitStatus.Success;
}
function reportStatistics() {
let nameSize = 0;
let valueSize = 0;
for (const { name, value } of statistics) {
if (name.length > nameSize) {
nameSize = name.length;
}
if (value.length > valueSize) {
valueSize = value.length;
}
}
for (const { name, value } of statistics) {
sys.write(padRight(name + ":", nameSize + 2) + padLeft(value.toString(), valueSize) + sys.newLine);
}
}
function reportStatisticalValue(name: string, value: string) {
statistics.push({ name, value });
}
function reportCountStatistic(name: string, count: number) {
reportStatisticalValue(name, "" + count);
}
function reportTimeStatistic(name: string, time: number) {
reportStatisticalValue(name, (time / 1000).toFixed(2) + "s");
}
}
function printVersion() {
sys.write(getDiagnosticText(Diagnostics.Version_0, ts.version) + sys.newLine);
}
function printHelp() {
var output = "";
function printHelp(showAllOptions: boolean) {
const output: string[] = [];
// We want to align our "syntax" and "examples" commands to a certain margin.
var syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length;
var examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length;
var marginLength = Math.max(syntaxLength, examplesLength);
const syntaxLength = getDiagnosticText(Diagnostics.Syntax_Colon_0, "").length;
const examplesLength = getDiagnosticText(Diagnostics.Examples_Colon_0, "").length;
let marginLength = Math.max(syntaxLength, examplesLength);
// Build up the syntactic skeleton.
var syntax = makePadding(marginLength - syntaxLength);
let syntax = makePadding(marginLength - syntaxLength);
syntax += "tsc [" + getDiagnosticText(Diagnostics.options) + "] [" + getDiagnosticText(Diagnostics.file) + " ...]";
output += getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax);
output += sys.newLine + sys.newLine;
output.push(getDiagnosticText(Diagnostics.Syntax_Colon_0, syntax));
output.push(sys.newLine + sys.newLine);
// Build up the list of examples.
var padding = makePadding(marginLength);
output += getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine;
output += padding + "tsc --out file.js file.ts" + sys.newLine;
output += padding + "tsc @args.txt" + sys.newLine;
output += sys.newLine;
const padding = makePadding(marginLength);
output.push(getDiagnosticText(Diagnostics.Examples_Colon_0, makePadding(marginLength - examplesLength) + "tsc hello.ts") + sys.newLine);
output.push(padding + "tsc --outFile file.js file.ts" + sys.newLine);
output.push(padding + "tsc @args.txt" + sys.newLine);
output.push(sys.newLine);
output += getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine;
output.push(getDiagnosticText(Diagnostics.Options_Colon) + sys.newLine);
// Sort our options by their names, (e.g. "--noImplicitAny" comes before "--watch")
var optsList = filter(optionDeclarations.slice(), v => !v.experimental);
optsList.sort((a, b) => compareValues<string>(a.name.toLowerCase(), b.name.toLowerCase()));
const optsList = showAllOptions ?
optionDeclarations.slice().sort((a, b) => compareValues<string>(a.name.toLowerCase(), b.name.toLowerCase())) :
filter(optionDeclarations.slice(), v => v.showInSimplifiedHelpView);
// We want our descriptions to align at the same column in our output,
// so we keep track of the longest option usage string.
var marginLength = 0;
var usageColumn: string[] = []; // Things like "-d, --declaration" go in here.
var descriptionColumn: string[] = [];
marginLength = 0;
const usageColumn: string[] = []; // Things like "-d, --declaration" go in here.
const descriptionColumn: string[] = [];
for (var i = 0; i < optsList.length; i++) {
var option = optsList[i];
const optionsDescriptionMap = createMap<string[]>(); // Map between option.description and list of option.type if it is a kind
for (let i = 0; i < optsList.length; i++) {
const option = optsList[i];
// If an option lacks a description,
// it is not officially supported.
@@ -443,7 +579,7 @@ namespace ts {
continue;
}
var usageText = " ";
let usageText = " ";
if (option.shortName) {
usageText += "-" + option.shortName;
usageText += getParamType(option);
@@ -454,26 +590,49 @@ namespace ts {
usageText += getParamType(option);
usageColumn.push(usageText);
descriptionColumn.push(getDiagnosticText(option.description));
let description: string;
if (option.name === "lib") {
description = getDiagnosticText(option.description);
const element = (<CommandLineOptionOfListType>option).element;
const typeMap = <Map<number | string>>element.type;
optionsDescriptionMap.set(description, arrayFrom(typeMap.keys()).map(key => `'${key}'`));
}
else {
description = getDiagnosticText(option.description);
}
descriptionColumn.push(description);
// Set the new margin for the description column if necessary.
marginLength = Math.max(usageText.length, marginLength);
}
// Special case that can't fit in the loop.
var usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">";
const usageText = " @<" + getDiagnosticText(Diagnostics.file) + ">";
usageColumn.push(usageText);
descriptionColumn.push(getDiagnosticText(Diagnostics.Insert_command_line_options_and_files_from_a_file));
marginLength = Math.max(usageText.length, marginLength);
// Print out each row, aligning all the descriptions on the same column.
for (var i = 0; i < usageColumn.length; i++) {
var usage = usageColumn[i];
var description = descriptionColumn[i];
output += usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine;
for (let i = 0; i < usageColumn.length; i++) {
const usage = usageColumn[i];
const description = descriptionColumn[i];
const kindsList = optionsDescriptionMap.get(description);
output.push(usage + makePadding(marginLength - usage.length + 2) + description + sys.newLine);
if (kindsList) {
output.push(makePadding(marginLength + 4));
for (const kind of kindsList) {
output.push(kind + " ");
}
output.push(sys.newLine);
}
}
sys.write(output);
for (const line of output) {
sys.write(line);
}
return;
function getParamType(option: CommandLineOption) {
@@ -487,6 +646,24 @@ namespace ts {
return Array(paddingLength + 1).join(" ");
}
}
function writeConfigFile(options: CompilerOptions, fileNames: string[]) {
const currentDirectory = sys.getCurrentDirectory();
const file = normalizePath(combinePaths(currentDirectory, "tsconfig.json"));
if (sys.fileExists(file)) {
reportDiagnostic(createCompilerDiagnostic(Diagnostics.A_tsconfig_json_file_is_already_defined_at_Colon_0, file), /* host */ undefined);
}
else {
sys.writeFile(file, generateTSConfig(options, fileNames, sys.newLine));
reportDiagnostic(createCompilerDiagnostic(Diagnostics.Successfully_created_a_tsconfig_json_file), /* host */ undefined);
}
return;
}
}
if (ts.sys.tryEnableSourceMapsForHost && /^development$/i.test(ts.sys.getEnvironmentVariable("NODE_ENV"))) {
ts.sys.tryEnableSourceMapsForHost();
}
ts.executeCommandLine(ts.sys.args);
+23 -5
View File
@@ -1,14 +1,13 @@
{
"extends": "../tsconfig-base",
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"out": "../../built/local/tsc.js",
"sourceMap": true
"outFile": "../../built/local/tsc.js",
"declaration": true
},
"files": [
"core.ts",
"performance.ts",
"sys.ts",
"types.ts",
"scanner.ts",
@@ -16,6 +15,25 @@
"utilities.ts",
"binder.ts",
"checker.ts",
"factory.ts",
"visitor.ts",
"transformers/ts.ts",
"transformers/jsx.ts",
"transformers/esnext.ts",
"transformers/es2017.ts",
"transformers/es2016.ts",
"transformers/es2015.ts",
"transformers/es5.ts",
"transformers/generators.ts",
"transformers/es5.ts",
"transformers/destructuring.ts",
"transformers/module/module.ts",
"transformers/module/system.ts",
"transformers/module/es2015.ts",
"transformer.ts",
"comments.ts",
"sourcemap.ts",
"declarationEmitter.ts",
"emitter.ts",
"program.ts",
"commandLineParser.ts",
+2579 -470
View File
File diff suppressed because it is too large Load Diff
+3209 -788
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+113 -316
View File
@@ -1,6 +1,6 @@
/// <reference path='harness.ts' />
/// <reference path='runnerbase.ts' />
/// <reference path='typeWriter.ts' />
/// <reference path="harness.ts" />
/// <reference path="runnerbase.ts" />
/// <reference path="typeWriter.ts" />
const enum CompilerTestType {
Conformance,
@@ -9,7 +9,8 @@ const enum CompilerTestType {
}
class CompilerBaselineRunner extends RunnerBase {
private basePath = 'tests/cases';
private basePath = "tests/cases";
private testSuiteName: TestRunnerKind;
private errors: boolean;
private emit: boolean;
private decl: boolean;
@@ -24,385 +25,181 @@ class CompilerBaselineRunner extends RunnerBase {
this.decl = true;
this.output = true;
if (testType === CompilerTestType.Conformance) {
this.basePath += '/conformance';
this.testSuiteName = "conformance";
}
else if (testType === CompilerTestType.Regressions) {
this.basePath += '/compiler';
this.testSuiteName = "compiler";
}
else if (testType === CompilerTestType.Test262) {
this.basePath += '/test262';
} else {
this.basePath += '/compiler'; // default to this for historical reasons
this.testSuiteName = "test262";
}
else {
this.testSuiteName = "compiler"; // default to this for historical reasons
}
this.basePath += "/" + this.testSuiteName;
}
public kind() {
return this.testSuiteName;
}
public enumerateTestFiles() {
return this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true });
}
private makeUnitName(name: string, root: string) {
const path = ts.toPath(name, root, (fileName) => Harness.Compiler.getCanonicalFileName(fileName));
const pathStart = ts.toPath(Harness.IO.getCurrentDirectory(), "", (fileName) => Harness.Compiler.getCanonicalFileName(fileName));
return pathStart ? path.replace(pathStart, "/") : path;
}
public checkTestCodeOutput(fileName: string) {
describe('compiler tests for ' + fileName, () => {
describe("compiler tests for " + fileName, () => {
// Mocha holds onto the closure environment of the describe callback even after the test is done.
// Everything declared here should be cleared out in the "after" callback.
var justName: string;
var content: string;
var testCaseContent: { settings: Harness.TestCaseParser.CompilerSetting[]; testUnitData: Harness.TestCaseParser.TestUnitData[]; }
let justName: string;
let lastUnit: Harness.TestCaseParser.TestUnitData;
let harnessSettings: Harness.TestCaseParser.CompilerSettings;
let hasNonDtsFiles: boolean;
var units: Harness.TestCaseParser.TestUnitData[];
var tcSettings: Harness.TestCaseParser.CompilerSetting[];
var createNewInstance: boolean;
var lastUnit: Harness.TestCaseParser.TestUnitData;
var rootDir: string;
var result: Harness.Compiler.CompilerResult;
var program: ts.Program;
var options: ts.CompilerOptions;
let result: Harness.Compiler.CompilerResult;
let options: ts.CompilerOptions;
// equivalent to the files that will be passed on the command line
var toBeCompiled: { unitName: string; content: string }[];
let toBeCompiled: Harness.Compiler.TestFile[];
// equivalent to other files on the file system not directly passed to the compiler (ie things that are referenced by other files)
var otherFiles: { unitName: string; content: string }[];
var harnessCompiler: Harness.Compiler.HarnessCompiler;
var createNewInstance = false;
let otherFiles: Harness.Compiler.TestFile[];
before(() => {
justName = fileName.replace(/^.*[\\\/]/, ''); // strips the fileName from the path.
content = Harness.IO.readFile(fileName);
testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(content, fileName);
units = testCaseContent.testUnitData;
tcSettings = testCaseContent.settings;
createNewInstance = false;
justName = fileName.replace(/^.*[\\\/]/, ""); // strips the fileName from the path.
const content = Harness.IO.readFile(fileName);
const rootDir = fileName.indexOf("conformance") === -1 ? "tests/cases/compiler/" : ts.getDirectoryPath(fileName) + "/";
const testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(content, fileName, rootDir);
const units = testCaseContent.testUnitData;
harnessSettings = testCaseContent.settings;
let tsConfigOptions: ts.CompilerOptions;
if (testCaseContent.tsConfig) {
assert.equal(testCaseContent.tsConfig.fileNames.length, 0, `list of files in tsconfig is not currently supported`);
tsConfigOptions = ts.clone(testCaseContent.tsConfig.options);
}
else {
const baseUrl = harnessSettings["baseUrl"];
if (baseUrl !== undefined && !ts.isRootedDiskPath(baseUrl)) {
harnessSettings["baseUrl"] = ts.getNormalizedAbsolutePath(baseUrl, rootDir);
}
}
lastUnit = units[units.length - 1];
rootDir = lastUnit.originalFilePath.indexOf('conformance') === -1 ? 'tests/cases/compiler/' : lastUnit.originalFilePath.substring(0, lastUnit.originalFilePath.lastIndexOf('/')) + '/';
harnessCompiler = Harness.Compiler.getCompiler();
hasNonDtsFiles = ts.forEach(units, unit => !ts.fileExtensionIs(unit.name, ".d.ts"));
// We need to assemble the list of input files for the compiler and other related files on the 'filesystem' (ie in a multi-file test)
// If the last file in a test uses require or a triple slash reference we'll assume all other files will be brought in via references,
// otherwise, assume all files are just meant to be in the same compilation session without explicit references to one another.
toBeCompiled = [];
otherFiles = [];
if (/require\(/.test(lastUnit.content) || /reference\spath/.test(lastUnit.content)) {
toBeCompiled.push({ unitName: rootDir + lastUnit.name, content: lastUnit.content });
if (testCaseContent.settings["noImplicitReferences"] || /require\(/.test(lastUnit.content) || /reference\spath/.test(lastUnit.content)) {
toBeCompiled.push({ unitName: this.makeUnitName(lastUnit.name, rootDir), content: lastUnit.content, fileOptions: lastUnit.fileOptions });
units.forEach(unit => {
if (unit.name !== lastUnit.name) {
otherFiles.push({ unitName: rootDir + unit.name, content: unit.content });
otherFiles.push({ unitName: this.makeUnitName(unit.name, rootDir), content: unit.content, fileOptions: unit.fileOptions });
}
});
} else {
}
else {
toBeCompiled = units.map(unit => {
return { unitName: rootDir + unit.name, content: unit.content };
return { unitName: this.makeUnitName(unit.name, rootDir), content: unit.content, fileOptions: unit.fileOptions };
});
}
options = harnessCompiler.compileFiles(toBeCompiled, otherFiles, function (compileResult, _program) {
result = compileResult;
// The program will be used by typeWriter
program = _program;
}, function (settings) {
harnessCompiler.setCompilerSettings(tcSettings);
});
});
beforeEach(() => {
/* The compiler doesn't handle certain flags flipping during a single compilation setting. Tests on these flags will need
a fresh compiler instance for themselves and then create a fresh one for the next test. Would be nice to get dev fixes
eventually to remove this limitation. */
for (var i = 0; i < tcSettings.length; ++i) {
// noImplicitAny is passed to getCompiler, but target is just passed in the settings blob to setCompilerSettings
if (!createNewInstance && (tcSettings[i].flag == "noimplicitany" || tcSettings[i].flag === 'target')) {
harnessCompiler = Harness.Compiler.getCompiler();
harnessCompiler.setCompilerSettings(tcSettings);
createNewInstance = true;
}
if (tsConfigOptions && tsConfigOptions.configFilePath !== undefined) {
tsConfigOptions.configFilePath = ts.combinePaths(rootDir, tsConfigOptions.configFilePath);
}
});
afterEach(() => {
if (createNewInstance) {
harnessCompiler = Harness.Compiler.getCompiler();
createNewInstance = false;
}
const output = Harness.Compiler.compileFiles(
toBeCompiled, otherFiles, harnessSettings, /*options*/ tsConfigOptions, /*currentDirectory*/ harnessSettings["currentDirectory"]);
options = output.options;
result = output.result;
});
after(() => {
// Mocha holds onto the closure environment of the describe callback even after the test is done.
// Therefore we have to clean out large objects after the test is done.
justName = undefined;
content = undefined;
testCaseContent = undefined;
units = undefined;
tcSettings = undefined;
lastUnit = undefined;
rootDir = undefined;
hasNonDtsFiles = undefined;
result = undefined;
program = undefined;
options = undefined;
toBeCompiled = undefined;
otherFiles = undefined;
harnessCompiler = undefined;
});
function getByteOrderMarkText(file: Harness.Compiler.GeneratedFile): string {
return file.writeByteOrderMark ? "\u00EF\u00BB\u00BF" : "";
}
function getErrorBaseline(toBeCompiled: { unitName: string; content: string }[], otherFiles: { unitName: string; content: string }[], result: Harness.Compiler.CompilerResult) {
return Harness.Compiler.getErrorBaseline(toBeCompiled.concat(otherFiles), result.errors);
}
// check errors
it('Correct errors for ' + fileName, () => {
if (this.errors) {
Harness.Baseline.runBaseline('Correct errors for ' + fileName, justName.replace(/\.tsx?$/, '.errors.txt'), (): string => {
if (result.errors.length === 0) return null;
return getErrorBaseline(toBeCompiled, otherFiles, result);
it("Correct errors for " + fileName, () => {
Harness.Compiler.doErrorBaseline(justName, toBeCompiled.concat(otherFiles), result.errors);
});
it (`Correct module resolution tracing for ${fileName}`, () => {
if (options.traceResolution) {
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".trace.json"), () => {
return JSON.stringify(result.traceResults || [], undefined, 4);
});
}
});
// Source maps?
it('Correct sourcemap content for ' + fileName, () => {
it("Correct sourcemap content for " + fileName, () => {
if (options.sourceMap || options.inlineSourceMap) {
Harness.Baseline.runBaseline('Correct sourcemap content for ' + fileName, justName.replace(/\.tsx?$/, '.sourcemap.txt'), () => {
var record = result.getSourceMapRecord();
if (options.noEmitOnError && result.errors.length !== 0 && record === undefined) {
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".sourcemap.txt"), () => {
const record = result.getSourceMapRecord();
if ((options.noEmitOnError && result.errors.length !== 0) || record === undefined) {
// Because of the noEmitOnError option no files are created. We need to return null because baselining isn't required.
/* tslint:disable:no-null-keyword */
return null;
/* tslint:enable:no-null-keyword */
}
return record;
});
}
});
it('Correct JS output for ' + fileName, () => {
if (!ts.fileExtensionIs(lastUnit.name, '.d.ts') && this.emit) {
if (result.files.length === 0 && result.errors.length === 0) {
throw new Error('Expected at least one js file to be emitted or at least one error to be created.');
}
// check js output
Harness.Baseline.runBaseline('Correct JS output for ' + fileName, justName.replace(/\.tsx?/, '.js'), () => {
var tsCode = '';
var tsSources = otherFiles.concat(toBeCompiled);
if (tsSources.length > 1) {
tsCode += '//// [' + fileName + '] ////\r\n\r\n';
}
for (var i = 0; i < tsSources.length; i++) {
tsCode += '//// [' + Harness.Path.getFileName(tsSources[i].unitName) + ']\r\n';
tsCode += tsSources[i].content + (i < (tsSources.length - 1) ? '\r\n' : '');
}
var jsCode = '';
for (var i = 0; i < result.files.length; i++) {
jsCode += '//// [' + Harness.Path.getFileName(result.files[i].fileName) + ']\r\n';
jsCode += getByteOrderMarkText(result.files[i]);
jsCode += result.files[i].code;
}
if (result.declFilesCode.length > 0) {
jsCode += '\r\n\r\n';
for (var i = 0; i < result.declFilesCode.length; i++) {
jsCode += '//// [' + Harness.Path.getFileName(result.declFilesCode[i].fileName) + ']\r\n';
jsCode += getByteOrderMarkText(result.declFilesCode[i]);
jsCode += result.declFilesCode[i].code;
}
}
var declFileCompilationResult = harnessCompiler.compileDeclarationFiles(toBeCompiled, otherFiles, result, function (settings) {
harnessCompiler.setCompilerSettings(tcSettings);
}, options);
if (declFileCompilationResult && declFileCompilationResult.declResult.errors.length) {
jsCode += '\r\n\r\n//// [DtsFileErrors]\r\n';
jsCode += '\r\n\r\n';
jsCode += getErrorBaseline(declFileCompilationResult.declInputFiles, declFileCompilationResult.declOtherFiles, declFileCompilationResult.declResult);
}
if (jsCode.length > 0) {
return tsCode + '\r\n\r\n' + jsCode;
} else {
return null;
}
});
it("Correct JS output for " + fileName, () => {
if (hasNonDtsFiles && this.emit) {
Harness.Compiler.doJsEmitBaseline(justName, fileName, options, result, toBeCompiled, otherFiles, harnessSettings);
}
});
it('Correct Sourcemap output for ' + fileName, () => {
if (options.inlineSourceMap) {
if (result.sourceMaps.length > 0) {
throw new Error('No sourcemap files should be generated if inlineSourceMaps was set.');
}
return null;
}
else if (options.sourceMap) {
if (result.sourceMaps.length !== result.files.length) {
throw new Error('Number of sourcemap files should be same as js files.');
}
Harness.Baseline.runBaseline('Correct Sourcemap output for ' + fileName, justName.replace(/\.tsx?/, '.js.map'), () => {
if (options.noEmitOnError && result.errors.length !== 0 && result.sourceMaps.length === 0) {
// We need to return null here or the runBaseLine will actually create a empty file.
// Baselining isn't required here because there is no output.
return null;
}
var sourceMapCode = '';
for (var i = 0; i < result.sourceMaps.length; i++) {
sourceMapCode += '//// [' + Harness.Path.getFileName(result.sourceMaps[i].fileName) + ']\r\n';
sourceMapCode += getByteOrderMarkText(result.sourceMaps[i]);
sourceMapCode += result.sourceMaps[i].code;
}
return sourceMapCode;
});
}
it("Correct Sourcemap output for " + fileName, () => {
Harness.Compiler.doSourcemapBaseline(justName, options, result, harnessSettings);
});
it('Correct type/symbol baselines for ' + fileName, () => {
it("Correct type/symbol baselines for " + fileName, () => {
if (fileName.indexOf("APISample") >= 0) {
return;
}
// NEWTODO: Type baselines
if (result.errors.length === 0) {
// The full walker simulates the types that you would get from doing a full
// compile. The pull walker simulates the types you get when you just do
// a type query for a random node (like how the LS would do it). Most of the
// time, these will be the same. However, occasionally, they can be different.
// Specifically, when the compiler internally depends on symbol IDs to order
// things, then we may see different results because symbols can be created in a
// different order with 'pull' operations, and thus can produce slightly differing
// output.
//
// For example, with a full type check, we may see a type outputed as: number | string
// But with a pull type check, we may see it as: string | number
//
// These types are equivalent, but depend on what order the compiler observed
// certain parts of the program.
let allFiles = toBeCompiled.concat(otherFiles).filter(file => !!program.getSourceFile(file.unitName));
let fullWalker = new TypeWriterWalker(program, /*fullTypeCheck:*/ true);
let pullWalker = new TypeWriterWalker(program, /*fullTypeCheck:*/ false);
let fullResults: ts.Map<TypeWriterResult[]> = {};
let pullResults: ts.Map<TypeWriterResult[]> = {};
for (let sourceFile of allFiles) {
fullResults[sourceFile.unitName] = fullWalker.getTypeAndSymbols(sourceFile.unitName);
pullResults[sourceFile.unitName] = fullWalker.getTypeAndSymbols(sourceFile.unitName);
}
// Produce baselines. The first gives the types for all expressions.
// The second gives symbols for all identifiers.
var e1: Error, e2: Error;
try {
checkBaseLines(/*isSymbolBaseLine:*/ false);
}
catch (e) {
e1 = e;
}
try {
checkBaseLines(/*isSymbolBaseLine:*/ true);
}
catch (e) {
e2 = e;
}
if (e1 || e2) {
throw e1 || e2;
}
return;
function checkBaseLines(isSymbolBaseLine: boolean) {
let fullBaseLine = generateBaseLine(fullResults, isSymbolBaseLine);
let pullBaseLine = generateBaseLine(pullResults, isSymbolBaseLine);
let fullExtension = isSymbolBaseLine ? '.symbols' : '.types';
let pullExtension = isSymbolBaseLine ? '.symbols.pull' : '.types.pull';
if (fullBaseLine !== pullBaseLine) {
Harness.Baseline.runBaseline('Correct full information for ' + fileName, justName.replace(/\.tsx?/, fullExtension), () => fullBaseLine);
Harness.Baseline.runBaseline('Correct pull information for ' + fileName, justName.replace(/\.tsx?/, pullExtension), () => pullBaseLine);
}
else {
Harness.Baseline.runBaseline('Correct information for ' + fileName, justName.replace(/\.tsx?/, fullExtension), () => fullBaseLine);
}
}
function generateBaseLine(typeWriterResults: ts.Map<TypeWriterResult[]>, isSymbolBaseline: boolean): string {
let typeLines: string[] = [];
let typeMap: { [fileName: string]: { [lineNum: number]: string[]; } } = {};
allFiles.forEach(file => {
var codeLines = file.content.split('\n');
typeWriterResults[file.unitName].forEach(result => {
if (isSymbolBaseline && !result.symbol) {
return;
}
var typeOrSymbolString = isSymbolBaseline ? result.symbol : result.type;
var formattedLine = result.sourceText.replace(/\r?\n/g, "") + " : " + typeOrSymbolString;
if (!typeMap[file.unitName]) {
typeMap[file.unitName] = {};
}
var typeInfo = [formattedLine];
var existingTypeInfo = typeMap[file.unitName][result.line];
if (existingTypeInfo) {
typeInfo = existingTypeInfo.concat(typeInfo);
}
typeMap[file.unitName][result.line] = typeInfo;
});
typeLines.push('=== ' + file.unitName + ' ===\r\n');
for (var i = 0; i < codeLines.length; i++) {
var currentCodeLine = codeLines[i];
typeLines.push(currentCodeLine + '\r\n');
if (typeMap[file.unitName]) {
var typeInfo = typeMap[file.unitName][i];
if (typeInfo) {
typeInfo.forEach(ty => {
typeLines.push('>' + ty + '\r\n');
});
if (i + 1 < codeLines.length && (codeLines[i + 1].match(/^\s*[{|}]\s*$/) || codeLines[i + 1].trim() === '')) {
}
else {
typeLines.push('\r\n');
}
}
}
else {
typeLines.push('No type information for this code.');
}
}
});
return typeLines.join('');
}
}
Harness.Compiler.doTypeAndSymbolBaseline(justName, result, toBeCompiled.concat(otherFiles).filter(file => !!result.program.getSourceFile(file.unitName)));
});
});
}
public initializeTests() {
describe("Setup compiler for compiler baselines", () => {
var harnessCompiler = Harness.Compiler.getCompiler();
this.parseOptions();
});
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
if (this.tests.length === 0) {
var testFiles = this.enumerateFiles(this.basePath, /\.tsx?$/, { recursive: true });
testFiles.forEach(fn => {
fn = fn.replace(/\\/g, "/");
this.checkTestCodeOutput(fn);
describe(this.testSuiteName + " tests", () => {
describe("Setup compiler for compiler baselines", () => {
this.parseOptions();
});
}
else {
this.tests.forEach(test => this.checkTestCodeOutput(test));
}
describe("Cleanup after compiler baselines", () => {
var harnessCompiler = Harness.Compiler.getCompiler();
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
if (this.tests.length === 0) {
const testFiles = this.enumerateTestFiles();
testFiles.forEach(fn => {
fn = fn.replace(/\\/g, "/");
this.checkTestCodeOutput(fn);
});
}
else {
this.tests.forEach(test => this.checkTestCodeOutput(test));
}
});
}
@@ -413,25 +210,25 @@ class CompilerBaselineRunner extends RunnerBase {
this.decl = false;
this.output = false;
var opts = this.options.split(',');
for (var i = 0; i < opts.length; i++) {
const opts = this.options.split(",");
for (let i = 0; i < opts.length; i++) {
switch (opts[i]) {
case 'error':
case "error":
this.errors = true;
break;
case 'emit':
case "emit":
this.emit = true;
break;
case 'decl':
case "decl":
this.decl = true;
break;
case 'output':
case "output":
this.output = true;
break;
default:
throw new Error('unsupported flag');
throw new Error("unsupported flag");
}
}
}
}
}
}
-175
View File
@@ -1,175 +0,0 @@
// Type definitions for chai 1.7.2
// Project: http://chaijs.com/
// Definitions by: Jed Hunsaker <https://github.com/jedhunsaker/>
// DefinitelyTyped: https://github.com/borisyankov/DefinitelyTyped
declare module chai {
function expect(target: any, message?: string): Expect;
// Provides a way to extend the internals of Chai
function use(fn: (chai: any, utils: any) => void): any;
interface ExpectStatic {
(target: any): Expect;
}
interface Assertions {
attr(name: string, value?: string): any;
css(name: string, value?: string): any;
data(name: string, value?: string): any;
class(className: string): any;
id(id: string): any;
html(html: string): any;
text(text: string): any;
value(value: string): any;
visible: any;
hidden: any;
selected: any;
checked: any;
disabled: any;
empty: any;
exist: any;
}
interface Expect extends LanguageChains, NumericComparison, TypeComparison, Assertions {
not: Expect;
deep: Deep;
a: TypeComparison;
an: TypeComparison;
include: Include;
contain: Include;
ok: Expect;
true: Expect;
false: Expect;
null: Expect;
undefined: Expect;
exist: Expect;
empty: Expect;
arguments: Expect;
Arguments: Expect;
equal: Equal;
equals: Equal;
eq: Equal;
eql: Equal;
eqls: Equal;
property: Property;
ownProperty: OwnProperty;
haveOwnProperty: OwnProperty;
length: Length;
lengthOf: Length;
match(RegularExpression: RegExp, message?: string): Expect;
string(string: string, message?: string): Expect;
keys: Keys;
key(string: string): Expect;
throw: Throw;
throws: Throw;
Throw: Throw;
respondTo(method: string, message?: string): Expect;
itself: Expect;
satisfy(matcher: Function, message?: string): Expect;
closeTo(expected: number, delta: number, message?: string): Expect;
members: Members;
}
interface LanguageChains {
to: Expect;
be: Expect;
been: Expect;
is: Expect;
that: Expect;
and: Expect;
have: Expect;
with: Expect;
at: Expect;
of: Expect;
same: Expect;
}
interface NumericComparison {
above: NumberComparer;
gt: NumberComparer;
greaterThan: NumberComparer;
least: NumberComparer;
gte: NumberComparer;
below: NumberComparer;
lt: NumberComparer;
lessThan: NumberComparer;
most: NumberComparer;
lte: NumberComparer;
within(start: number, finish: number, message?: string): Expect;
}
interface NumberComparer {
(value: number, message?: string): Expect;
}
interface TypeComparison {
(type: string, message?: string): Expect;
instanceof: InstanceOf;
instanceOf: InstanceOf;
}
interface InstanceOf {
(constructor: Object, message?: string): Expect;
}
interface Deep {
equal: Equal;
property: Property;
}
interface Equal {
(value: any, message?: string): Expect;
}
interface Property {
(name: string, value?: any, message?: string): Expect;
}
interface OwnProperty {
(name: string, message?: string): Expect;
}
interface Length extends LanguageChains, NumericComparison {
(length: number, message?: string): Expect;
}
interface Include {
(value: Object, message?: string): Expect;
(value: string, message?: string): Expect;
(value: number, message?: string): Expect;
keys: Keys;
members: Members;
}
interface Keys {
(...keys: string[]): Expect;
(keys: any[]): Expect;
}
interface Members {
(set: any[], message?: string): Expect;
}
interface Throw {
(): Expect;
(expected: string, message?: string): Expect;
(expected: RegExp, message?: string): Expect;
(constructor: Error, expected?: string, message?: string): Expect;
(constructor: Error, expected?: RegExp, message?: string): Expect;
(constructor: Function, expected?: string, message?: string): Expect;
(constructor: Function, expected?: RegExp, message?: string): Expect;
}
function assert(expression: any, message?: string): void;
module assert {
function equal(actual: any, expected: any, message?: string): void;
function notEqual(actual: any, expected: any, message?: string): void;
function isTrue(value: any, message?: string): void;
function isFalse(value: any, message?: string): void;
function isNull(value: any, message?: string): void;
function isNotNull(value: any, message?: string): void;
}
}
-45
View File
@@ -1,45 +0,0 @@
// Type definitions for mocha 1.9.0
// Project: http://visionmedia.github.io/mocha/
// Definitions by: Kazi Manzur Rashid <https://github.com/kazimanzurrashid/>
// DefinitelyTyped: https://github.com/borisyankov/DefinitelyTyped
declare var describe : {
(description: string, spec: () => void): void;
only(description: string, spec: () => void): void;
skip(description: string, spec: () => void): void;
timeout(ms: number): void;
}
declare var it: {
(expectation: string, assertion?: () => void): void;
(expectation: string, assertion?: (done: () => void) => void): void;
only(expectation: string, assertion?: () => void): void;
only(expectation: string, assertion?: (done: () => void) => void): void;
skip(expectation: string, assertion?: () => void): void;
skip(expectation: string, assertion?: (done: () => void) => void): void;
timeout(ms: number): void;
};
/** Runs once before any 'it' blocks in the current 'describe' are run */
declare function before(action: () => void): void;
/** Runs once before any 'it' blocks in the current 'describe' are run */
declare function before(action: (done: () => void) => void): void;
/** Runs once after all 'it' blocks in the current 'describe' are run */
declare function after(action: () => void): void;
/** Runs once after all 'it' blocks in the current 'describe' are run */
declare function after(action: (done: () => void) => void): void;
/** Runs before each individual 'it' block in the current 'describe' is run */
declare function beforeEach(action: () => void): void;
/** Runs before each individual 'it' block in the current 'describe' is run */
declare function beforeEach(action: (done: () => void) => void): void;
/** Runs after each individual 'it' block in the current 'describe' is run */
declare function afterEach(action: () => void): void;
/** Runs after each individual 'it' block in the current 'describe' is run */
declare function afterEach(action: (done: () => void) => void): void;
-1296
View File
File diff suppressed because it is too large Load Diff
+2678 -1269
View File
File diff suppressed because it is too large Load Diff
+41 -75
View File
@@ -1,111 +1,77 @@
///<reference path='fourslash.ts' />
///<reference path='harness.ts'/>
///<reference path='runnerbase.ts' />
///<reference path="fourslash.ts" />
///<reference path="harness.ts"/>
///<reference path="runnerbase.ts" />
const enum FourSlashTestType {
const enum FourSlashTestType {
Native,
Shims,
ShimsWithPreprocess,
Server
}
class FourSlashRunner extends RunnerBase {
protected basePath: string;
protected testSuiteName: string;
protected testSuiteName: TestRunnerKind;
constructor(private testType: FourSlashTestType) {
super();
switch (testType) {
case FourSlashTestType.Native:
this.basePath = 'tests/cases/fourslash';
this.testSuiteName = 'fourslash';
this.basePath = "tests/cases/fourslash";
this.testSuiteName = "fourslash";
break;
case FourSlashTestType.Shims:
this.basePath = 'tests/cases/fourslash/shims';
this.testSuiteName = 'fourslash-shims';
this.basePath = "tests/cases/fourslash/shims";
this.testSuiteName = "fourslash-shims";
break;
case FourSlashTestType.ShimsWithPreprocess:
this.basePath = "tests/cases/fourslash/shims-pp";
this.testSuiteName = "fourslash-shims-pp";
break;
case FourSlashTestType.Server:
this.basePath = 'tests/cases/fourslash/server';
this.testSuiteName = 'fourslash-server';
this.basePath = "tests/cases/fourslash/server";
this.testSuiteName = "fourslash-server";
break;
}
}
public enumerateTestFiles() {
return this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false });
}
public kind() {
return this.testSuiteName;
}
public initializeTests() {
if (this.tests.length === 0) {
this.tests = this.enumerateFiles(this.basePath, /\.ts/i, { recursive: false });
this.tests = this.enumerateTestFiles();
}
this.tests.forEach((fn: string) => {
describe(fn, () => {
fn = ts.normalizeSlashes(fn);
var justName = fn.replace(/^.*[\\\/]/, '');
describe(this.testSuiteName + " tests", () => {
this.tests.forEach((fn: string) => {
describe(fn, () => {
fn = ts.normalizeSlashes(fn);
const justName = fn.replace(/^.*[\\\/]/, "");
// Convert to relative path
var testIndex = fn.indexOf('tests/');
if (testIndex >= 0) fn = fn.substr(testIndex);
// Convert to relative path
const testIndex = fn.indexOf("tests/");
if (testIndex >= 0) fn = fn.substr(testIndex);
if (justName && !justName.match(/fourslash\.ts$/i) && !justName.match(/\.d\.ts$/i)) {
it(this.testSuiteName + ' test ' + justName + ' runs correctly', () => {
FourSlash.runFourSlashTest(this.basePath, this.testType, fn);
});
}
if (justName && !justName.match(/fourslash\.ts$/i) && !justName.match(/\.d\.ts$/i)) {
it(this.testSuiteName + " test " + justName + " runs correctly", () => {
FourSlash.runFourSlashTest(this.basePath, this.testType, fn);
});
}
});
});
});
describe('Generate Tao XML', () => {
var invalidReasons: any = {};
FourSlash.xmlData.forEach(xml => {
if (xml.invalidReason !== null) {
invalidReasons[xml.invalidReason] = (invalidReasons[xml.invalidReason] || 0) + 1;
}
});
var invalidReport: { reason: string; count: number }[] = [];
for (var reason in invalidReasons) {
if (invalidReasons.hasOwnProperty(reason)) {
invalidReport.push({ reason: reason, count: invalidReasons[reason] });
}
}
invalidReport.sort((lhs, rhs) => lhs.count > rhs.count ? -1 : lhs.count === rhs.count ? 0 : 1);
var lines: string[] = [];
lines.push('<!-- Blocked Test Report');
invalidReport.forEach((reasonAndCount) => {
lines.push(reasonAndCount.count + ' tests blocked by ' + reasonAndCount.reason);
});
lines.push('-->');
lines.push('<TaoTest xmlns="http://microsoft.com/schemas/VSLanguages/TAO">');
lines.push(' <InitTest>');
lines.push(' <StartTarget />');
lines.push(' </InitTest>');
lines.push(' <ScenarioList>');
FourSlash.xmlData.forEach(xml => {
if (xml.invalidReason !== null) {
lines.push('<!-- Skipped ' + xml.originalName + ', reason: ' + xml.invalidReason + ' -->');
} else {
lines.push(' <Scenario Name="' + xml.originalName + '">');
xml.actions.forEach(action => {
lines.push(' ' + action);
});
lines.push(' </Scenario>');
}
});
lines.push(' </ScenarioList>');
lines.push(' <CleanupScenario>');
lines.push(' <CloseAllDocuments />');
lines.push(' <CleanupCreatedFiles />');
lines.push(' </CleanupScenario>');
lines.push(' <CleanupTest>');
lines.push(' <CloseTarget />');
lines.push(' </CleanupTest>');
lines.push('</TaoTest>');
Harness.IO.writeFile('built/local/fourslash.xml', lines.join('\r\n'));
});
}
}
class GeneratedFourslashRunner extends FourSlashRunner {
constructor(testType: FourSlashTestType) {
super(testType);
this.basePath += '/generated/';
this.basePath += "/generated/";
}
}
}
+1140 -923
View File
File diff suppressed because it is too large Load Diff
+369 -122
View File
@@ -1,21 +1,25 @@
/// <reference path='..\services\services.ts' />
/// <reference path='..\services\shims.ts' />
/// <reference path='..\server\client.ts' />
/// <reference path='harness.ts' />
/// <reference path="..\services\services.ts" />
/// <reference path="..\services\shims.ts" />
/// <reference path="..\server\client.ts" />
/// <reference path="harness.ts" />
module Harness.LanguageService {
namespace Harness.LanguageService {
export class ScriptInfo {
public version: number = 1;
public version = 1;
public editRanges: { length: number; textChangeRange: ts.TextChangeRange; }[] = [];
public lineMap: number[] = null;
private lineMap: number[] = undefined;
constructor(public fileName: string, public content: string) {
constructor(public fileName: string, public content: string, public isRootFile: boolean) {
this.setContent(content);
}
private setContent(content: string): void {
this.content = content;
this.lineMap = ts.computeLineStarts(content);
this.lineMap = undefined;
}
public getLineMap(): number[] {
return this.lineMap || (this.lineMap = ts.computeLineStarts(this.content));
}
public updateContent(content: string): void {
@@ -26,9 +30,9 @@ module Harness.LanguageService {
public editContent(start: number, end: number, newText: string): void {
// Apply edits
var prefix = this.content.substring(0, start);
var middle = newText;
var suffix = this.content.substring(end);
const prefix = this.content.substring(0, start);
const middle = newText;
const suffix = this.content.substring(end);
this.setContent(prefix + middle + suffix);
// Store edit range + new length of script
@@ -48,10 +52,10 @@ module Harness.LanguageService {
return ts.unchangedTextChangeRange;
}
var initialEditRangeIndex = this.editRanges.length - (this.version - startVersion);
var lastEditRangeIndex = this.editRanges.length - (this.version - endVersion);
const initialEditRangeIndex = this.editRanges.length - (this.version - startVersion);
const lastEditRangeIndex = this.editRanges.length - (this.version - endVersion);
var entries = this.editRanges.slice(initialEditRangeIndex, lastEditRangeIndex);
const entries = this.editRanges.slice(initialEditRangeIndex, lastEditRangeIndex);
return ts.collapseTextChangeRangesAcrossMultipleVersions(entries.map(e => e.textChangeRange));
}
}
@@ -74,7 +78,7 @@ module Harness.LanguageService {
}
public getChangeRange(oldScript: ts.IScriptSnapshot): ts.TextChangeRange {
var oldShim = <ScriptSnapshot>oldScript;
const oldShim = <ScriptSnapshot>oldScript;
return this.scriptInfo.getTextChangeRangeBetweenVersions(oldShim.version, this.version);
}
}
@@ -92,11 +96,11 @@ module Harness.LanguageService {
}
public getChangeRange(oldScript: ts.ScriptSnapshotShim): string {
var oldShim = <ScriptSnapshotProxy>oldScript;
const oldShim = <ScriptSnapshotProxy>oldScript;
var range = this.scriptSnapshot.getChangeRange(oldShim.scriptSnapshot);
if (range === null) {
return null;
const range = this.scriptSnapshot.getChangeRange(oldShim.scriptSnapshot);
if (range === undefined) {
return undefined;
}
return JSON.stringify({ span: { start: range.span.start, length: range.span.length }, newLength: range.newLength });
@@ -118,11 +122,11 @@ module Harness.LanguageService {
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo;
}
export class LanguageServiceAdapterHost {
protected fileNameToScript: ts.Map<ScriptInfo> = {};
export class LanguageServiceAdapterHost {
protected virtualFileSystem: Utils.VirtualFileSystem = new Utils.VirtualFileSystem(virtualFileSystemRoot, /*useCaseSensitiveFilenames*/false);
constructor(protected cancellationToken = DefaultHostCancellationToken.Instance,
protected settings = ts.getDefaultCompilerOptions()) {
protected settings = ts.getDefaultCompilerOptions()) {
}
public getNewLine(): string {
@@ -130,22 +134,30 @@ module Harness.LanguageService {
}
public getFilenames(): string[] {
var fileNames: string[] = [];
ts.forEachKey(this.fileNameToScript,(fileName) => { fileNames.push(fileName); });
const fileNames: string[] = [];
for (const virtualEntry of this.virtualFileSystem.getAllFileEntries()) {
const scriptInfo = virtualEntry.content;
if (scriptInfo.isRootFile) {
// only include root files here
// usually it means that we won't include lib.d.ts in the list of root files so it won't mess the computation of compilation root dir.
fileNames.push(scriptInfo.fileName);
}
}
return fileNames;
}
public getScriptInfo(fileName: string): ScriptInfo {
return ts.lookUp(this.fileNameToScript, fileName);
const fileEntry = this.virtualFileSystem.traversePath(fileName);
return fileEntry && fileEntry.isFile() ? (<Utils.VirtualFile>fileEntry).content : undefined;
}
public addScript(fileName: string, content: string): void {
this.fileNameToScript[fileName] = new ScriptInfo(fileName, content);
public addScript(fileName: string, content: string, isRootFile: boolean): void {
this.virtualFileSystem.addFile(fileName, new ScriptInfo(fileName, content, isRootFile));
}
public editScript(fileName: string, start: number, end: number, newText: string) {
var script = this.getScriptInfo(fileName);
if (script !== null) {
const script = this.getScriptInfo(fileName);
if (script !== undefined) {
script.editContent(start, end, newText);
return;
}
@@ -153,102 +165,186 @@ module Harness.LanguageService {
throw new Error("No script with name '" + fileName + "'");
}
public openFile(fileName: string): void {
public openFile(_fileName: string, _content?: string, _scriptKindName?: string): void {
}
/**
* @param line 0 based index
* @param col 0 based index
*/
* @param line 0 based index
* @param col 0 based index
*/
public positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter {
var script: ScriptInfo = this.fileNameToScript[fileName];
assert.isNotNull(script);
const script: ScriptInfo = this.getScriptInfo(fileName);
assert.isOk(script);
return ts.computeLineAndCharacterOfPosition(script.lineMap, position);
return ts.computeLineAndCharacterOfPosition(script.getLineMap(), position);
}
}
/// Native adapter
class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost {
class NativeLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceHost {
getCompilationSettings() { return this.settings; }
getCancellationToken() { return this.cancellationToken; }
getCurrentDirectory(): string { return ""; }
getDefaultLibFileName(): string { return ""; }
getDirectories(path: string): string[] {
const dir = this.virtualFileSystem.traversePath(path);
if (dir && dir.isDirectory()) {
return ts.map((<Utils.VirtualDirectory>dir).getDirectories(), (d) => ts.combinePaths(path, d.name));
}
return [];
}
getCurrentDirectory(): string { return virtualFileSystemRoot; }
getDefaultLibFileName(): string { return Harness.Compiler.defaultLibFileName; }
getScriptFileNames(): string[] { return this.getFilenames(); }
getScriptSnapshot(fileName: string): ts.IScriptSnapshot {
var script = this.getScriptInfo(fileName);
const script = this.getScriptInfo(fileName);
return script ? new ScriptSnapshot(script) : undefined;
}
getScriptKind(): ts.ScriptKind { return ts.ScriptKind.Unknown; }
getScriptVersion(fileName: string): string {
var script = this.getScriptInfo(fileName);
const script = this.getScriptInfo(fileName);
return script ? script.version.toString() : undefined;
}
log(s: string): void { }
trace(s: string): void { }
error(s: string): void { }
fileExists(fileName: string): boolean {
const script = this.getScriptSnapshot(fileName);
return script !== undefined;
}
readDirectory(path: string, extensions?: string[], exclude?: string[], include?: string[]): string[] {
return ts.matchFiles(path, extensions, exclude, include,
/*useCaseSensitiveFileNames*/ false,
this.getCurrentDirectory(),
(p) => this.virtualFileSystem.getAccessibleFileSystemEntries(p));
}
readFile(path: string): string {
const snapshot = this.getScriptSnapshot(path);
return snapshot.getText(0, snapshot.getLength());
}
getTypeRootsVersion() {
return 0;
}
log(_: string): void { }
trace(_: string): void { }
error(_: string): void { }
}
export class NativeLanugageServiceAdapter implements LanguageServiceAdapter {
export class NativeLanguageServiceAdapter implements LanguageServiceAdapter {
private host: NativeLanguageServiceHost;
constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
this.host = new NativeLanguageServiceHost(cancellationToken, options);
}
getHost() { return this.host; }
getLanguageService(): ts.LanguageService { return ts.createLanguageService(this.host); }
getClassifier(): ts.Classifier { return ts.createClassifier(); }
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents); }
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { return ts.preProcessFile(fileContents, /* readImportFiles */ true, ts.hasJavaScriptFileExtension(fileName)); }
}
/// Shim adapter
class ShimLanguageServiceHost extends LanguageServiceAdapterHost implements ts.LanguageServiceShimHost, ts.CoreServicesShimHost {
private nativeHost: NativeLanguageServiceHost;
constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
public getModuleResolutionsForFile: (fileName: string) => string;
public getTypeReferenceDirectiveResolutionsForFile: (fileName: string) => string;
constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
super(cancellationToken, options);
this.nativeHost = new NativeLanguageServiceHost(cancellationToken, options);
if (preprocessToResolve) {
const compilerOptions = this.nativeHost.getCompilationSettings();
const moduleResolutionHost: ts.ModuleResolutionHost = {
fileExists: fileName => this.getScriptInfo(fileName) !== undefined,
readFile: fileName => {
const scriptInfo = this.getScriptInfo(fileName);
return scriptInfo && scriptInfo.content;
}
};
this.getModuleResolutionsForFile = (fileName) => {
const scriptInfo = this.getScriptInfo(fileName);
const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ true);
const imports: ts.MapLike<string> = {};
for (const module of preprocessInfo.importedFiles) {
const resolutionInfo = ts.resolveModuleName(module.fileName, fileName, compilerOptions, moduleResolutionHost);
if (resolutionInfo.resolvedModule) {
imports[module.fileName] = resolutionInfo.resolvedModule.resolvedFileName;
}
}
return JSON.stringify(imports);
};
this.getTypeReferenceDirectiveResolutionsForFile = (fileName) => {
const scriptInfo = this.getScriptInfo(fileName);
if (scriptInfo) {
const preprocessInfo = ts.preProcessFile(scriptInfo.content, /*readImportFiles*/ false);
const resolutions: ts.MapLike<ts.ResolvedTypeReferenceDirective> = {};
const settings = this.nativeHost.getCompilationSettings();
for (const typeReferenceDirective of preprocessInfo.typeReferenceDirectives) {
const resolutionInfo = ts.resolveTypeReferenceDirective(typeReferenceDirective.fileName, fileName, settings, moduleResolutionHost);
if (resolutionInfo.resolvedTypeReferenceDirective.resolvedFileName) {
resolutions[typeReferenceDirective.fileName] = resolutionInfo.resolvedTypeReferenceDirective;
}
}
return JSON.stringify(resolutions);
}
else {
return "[]";
}
};
}
}
getFilenames(): string[] { return this.nativeHost.getFilenames(); }
getScriptInfo(fileName: string): ScriptInfo { return this.nativeHost.getScriptInfo(fileName); }
addScript(fileName: string, content: string): void { this.nativeHost.addScript(fileName, content); }
addScript(fileName: string, content: string, isRootFile: boolean): void { this.nativeHost.addScript(fileName, content, isRootFile); }
editScript(fileName: string, start: number, end: number, newText: string): void { this.nativeHost.editScript(fileName, start, end, newText); }
positionToLineAndCharacter(fileName: string, position: number): ts.LineAndCharacter { return this.nativeHost.positionToLineAndCharacter(fileName, position); }
getCompilationSettings(): string { return JSON.stringify(this.nativeHost.getCompilationSettings()); }
getCancellationToken(): ts.HostCancellationToken { return this.nativeHost.getCancellationToken(); }
getCurrentDirectory(): string { return this.nativeHost.getCurrentDirectory(); }
getDirectories(path: string): string { return JSON.stringify(this.nativeHost.getDirectories(path)); }
getDefaultLibFileName(): string { return this.nativeHost.getDefaultLibFileName(); }
getScriptFileNames(): string { return JSON.stringify(this.nativeHost.getScriptFileNames()); }
getScriptSnapshot(fileName: string): ts.ScriptSnapshotShim {
var nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(fileName);
return nativeScriptSnapshot && new ScriptSnapshotProxy(nativeScriptSnapshot);
const nativeScriptSnapshot = this.nativeHost.getScriptSnapshot(fileName);
return nativeScriptSnapshot && new ScriptSnapshotProxy(nativeScriptSnapshot);
}
getScriptKind(): ts.ScriptKind { return this.nativeHost.getScriptKind(); }
getScriptVersion(fileName: string): string { return this.nativeHost.getScriptVersion(fileName); }
getLocalizedDiagnosticMessages(): string { return JSON.stringify({}); }
readDirectory(rootDir: string, extension: string): string {
throw new Error("NYI");
readDirectory(_rootDir: string, _extension: string): string {
return ts.notImplemented();
}
readDirectoryNames = ts.notImplemented;
readFileNames = ts.notImplemented;
fileExists(fileName: string) { return this.getScriptInfo(fileName) !== undefined; }
readFile(fileName: string) {
const snapshot = this.nativeHost.getScriptSnapshot(fileName);
return snapshot && snapshot.getText(0, snapshot.getLength());
}
log(s: string): void { this.nativeHost.log(s); }
trace(s: string): void { this.nativeHost.trace(s); }
error(s: string): void { this.nativeHost.error(s); }
directoryExists(): boolean {
// for tests pessimistically assume that directory always exists
return true;
}
}
class ClassifierShimProxy implements ts.Classifier {
class ClassifierShimProxy implements ts.Classifier {
constructor(private shim: ts.ClassifierShim) {
}
getEncodedLexicalClassifications(text: string, lexState: ts.EndOfLineState, classifyKeywordsInGenerics?: boolean): ts.Classifications {
throw new Error("NYI");
getEncodedLexicalClassifications(_text: string, _lexState: ts.EndOfLineState, _classifyKeywordsInGenerics?: boolean): ts.Classifications {
return ts.notImplemented();
}
getClassificationsForLine(text: string, lexState: ts.EndOfLineState, classifyKeywordsInGenerics?: boolean): ts.ClassificationResult {
var result = this.shim.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics).split('\n');
var entries: ts.ClassificationInfo[] = [];
var i = 0;
var position = 0;
const result = this.shim.getClassificationsForLine(text, lexState, classifyKeywordsInGenerics).split("\n");
const entries: ts.ClassificationInfo[] = [];
let i = 0;
let position = 0;
for (; i < result.length - 1; i += 2) {
var t = entries[i / 2] = {
const t = entries[i / 2] = {
length: parseInt(result[i]),
classification: parseInt(result[i + 1])
};
@@ -256,7 +352,7 @@ module Harness.LanguageService {
assert.isTrue(t.length > 0, "Result length should be greater than 0, got :" + t.length);
position += t.length;
}
var finalLexState = parseInt(result[result.length - 1]);
const finalLexState = parseInt(result[result.length - 1]);
assert.equal(position, text.length, "Expected cumulative length of all entries to match the length of the source. expected: " + text.length + ", but got: " + position);
@@ -268,11 +364,11 @@ module Harness.LanguageService {
}
function unwrapJSONCallResult(result: string): any {
var parsedResult = JSON.parse(result);
const parsedResult = JSON.parse(result);
if (parsedResult.error) {
throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error));
}
else if (parsedResult.canceled) {
else if (parsedResult.canceled) {
throw new ts.OperationCanceledException();
}
return parsedResult.result;
@@ -281,13 +377,6 @@ module Harness.LanguageService {
class LanguageServiceShimProxy implements ts.LanguageService {
constructor(private shim: ts.LanguageServiceShim) {
}
private unwrappJSONCallResult(result: string): any {
var parsedResult = JSON.parse(result);
if (parsedResult.error) {
throw new Error("Language Service Shim Error: " + JSON.stringify(parsedResult.error));
}
return parsedResult.result;
}
cleanupSemanticCache(): void {
this.shim.cleanupSemanticCache();
}
@@ -318,6 +407,9 @@ module Harness.LanguageService {
getCompletionEntryDetails(fileName: string, position: number, entryName: string): ts.CompletionEntryDetails {
return unwrapJSONCallResult(this.shim.getCompletionEntryDetails(fileName, position, entryName));
}
getCompletionEntrySymbol(): ts.Symbol {
throw new Error("getCompletionEntrySymbol not implemented across the shim layer.");
}
getQuickInfoAtPosition(fileName: string, position: number): ts.QuickInfo {
return unwrapJSONCallResult(this.shim.getQuickInfoAtPosition(fileName, position));
}
@@ -339,9 +431,12 @@ module Harness.LanguageService {
getDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] {
return unwrapJSONCallResult(this.shim.getDefinitionAtPosition(fileName, position));
}
getTypeDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[]{
getTypeDefinitionAtPosition(fileName: string, position: number): ts.DefinitionInfo[] {
return unwrapJSONCallResult(this.shim.getTypeDefinitionAtPosition(fileName, position));
}
getImplementationAtPosition(fileName: string, position: number): ts.ImplementationLocation[] {
return unwrapJSONCallResult(this.shim.getImplementationAtPosition(fileName, position));
}
getReferencesAtPosition(fileName: string, position: number): ts.ReferenceEntry[] {
return unwrapJSONCallResult(this.shim.getReferencesAtPosition(fileName, position));
}
@@ -360,6 +455,10 @@ module Harness.LanguageService {
getNavigationBarItems(fileName: string): ts.NavigationBarItem[] {
return unwrapJSONCallResult(this.shim.getNavigationBarItems(fileName));
}
getNavigationTree(fileName: string): ts.NavigationTree {
return unwrapJSONCallResult(this.shim.getNavigationTree(fileName));
}
getOutliningSpans(fileName: string): ts.OutliningSpan[] {
return unwrapJSONCallResult(this.shim.getOutliningSpans(fileName));
}
@@ -381,42 +480,57 @@ module Harness.LanguageService {
getFormattingEditsAfterKeystroke(fileName: string, position: number, key: string, options: ts.FormatCodeOptions): ts.TextChange[] {
return unwrapJSONCallResult(this.shim.getFormattingEditsAfterKeystroke(fileName, position, key, JSON.stringify(options)));
}
getDocCommentTemplateAtPosition(fileName: string, position: number): ts.TextInsertion {
return unwrapJSONCallResult(this.shim.getDocCommentTemplateAtPosition(fileName, position));
}
isValidBraceCompletionAtPosition(fileName: string, position: number, openingBrace: number): boolean {
return unwrapJSONCallResult(this.shim.isValidBraceCompletionAtPosition(fileName, position, openingBrace));
}
getCodeFixesAtPosition(): ts.CodeAction[] {
throw new Error("Not supported on the shim.");
}
getEmitOutput(fileName: string): ts.EmitOutput {
return unwrapJSONCallResult(this.shim.getEmitOutput(fileName));
}
getProgram(): ts.Program {
throw new Error("Program can not be marshaled across the shim layer.");
}
getSourceFile(fileName: string): ts.SourceFile {
getNonBoundSourceFile(): ts.SourceFile {
throw new Error("SourceFile can not be marshaled across the shim layer.");
}
getSourceFile(): ts.SourceFile {
throw new Error("SourceFile can not be marshaled across the shim layer.");
}
dispose(): void { this.shim.dispose({}); }
}
export class ShimLanugageServiceAdapter implements LanguageServiceAdapter {
export class ShimLanguageServiceAdapter implements LanguageServiceAdapter {
private host: ShimLanguageServiceHost;
private factory: ts.TypeScriptServicesFactory;
constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
this.host = new ShimLanguageServiceHost(cancellationToken, options);
constructor(preprocessToResolve: boolean, cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
this.host = new ShimLanguageServiceHost(preprocessToResolve, cancellationToken, options);
this.factory = new TypeScript.Services.TypeScriptServicesFactory();
}
getHost() { return this.host; }
getLanguageService(): ts.LanguageService { return new LanguageServiceShimProxy(this.factory.createLanguageServiceShim(this.host)); }
getClassifier(): ts.Classifier { return new ClassifierShimProxy(this.factory.createClassifierShim(this.host)); }
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo {
var shimResult: {
let shimResult: {
referencedFiles: ts.IFileReference[];
typeReferenceDirectives: ts.IFileReference[];
importedFiles: ts.IFileReference[];
isLibFile: boolean;
};
var coreServicesShim = this.factory.createCoreServicesShim(this.host);
const coreServicesShim = this.factory.createCoreServicesShim(this.host);
shimResult = unwrapJSONCallResult(coreServicesShim.getPreProcessedFileInfo(fileName, ts.ScriptSnapshot.fromString(fileContents)));
var convertResult: ts.PreProcessedFileInfo = {
const convertResult: ts.PreProcessedFileInfo = {
referencedFiles: [],
importedFiles: [],
isLibFile: shimResult.isLibFile
ambientExternalModules: [],
isLibFile: shimResult.isLibFile,
typeReferenceDirectives: []
};
ts.forEach(shimResult.referencedFiles, refFile => {
@@ -435,33 +549,40 @@ module Harness.LanguageService {
});
});
ts.forEach(shimResult.typeReferenceDirectives, typeRefDirective => {
convertResult.importedFiles.push({
fileName: typeRefDirective.path,
pos: typeRefDirective.position,
end: typeRefDirective.position + typeRefDirective.length
});
});
return convertResult;
}
}
// Server adapter
class SessionClientHost extends NativeLanguageServiceHost implements ts.server.SessionClientHost {
class SessionClientHost extends NativeLanguageServiceHost implements ts.server.SessionClientHost {
private client: ts.server.SessionClient;
constructor(cancellationToken: ts.HostCancellationToken, settings: ts.CompilerOptions) {
super(cancellationToken, settings);
}
onMessage(message: string): void {
onMessage(): void {
}
writeMessage(message: string): void {
writeMessage(): void {
}
setClient(client: ts.server.SessionClient) {
this.client = client;
}
openFile(fileName: string): void {
super.openFile(fileName);
this.client.openFile(fileName);
openFile(fileName: string, content?: string, scriptKindName?: "TS" | "JS" | "TSX" | "JSX"): void {
super.openFile(fileName, content, scriptKindName);
this.client.openFile(fileName, content, scriptKindName);
}
editScript(fileName: string, start: number, end: number, newText: string) {
@@ -470,37 +591,36 @@ module Harness.LanguageService {
}
}
class SessionServerHost implements ts.server.ServerHost, ts.server.Logger {
class SessionServerHost implements ts.server.ServerHost, ts.server.Logger {
args: string[] = [];
newLine: string;
useCaseSensitiveFileNames: boolean = false;
useCaseSensitiveFileNames = false;
constructor(private host: NativeLanguageServiceHost) {
this.newLine = this.host.getNewLine();
}
onMessage(message: string): void {
onMessage(): void {
}
writeMessage(message: string): void {
writeMessage(_message: string): void {
}
write(message: string): void {
write(message: string): void {
this.writeMessage(message);
}
readFile(fileName: string): string {
if (fileName.indexOf(Harness.Compiler.defaultLibFileName) >= 0) {
if (fileName.indexOf(Harness.Compiler.defaultLibFileName) >= 0) {
fileName = Harness.Compiler.defaultLibFileName;
}
var snapshot = this.host.getScriptSnapshot(fileName);
const snapshot = this.host.getScriptSnapshot(fileName);
return snapshot && snapshot.getText(0, snapshot.getLength());
}
writeFile(name: string, text: string, writeByteOrderMark: boolean): void {
writeFile(): void {
}
resolvePath(path: string): string {
@@ -511,31 +631,44 @@ module Harness.LanguageService {
return !!this.host.getScriptSnapshot(path);
}
directoryExists(path: string): boolean {
return false;
directoryExists(): boolean {
// for tests assume that directory exists
return true;
}
getExecutingFilePath(): string {
return "";
}
exit(exitCode: number): void {
exit(): void {
}
createDirectory(directoryName: string): void {
throw new Error("Not Implemented Yet.");
createDirectory(_directoryName: string): void {
return ts.notImplemented();
}
getCurrentDirectory(): string {
return this.host.getCurrentDirectory();
}
readDirectory(path: string, extension?: string): string[] {
throw new Error("Not implemented Yet.");
getDirectories(): string[] {
return [];
}
watchFile(fileName: string, callback: (fileName: string) => void): ts.FileWatcher {
return { close() { } };
getEnvironmentVariable(name: string): string {
return ts.sys.getEnvironmentVariable(name);
}
readDirectory(_path: string, _extension?: string[], _exclude?: string[], _include?: string[]): string[] {
return ts.notImplemented();
}
watchFile(): ts.FileWatcher {
return { close: ts.noop };
}
watchDirectory(): ts.FileWatcher {
return { close: ts.noop };
}
close(): void {
@@ -548,12 +681,16 @@ module Harness.LanguageService {
msg(message: string) {
return this.host.log(message);
}
loggingEnabled() {
return true;
}
isVerbose() {
getLogFileName(): string {
return undefined;
}
hasLevel() {
return false;
}
@@ -567,20 +704,131 @@ module Harness.LanguageService {
startGroup(): void {
}
setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): any {
return setTimeout(callback, ms, args);
}
clearTimeout(timeoutId: any): void {
clearTimeout(timeoutId);
}
setImmediate(callback: (...args: any[]) => void, _ms: number, ...args: any[]): any {
return setImmediate(callback, args);
}
clearImmediate(timeoutId: any): void {
clearImmediate(timeoutId);
}
createHash(s: string) {
return s;
}
require(_initialDir: string, _moduleName: string): ts.server.RequireResult {
switch (_moduleName) {
// Adds to the Quick Info a fixed string and a string from the config file
// and replaces the first display part
case "quickinfo-augmeneter":
return {
module: () => ({
create(info: ts.server.PluginCreateInfo) {
const proxy = makeDefaultProxy(info);
const langSvc: any = info.languageService;
proxy.getQuickInfoAtPosition = function () {
const parts = langSvc.getQuickInfoAtPosition.apply(langSvc, arguments);
if (parts.displayParts.length > 0) {
parts.displayParts[0].text = "Proxied";
}
parts.displayParts.push({ text: info.config.message, kind: "punctuation" });
return parts;
};
return proxy;
}
}),
error: undefined
};
// Throws during initialization
case "create-thrower":
return {
module: () => ({
create() {
throw new Error("I am not a well-behaved plugin");
}
}),
error: undefined
};
// Adds another diagnostic
case "diagnostic-adder":
return {
module: () => ({
create(info: ts.server.PluginCreateInfo) {
const proxy = makeDefaultProxy(info);
proxy.getSemanticDiagnostics = function (filename: string) {
const prev = info.languageService.getSemanticDiagnostics(filename);
const sourceFile: ts.SourceFile = info.languageService.getSourceFile(filename);
prev.push({
category: ts.DiagnosticCategory.Warning,
file: sourceFile,
code: 9999,
length: 3,
messageText: `Plugin diagnostic`,
start: 0
});
return prev;
};
return proxy;
}
}),
error: undefined
};
default:
return {
module: undefined,
error: "Could not resolve module"
};
}
function makeDefaultProxy(info: ts.server.PluginCreateInfo) {
// tslint:disable-next-line:no-null-keyword
const proxy = Object.create(/*prototype*/ null);
const langSvc: any = info.languageService;
for (const k of Object.keys(langSvc)) {
proxy[k] = function () {
return langSvc[k].apply(langSvc, arguments);
};
}
return proxy;
}
}
}
export class ServerLanugageServiceAdapter implements LanguageServiceAdapter {
export class ServerLanguageServiceAdapter implements LanguageServiceAdapter {
private host: SessionClientHost;
private client: ts.server.SessionClient;
constructor(cancellationToken?: ts.HostCancellationToken, options?: ts.CompilerOptions) {
// This is the main host that tests use to direct tests
var clientHost = new SessionClientHost(cancellationToken, options);
var client = new ts.server.SessionClient(clientHost);
const clientHost = new SessionClientHost(cancellationToken, options);
const client = new ts.server.SessionClient(clientHost);
// This host is just a proxy for the clientHost, it uses the client
// host to answer server queries about files on disk
var serverHost = new SessionServerHost(clientHost);
var server = new ts.server.Session(serverHost, Buffer.byteLength, process.hrtime, serverHost);
const serverHost = new SessionServerHost(clientHost);
const opts: ts.server.SessionOptions = {
host: serverHost,
cancellationToken: ts.server.nullCancellationToken,
useSingleInferredProject: false,
typingsInstaller: undefined,
byteLength: Utils.byteLength,
hrtime: process.hrtime,
logger: serverHost,
canUseEvents: true
};
const server = new ts.server.Session(opts);
// Fake the connection between the client and the server
serverHost.writeMessage = client.onMessage.bind(client);
@@ -597,7 +845,6 @@ module Harness.LanguageService {
getHost() { return this.host; }
getLanguageService(): ts.LanguageService { return this.client; }
getClassifier(): ts.Classifier { throw new Error("getClassifier is not available using the server interface."); }
getPreProcessedFileInfo(fileName: string, fileContents: string): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); }
getPreProcessedFileInfo(): ts.PreProcessedFileInfo { throw new Error("getPreProcessedFileInfo is not available using the server interface."); }
}
}
+26 -21
View File
@@ -1,40 +1,44 @@
declare var require: any, process: any;
var fs: any = require('fs');
var path: any = require('path');
declare const require: any, process: any;
const fs: any = require("fs");
const path: any = require("path");
function instrumentForRecording(fn: string, tscPath: string) {
instrument(tscPath, 'ts.sys = Playback.wrapSystem(ts.sys); ts.sys.startRecord("' + fn + '");', 'ts.sys.endRecord();');
instrument(tscPath, `
ts.sys = Playback.wrapSystem(ts.sys);
ts.sys.startRecord("${ fn }");`, `ts.sys.endRecord();`);
}
function instrumentForReplay(logFilename: string, tscPath: string) {
instrument(tscPath, 'ts.sys = Playback.wrapSystem(ts.sys); ts.sys.startReplay("' + logFilename + '");');
instrument(tscPath, `
ts.sys = Playback.wrapSystem(ts.sys);
ts.sys.startReplay("${ logFilename }");`);
}
function instrument(tscPath: string, prepareCode: string, cleanupCode: string = '') {
var bak = tscPath + '.bak';
function instrument(tscPath: string, prepareCode: string, cleanupCode = "") {
const bak = `${tscPath}.bak`;
fs.exists(bak, (backupExists: boolean) => {
var filename = tscPath;
let filename = tscPath;
if (backupExists) {
filename = bak;
}
fs.readFile(filename, 'utf-8', (err: any, tscContent: string) => {
fs.readFile(filename, "utf-8", (err: any, tscContent: string) => {
if (err) throw err;
fs.writeFile(bak, tscContent, (err: any) => {
if (err) throw err;
fs.readFile(path.resolve(path.dirname(tscPath) + '/loggedIO.js'), 'utf-8', (err: any, loggerContent: string) => {
fs.readFile(path.resolve(path.dirname(tscPath) + "/loggedIO.js"), "utf-8", (err: any, loggerContent: string) => {
if (err) throw err;
var invocationLine = 'ts.executeCommandLine(ts.sys.args);';
var index1 = tscContent.indexOf(invocationLine);
const invocationLine = "ts.executeCommandLine(ts.sys.args);";
const index1 = tscContent.indexOf(invocationLine);
if (index1 < 0) {
throw new Error("Could not find " + invocationLine);
throw new Error(`Could not find ${invocationLine}`);
}
var index2 = index1 + invocationLine.length;
var newContent = tscContent.substr(0, index1) + loggerContent + prepareCode + invocationLine + cleanupCode + tscContent.substr(index2) + '\r\n';
const index2 = index1 + invocationLine.length;
const newContent = tscContent.substr(0, index1) + loggerContent + prepareCode + invocationLine + cleanupCode + tscContent.substr(index2) + "\r\n";
fs.writeFile(tscPath, newContent);
});
});
@@ -42,15 +46,16 @@ function instrument(tscPath: string, prepareCode: string, cleanupCode: string =
});
}
var isJson = (arg: string) => arg.indexOf(".json") > 0;
const isJson = (arg: string) => arg.indexOf(".json") > 0;
var record = process.argv.indexOf('record');
var tscPath = process.argv[process.argv.length - 1];
const record = process.argv.indexOf("record");
const tscPath = process.argv[process.argv.length - 1];
if (record >= 0) {
console.log('Instrumenting ' + tscPath + ' for recording');
console.log(`Instrumenting ${tscPath} for recording`);
instrumentForRecording(process.argv[record + 1], tscPath);
} else if (process.argv.some(isJson)) {
var filename = process.argv.filter(isJson)[0];
}
else if (process.argv.some(isJson)) {
const filename = process.argv.filter(isJson)[0];
instrumentForReplay(filename, tscPath);
}
+174 -143
View File
@@ -1,6 +1,8 @@
/// <reference path="..\..\src\compiler\sys.ts" />
/// <reference path="..\..\src\harness\harness.ts" />
/// <reference path="..\..\src\harness\harnessLanguageService.ts" />
/// <reference path="..\..\src\harness\runnerbase.ts" />
/// <reference path="..\..\src\harness\typeWriter.ts" />
interface FileInformation {
contents: string;
@@ -8,19 +10,21 @@ interface FileInformation {
}
interface FindFileResult {
}
interface IOLogFile {
path: string;
codepage: number;
result?: FileInformation;
}
interface IOLog {
timestamp: string;
arguments: string[];
executingPath: string;
currentDirectory: string;
useCustomLibraryFile?: boolean;
filesRead: {
path: string;
codepage: number;
result?: FileInformation;
}[];
filesRead: IOLogFile[];
filesWritten: {
path: string;
contents: string;
@@ -58,6 +62,13 @@ interface IOLog {
path: string;
result?: string;
}[];
directoriesRead: {
path: string,
extensions: string[],
exclude: string[],
include: string[],
result: string[]
}[];
}
interface PlaybackControl {
@@ -69,10 +80,10 @@ interface PlaybackControl {
endRecord(): void;
}
module Playback {
var recordLog: IOLog = undefined;
var replayLog: IOLog = undefined;
var recordLogFileNameBase = '';
namespace Playback {
let recordLog: IOLog = undefined;
let replayLog: IOLog = undefined;
let recordLogFileNameBase = "";
interface Memoized<T> {
(s: string): T;
@@ -80,26 +91,29 @@ module Playback {
}
function memoize<T>(func: (s: string) => T): Memoized<T> {
var lookup: { [s: string]: T } = {};
var run: Memoized<T> = <Memoized<T>>((s: string) => {
let lookup: { [s: string]: T } = {};
const run: Memoized<T> = <Memoized<T>>((s: string) => {
if (lookup.hasOwnProperty(s)) return lookup[s];
return lookup[s] = func(s);
});
run.reset = () => {
lookup = null;
lookup = undefined;
};
return run;
}
export interface PlaybackIO extends Harness.IO, PlaybackControl { }
export interface PlaybackSystem extends ts.System, PlaybackControl { }
function createEmptyLog(): IOLog {
return {
timestamp: (new Date()).toString(),
arguments: [],
currentDirectory: '',
currentDirectory: "",
filesRead: [],
directoriesRead: [],
filesWritten: [],
filesDeleted: [],
filesAppended: [],
@@ -109,12 +123,14 @@ module Playback {
dirExists: [],
dirsCreated: [],
pathsResolved: [],
executingPath: ''
executingPath: ""
};
}
function initWrapper<T>(wrapper: PlaybackControl, underlying: T) {
Object.keys(underlying).forEach(prop => {
function initWrapper(wrapper: PlaybackSystem, underlying: ts.System): void;
function initWrapper(wrapper: PlaybackIO, underlying: Harness.IO): void;
function initWrapper(wrapper: PlaybackSystem | PlaybackIO, underlying: ts.System | Harness.IO): void {
ts.forEach(Object.keys(underlying), prop => {
(<any>wrapper)[prop] = (<any>underlying)[prop];
});
@@ -123,7 +139,7 @@ module Playback {
};
wrapper.startReplayFromData = log => {
replayLog = log;
// Remove non-found files from the log (shouldn't really need them, but we still record them for diganostic purposes)
// Remove non-found files from the log (shouldn't really need them, but we still record them for diagnostic purposes)
replayLog.filesRead = replayLog.filesRead.filter(f => f.result.contents !== undefined);
};
@@ -134,6 +150,107 @@ module Playback {
wrapper.startRecord = (fileNameBase) => {
recordLogFileNameBase = fileNameBase;
recordLog = createEmptyLog();
if (typeof underlying.args !== "function") {
recordLog.arguments = underlying.args;
}
};
wrapper.startReplayFromFile = logFn => {
wrapper.startReplayFromString(underlying.readFile(logFn));
};
wrapper.endRecord = () => {
if (recordLog !== undefined) {
let i = 0;
const fn = () => recordLogFileNameBase + i + ".json";
while (underlying.fileExists(fn())) i++;
underlying.writeFile(fn(), JSON.stringify(recordLog));
recordLog = undefined;
}
};
wrapper.fileExists = recordReplay(wrapper.fileExists, underlying)(
path => callAndRecord(underlying.fileExists(path), recordLog.fileExists, { path }),
memoize(path => {
// If we read from the file, it must exist
if (findFileByPath(replayLog.filesRead, path, /*throwFileNotFoundError*/ false)) {
return true;
}
else {
return findResultByFields(replayLog.fileExists, { path }, /*defaultValue*/ false);
}
})
);
wrapper.getExecutingFilePath = () => {
if (replayLog !== undefined) {
return replayLog.executingPath;
}
else if (recordLog !== undefined) {
return recordLog.executingPath = underlying.getExecutingFilePath();
}
else {
return underlying.getExecutingFilePath();
}
};
wrapper.getCurrentDirectory = () => {
if (replayLog !== undefined) {
return replayLog.currentDirectory || "";
}
else if (recordLog !== undefined) {
return recordLog.currentDirectory = underlying.getCurrentDirectory();
}
else {
return underlying.getCurrentDirectory();
}
};
wrapper.resolvePath = recordReplay(wrapper.resolvePath, underlying)(
path => callAndRecord(underlying.resolvePath(path), recordLog.pathsResolved, { path }),
memoize(path => findResultByFields(replayLog.pathsResolved, { path }, !ts.isRootedDiskPath(ts.normalizeSlashes(path)) && replayLog.currentDirectory ? replayLog.currentDirectory + "/" + path : ts.normalizeSlashes(path))));
wrapper.readFile = recordReplay(wrapper.readFile, underlying)(
(path: string) => {
const result = underlying.readFile(path);
const logEntry = { path, codepage: 0, result: { contents: result, codepage: 0 } };
recordLog.filesRead.push(logEntry);
return result;
},
memoize(path => findFileByPath(replayLog.filesRead, path, /*throwFileNotFoundError*/ true).contents));
wrapper.readDirectory = recordReplay(wrapper.readDirectory, underlying)(
(path, extensions, exclude, include) => {
const result = (<ts.System>underlying).readDirectory(path, extensions, exclude, include);
const logEntry = { path, extensions, exclude, include, result };
recordLog.directoriesRead.push(logEntry);
return result;
},
path => {
// Because extensions is an array of all allowed extension, we will want to merge each of the replayLog.directoriesRead into one
// if each of the directoriesRead has matched path with the given path (directory with same path but different extension will considered
// different entry).
// TODO (yuisu): We can certainly remove these once we recapture the RWC using new API
const normalizedPath = ts.normalizePath(path).toLowerCase();
const result: string[] = [];
for (const directory of replayLog.directoriesRead) {
if (ts.normalizeSlashes(directory.path).toLowerCase() === normalizedPath) {
result.push(...directory.result);
}
}
return result;
});
wrapper.writeFile = recordReplay(wrapper.writeFile, underlying)(
(path: string, contents: string) => callAndRecord(underlying.writeFile(path, contents), recordLog.filesWritten, { path, contents, bom: false }),
() => noOpReplay("writeFile"));
wrapper.exit = (exitCode) => {
if (recordLog !== undefined) {
wrapper.endRecord();
}
underlying.exit(exitCode);
};
}
@@ -142,9 +259,11 @@ module Playback {
return <any>(function () {
if (replayLog !== undefined) {
return replay.apply(undefined, arguments);
} else if (recordLog !== undefined) {
}
else if (recordLog !== undefined) {
return record.apply(undefined, arguments);
} else {
}
else {
return original.apply(underlying, arguments);
}
});
@@ -161,152 +280,64 @@ module Playback {
}
function findResultByFields<T>(logArray: { result?: T }[], expectedFields: {}, defaultValue?: T): T {
var predicate = (entry: { result?: T }) => {
const predicate = (entry: { result?: T }) => {
return Object.getOwnPropertyNames(expectedFields).every((name) => (<any>entry)[name] === (<any>expectedFields)[name]);
};
var results = logArray.filter(entry => predicate(entry));
const results = logArray.filter(entry => predicate(entry));
if (results.length === 0) {
if (defaultValue !== undefined) {
return defaultValue;
} else {
throw new Error('No matching result in log array for: ' + JSON.stringify(expectedFields));
}
else {
throw new Error("No matching result in log array for: " + JSON.stringify(expectedFields));
}
}
return results[0].result;
}
function findResultByPath<T>(wrapper: { resolvePath(s: string): string }, logArray: { path: string; result?: T }[], expectedPath: string, defaultValue?: T): T {
var normalizedName = ts.normalizeSlashes(expectedPath).toLowerCase();
function findFileByPath(logArray: IOLogFile[],
expectedPath: string, throwFileNotFoundError: boolean): FileInformation {
const normalizedName = ts.normalizePath(expectedPath).toLowerCase();
// Try to find the result through normal fileName
for (var i = 0; i < logArray.length; i++) {
if (ts.normalizeSlashes(logArray[i].path).toLowerCase() === normalizedName) {
return logArray[i].result;
}
}
// Fallback, try to resolve the target paths as well
if (replayLog.pathsResolved.length > 0) {
var normalizedResolvedName = wrapper.resolvePath(expectedPath).toLowerCase();
for (var i = 0; i < logArray.length; i++) {
if (wrapper.resolvePath(logArray[i].path).toLowerCase() === normalizedResolvedName) {
return logArray[i].result;
}
for (const log of logArray) {
if (ts.normalizeSlashes(log.path).toLowerCase() === normalizedName) {
return log.result;
}
}
// If we got here, we didn't find a match
if (defaultValue === undefined) {
throw new Error('No matching result in log array for path: ' + expectedPath);
} else {
return defaultValue;
if (throwFileNotFoundError) {
throw new Error("No matching result in log array for path: " + expectedPath);
}
else {
return undefined;
}
}
var pathEquivCache: any = {};
function pathsAreEquivalent(left: string, right: string, wrapper: { resolvePath(s: string): string }) {
var key = left + '-~~-' + right;
function areSame(a: string, b: string) {
return ts.normalizeSlashes(a).toLowerCase() === ts.normalizeSlashes(b).toLowerCase();
}
function check() {
if (Harness.Path.getFileName(left).toLowerCase() === Harness.Path.getFileName(right).toLowerCase()) {
return areSame(left, right) || areSame(wrapper.resolvePath(left), right) || areSame(left, wrapper.resolvePath(right)) || areSame(wrapper.resolvePath(left), wrapper.resolvePath(right));
}
}
if (pathEquivCache.hasOwnProperty(key)) {
return pathEquivCache[key];
} else {
return pathEquivCache[key] = check();
}
function noOpReplay(_name: string) {
// console.log("Swallowed write operation during replay: " + name);
}
function noOpReplay(name: string) {
//console.log("Swallowed write operation during replay: " + name);
export function wrapIO(underlying: Harness.IO): PlaybackIO {
const wrapper: PlaybackIO = <any>{};
initWrapper(wrapper, underlying);
wrapper.directoryName = notSupported;
wrapper.createDirectory = notSupported;
wrapper.directoryExists = notSupported;
wrapper.deleteFile = notSupported;
wrapper.listFiles = notSupported;
return wrapper;
function notSupported(): never {
throw new Error("NotSupported");
}
}
export function wrapSystem(underlying: ts.System): PlaybackSystem {
var wrapper: PlaybackSystem = <any>{};
const wrapper: PlaybackSystem = <any>{};
initWrapper(wrapper, underlying);
wrapper.startReplayFromFile = logFn => {
wrapper.startReplayFromString(underlying.readFile(logFn));
};
wrapper.endRecord = () => {
if (recordLog !== undefined) {
var i = 0;
var fn = () => recordLogFileNameBase + i + '.json';
while (underlying.fileExists(fn())) i++;
underlying.writeFile(fn(), JSON.stringify(recordLog));
recordLog = undefined;
}
};
Object.defineProperty(wrapper, 'args', {
get() {
if (replayLog !== undefined) {
return replayLog.arguments;
} else if (recordLog !== undefined) {
recordLog.arguments = underlying.args;
}
return underlying.args;
}
});
wrapper.fileExists = recordReplay(wrapper.fileExists, underlying)(
(path) => callAndRecord(underlying.fileExists(path), recordLog.fileExists, { path: path }),
memoize((path) => {
// If we read from the file, it must exist
if (findResultByPath(wrapper, replayLog.filesRead, path, null) !== null) {
return true;
} else {
return findResultByFields(replayLog.fileExists, { path: path }, false);
}
})
);
wrapper.getExecutingFilePath = () => {
if (replayLog !== undefined) {
return replayLog.executingPath;
} else if (recordLog !== undefined) {
return recordLog.executingPath = underlying.getExecutingFilePath();
} else {
return underlying.getExecutingFilePath();
}
};
wrapper.getCurrentDirectory = () => {
if (replayLog !== undefined) {
return replayLog.currentDirectory || '';
} else if (recordLog !== undefined) {
return recordLog.currentDirectory = underlying.getCurrentDirectory();
} else {
return underlying.getCurrentDirectory();
}
};
wrapper.resolvePath = recordReplay(wrapper.resolvePath, underlying)(
(path) => callAndRecord(underlying.resolvePath(path), recordLog.pathsResolved, { path: path }),
memoize((path) => findResultByFields(replayLog.pathsResolved, { path: path }, !ts.isRootedDiskPath(ts.normalizeSlashes(path)) && replayLog.currentDirectory ? replayLog.currentDirectory + '/' + path : ts.normalizeSlashes(path))));
wrapper.readFile = recordReplay(wrapper.readFile, underlying)(
(path) => {
var result = underlying.readFile(path);
var logEntry = { path: path, codepage: 0, result: { contents: result, codepage: 0 } };
recordLog.filesRead.push(logEntry);
return result;
},
memoize((path) => findResultByPath(wrapper, replayLog.filesRead, path).contents));
wrapper.writeFile = recordReplay(wrapper.writeFile, underlying)(
(path, contents) => callAndRecord(underlying.writeFile(path, contents), recordLog.filesWritten, { path: path, contents: contents, bom: false }),
(path, contents) => noOpReplay('writeFile'));
wrapper.exit = (exitCode) => {
if (recordLog !== undefined) {
wrapper.endRecord();
}
underlying.exit(exitCode);
};
return wrapper;
}
}
+295 -185
View File
@@ -6,19 +6,11 @@ interface ProjectRunnerTestCase {
scenario: string;
projectRoot: string; // project where it lives - this also is the current directory when compiling
inputFiles: string[]; // list of input files to be given to program
out?: string; // --out
outDir?: string; // --outDir
sourceMap?: boolean; // --map
mapRoot?: string; // --mapRoot
resolveMapRoot?: boolean; // should we resolve this map root and give compiler the absolute disk path as map root?
sourceRoot?: string; // --sourceRoot
resolveSourceRoot?: boolean; // should we resolve this source root and give compiler the absolute disk path as map root?
declaration?: boolean; // --d
baselineCheck?: boolean; // Verify the baselines of output files, if this is false, we will write to output to the disk but there is no verification of baselines
runTest?: boolean; // Run the resulting test
bug?: string; // If there is any bug associated with this test case
noResolve?: boolean;
rootDir?: string; // --rootDir
}
interface ProjectRunnerTestCaseResolutionInfo extends ProjectRunnerTestCase {
@@ -33,22 +25,30 @@ interface BatchCompileProjectTestCaseEmittedFile extends Harness.Compiler.Genera
interface CompileProjectFilesResult {
moduleKind: ts.ModuleKind;
program: ts.Program;
program?: ts.Program;
compilerOptions?: ts.CompilerOptions;
errors: ts.Diagnostic[];
sourceMapData: ts.SourceMapData[];
sourceMapData?: ts.SourceMapData[];
}
interface BatchCompileProjectTestCaseResult extends CompileProjectFilesResult {
outputFiles: BatchCompileProjectTestCaseEmittedFile[];
nonSubfolderDiskFiles: number;
outputFiles?: BatchCompileProjectTestCaseEmittedFile[];
}
class ProjectRunner extends RunnerBase {
public enumerateTestFiles() {
return this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true });
}
public kind(): TestRunnerKind {
return "project";
}
public initializeTests() {
if (this.tests.length === 0) {
var testFiles = this.enumerateFiles("tests/cases/project", /\.json$/, { recursive: true });
const testFiles = this.enumerateTestFiles();
testFiles.forEach(fn => {
fn = fn.replace(/\\/g, "/");
this.runProjectTestCase(fn);
});
}
@@ -58,22 +58,23 @@ class ProjectRunner extends RunnerBase {
}
private runProjectTestCase(testCaseFileName: string) {
var testCase: ProjectRunnerTestCase;
let testCase: ProjectRunnerTestCase & ts.CompilerOptions;
let testFileText: string;
try {
var testFileText = ts.sys.readFile(testCaseFileName);
testFileText = Harness.IO.readFile(testCaseFileName);
}
catch (e) {
assert(false, "Unable to open testcase file: " + testCaseFileName + ": " + e.message);
}
try {
testCase = <ProjectRunnerTestCase>JSON.parse(testFileText);
testCase = <ProjectRunnerTestCase & ts.CompilerOptions>JSON.parse(testFileText);
}
catch (e) {
assert(false, "Testcase: " + testCaseFileName + " does not contain valid json format: " + e.message);
}
var testCaseJustName = testCaseFileName.replace(/^.*[\\\/]/, '').replace(/\.json/, "");
let testCaseJustName = testCaseFileName.replace(/^.*[\\\/]/, "").replace(/\.json/, "");
function moduleNameToString(moduleKind: ts.ModuleKind) {
return moduleKind === ts.ModuleKind.AMD
@@ -89,7 +90,7 @@ class ProjectRunner extends RunnerBase {
}
// When test case output goes to tests/baselines/local/projectOutput/testCaseName/moduleKind/
// We have these two separate locations because when comparing baselines the baseline verifier will delete the existing file
// We have these two separate locations because when comparing baselines the baseline verifier will delete the existing file
// so even if it was created by compiler in that location, the file will be deleted by verified before we can read it
// so lets keep these two locations separate
function getProjectOutputFolder(fileName: string, moduleKind: ts.ModuleKind) {
@@ -97,9 +98,9 @@ class ProjectRunner extends RunnerBase {
}
function cleanProjectUrl(url: string) {
var diskProjectPath = ts.normalizeSlashes(ts.sys.resolvePath(testCase.projectRoot));
var projectRootUrl = "file:///" + diskProjectPath;
var normalizedProjectRoot = ts.normalizeSlashes(testCase.projectRoot);
let diskProjectPath = ts.normalizeSlashes(Harness.IO.resolvePath(testCase.projectRoot));
let projectRootUrl = "file:///" + diskProjectPath;
const normalizedProjectRoot = ts.normalizeSlashes(testCase.projectRoot);
diskProjectPath = diskProjectPath.substr(0, diskProjectPath.lastIndexOf(normalizedProjectRoot));
projectRootUrl = projectRootUrl.substr(0, projectRootUrl.lastIndexOf(normalizedProjectRoot));
if (url && url.length) {
@@ -110,8 +111,7 @@ class ProjectRunner extends RunnerBase {
else if (url.indexOf(diskProjectPath) === 0) {
// Replace the disk specific path into the project root path
url = url.substr(diskProjectPath.length);
// TODO: should be '!=='?
if (url.charCodeAt(0) != ts.CharacterCodes.slash) {
if (url.charCodeAt(0) !== ts.CharacterCodes.slash) {
url = "/" + url;
}
}
@@ -121,24 +121,25 @@ class ProjectRunner extends RunnerBase {
}
function getCurrentDirectory() {
return ts.sys.resolvePath(testCase.projectRoot);
return Harness.IO.resolvePath(testCase.projectRoot);
}
function compileProjectFiles(moduleKind: ts.ModuleKind, getInputFiles: ()=> string[],
getSourceFileText: (fileName: string) => string,
writeFile: (fileName: string, data: string, writeByteOrderMark: boolean) => void): CompileProjectFilesResult {
function compileProjectFiles(moduleKind: ts.ModuleKind, getInputFiles: () => string[],
getSourceFileTextImpl: (fileName: string) => string,
writeFile: (fileName: string, data: string, writeByteOrderMark: boolean) => void,
compilerOptions: ts.CompilerOptions): CompileProjectFilesResult {
var program = ts.createProgram(getInputFiles(), createCompilerOptions(), createCompilerHost());
var errors = ts.getPreEmitDiagnostics(program);
const program = ts.createProgram(getInputFiles(), compilerOptions, createCompilerHost());
let errors = ts.getPreEmitDiagnostics(program);
var emitResult = program.emit();
const emitResult = program.emit();
errors = ts.concatenate(errors, emitResult.diagnostics);
var sourceMapData = emitResult.sourceMaps;
const sourceMapData = emitResult.sourceMaps;
// Clean up source map data that will be used in baselining
if (sourceMapData) {
for (var i = 0; i < sourceMapData.length; i++) {
for (var j = 0; j < sourceMapData[i].sourceMapSources.length; j++) {
for (let i = 0; i < sourceMapData.length; i++) {
for (let j = 0; j < sourceMapData[i].sourceMapSources.length; j++) {
sourceMapData[i].sourceMapSources[j] = cleanProjectUrl(sourceMapData[i].sourceMapSources[j]);
}
sourceMapData[i].jsSourceMappingURL = cleanProjectUrl(sourceMapData[i].jsSourceMappingURL);
@@ -153,27 +154,18 @@ class ProjectRunner extends RunnerBase {
sourceMapData
};
function createCompilerOptions(): ts.CompilerOptions {
return {
declaration: !!testCase.declaration,
sourceMap: !!testCase.sourceMap,
out: testCase.out,
outDir: testCase.outDir,
mapRoot: testCase.resolveMapRoot && testCase.mapRoot ? ts.sys.resolvePath(testCase.mapRoot) : testCase.mapRoot,
sourceRoot: testCase.resolveSourceRoot && testCase.sourceRoot ? ts.sys.resolvePath(testCase.sourceRoot) : testCase.sourceRoot,
module: moduleKind,
noResolve: testCase.noResolve,
rootDir: testCase.rootDir
};
function getSourceFileText(fileName: string): string {
const text = getSourceFileTextImpl(fileName);
return text !== undefined ? text : getSourceFileTextImpl(ts.getNormalizedAbsolutePath(fileName, getCurrentDirectory()));
}
function getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile {
var sourceFile: ts.SourceFile = undefined;
let sourceFile: ts.SourceFile = undefined;
if (fileName === Harness.Compiler.defaultLibFileName) {
sourceFile = languageVersion === ts.ScriptTarget.ES6 ? Harness.Compiler.defaultES6LibSourceFile : Harness.Compiler.defaultLibSourceFile;
sourceFile = Harness.Compiler.getDefaultLibrarySourceFile(Harness.Compiler.getDefaultLibFileName(compilerOptions));
}
else {
var text = getSourceFileText(fileName);
const text = getSourceFileText(fileName);
if (text !== undefined) {
sourceFile = Harness.Compiler.createSourceFileAndAssertInvariants(fileName, text, languageVersion);
}
@@ -185,36 +177,132 @@ class ProjectRunner extends RunnerBase {
function createCompilerHost(): ts.CompilerHost {
return {
getSourceFile,
getDefaultLibFileName: options => Harness.Compiler.defaultLibFileName,
getDefaultLibFileName: () => Harness.Compiler.defaultLibFileName,
writeFile,
getCurrentDirectory,
getCanonicalFileName: Harness.Compiler.getCanonicalFileName,
useCaseSensitiveFileNames: () => ts.sys.useCaseSensitiveFileNames,
getNewLine: () => ts.sys.newLine
useCaseSensitiveFileNames: () => Harness.IO.useCaseSensitiveFileNames(),
getNewLine: () => Harness.IO.newLine(),
fileExists: fileName => fileName === Harness.Compiler.defaultLibFileName || getSourceFileText(fileName) !== undefined,
readFile: fileName => Harness.IO.readFile(fileName),
getDirectories: path => Harness.IO.getDirectories(path)
};
}
}
function batchCompilerProjectTestCase(moduleKind: ts.ModuleKind): BatchCompileProjectTestCaseResult{
var nonSubfolderDiskFiles = 0;
var outputFiles: BatchCompileProjectTestCaseEmittedFile[] = [];
function batchCompilerProjectTestCase(moduleKind: ts.ModuleKind): BatchCompileProjectTestCaseResult {
let nonSubfolderDiskFiles = 0;
var projectCompilerResult = compileProjectFiles(moduleKind, () => testCase.inputFiles, getSourceFileText, writeFile);
const outputFiles: BatchCompileProjectTestCaseEmittedFile[] = [];
let inputFiles = testCase.inputFiles;
let compilerOptions = createCompilerOptions();
let configFileName: string;
if (compilerOptions.project) {
// Parse project
configFileName = ts.normalizePath(ts.combinePaths(compilerOptions.project, "tsconfig.json"));
assert(!inputFiles || inputFiles.length === 0, "cannot specify input files and project option together");
}
else if (!inputFiles || inputFiles.length === 0) {
configFileName = ts.findConfigFile("", fileExists);
}
if (configFileName) {
const result = ts.readConfigFile(configFileName, getSourceFileText);
if (result.error) {
return {
moduleKind,
errors: [result.error]
};
}
const configObject = result.config;
const configParseHost: ts.ParseConfigHost = {
useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(),
fileExists,
readDirectory,
readFile
};
const configParseResult = ts.parseJsonConfigFileContent(configObject, configParseHost, ts.getDirectoryPath(configFileName), compilerOptions);
if (configParseResult.errors.length > 0) {
return {
moduleKind,
errors: configParseResult.errors
};
}
inputFiles = configParseResult.fileNames;
compilerOptions = configParseResult.options;
}
const projectCompilerResult = compileProjectFiles(moduleKind, () => inputFiles, getSourceFileText, writeFile, compilerOptions);
return {
moduleKind,
program: projectCompilerResult.program,
compilerOptions,
sourceMapData: projectCompilerResult.sourceMapData,
outputFiles,
errors: projectCompilerResult.errors,
nonSubfolderDiskFiles,
};
function createCompilerOptions() {
// Set the special options that depend on other testcase options
const compilerOptions: ts.CompilerOptions = {
mapRoot: testCase.resolveMapRoot && testCase.mapRoot ? Harness.IO.resolvePath(testCase.mapRoot) : testCase.mapRoot,
sourceRoot: testCase.resolveSourceRoot && testCase.sourceRoot ? Harness.IO.resolvePath(testCase.sourceRoot) : testCase.sourceRoot,
module: moduleKind,
moduleResolution: ts.ModuleResolutionKind.Classic, // currently all tests use classic module resolution kind, this will change in the future
};
// Set the values specified using json
const optionNameMap = ts.arrayToMap(ts.optionDeclarations, option => option.name);
for (const name in testCase) {
if (name !== "mapRoot" && name !== "sourceRoot") {
const option = optionNameMap.get(name);
if (option) {
const optType = option.type;
let value = <any>testCase[name];
if (typeof optType !== "string") {
const key = value.toLowerCase();
const optTypeValue = optType.get(key);
if (optTypeValue) {
value = optTypeValue;
}
}
compilerOptions[option.name] = value;
}
}
}
return compilerOptions;
}
function getFileNameInTheProjectTest(fileName: string): string {
return ts.isRootedDiskPath(fileName)
? fileName
: ts.normalizeSlashes(testCase.projectRoot) + "/" + ts.normalizeSlashes(fileName);
}
function readDirectory(rootDir: string, extension: string[], exclude: string[], include: string[]): string[] {
const harnessReadDirectoryResult = Harness.IO.readDirectory(getFileNameInTheProjectTest(rootDir), extension, exclude, include);
const result: string[] = [];
for (let i = 0; i < harnessReadDirectoryResult.length; i++) {
result[i] = ts.getRelativePathToDirectoryOrUrl(testCase.projectRoot, harnessReadDirectoryResult[i],
getCurrentDirectory(), Harness.Compiler.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
}
return result;
}
function fileExists(fileName: string): boolean {
return Harness.IO.fileExists(getFileNameInTheProjectTest(fileName));
}
function readFile(fileName: string): string {
return Harness.IO.readFile(getFileNameInTheProjectTest(fileName));
}
function getSourceFileText(fileName: string): string {
let text: string = undefined;
try {
var text = ts.sys.readFile(ts.isRootedDiskPath(fileName)
? fileName
: ts.normalizeSlashes(testCase.projectRoot) + "/" + ts.normalizeSlashes(fileName));
text = Harness.IO.readFile(getFileNameInTheProjectTest(fileName));
}
catch (e) {
// text doesn't get defined.
@@ -223,215 +311,237 @@ class ProjectRunner extends RunnerBase {
}
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) {
var diskFileName = ts.isRootedDiskPath(fileName)
// convert file name to rooted name
// if filename is not rooted - concat it with project root and then expand project root relative to current directory
const diskFileName = ts.isRootedDiskPath(fileName)
? fileName
: ts.normalizeSlashes(testCase.projectRoot) + "/" + ts.normalizeSlashes(fileName);
: Harness.IO.resolvePath(ts.normalizeSlashes(testCase.projectRoot) + "/" + ts.normalizeSlashes(fileName));
var diskRelativeName = ts.getRelativePathToDirectoryOrUrl(testCase.projectRoot, diskFileName,
getCurrentDirectory(), Harness.Compiler.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
const currentDirectory = getCurrentDirectory();
// compute file name relative to current directory (expanded project root)
let diskRelativeName = ts.getRelativePathToDirectoryOrUrl(currentDirectory, diskFileName, currentDirectory, Harness.Compiler.getCanonicalFileName, /*isAbsolutePathAnUrl*/ false);
if (ts.isRootedDiskPath(diskRelativeName) || diskRelativeName.substr(0, 3) === "../") {
// If the generated output file resides in the parent folder or is rooted path,
// If the generated output file resides in the parent folder or is rooted path,
// we need to instead create files that can live in the project reference folder
// but make sure extension of these files matches with the fileName the compiler asked to write
diskRelativeName = "diskFile" + nonSubfolderDiskFiles++ +
diskRelativeName = "diskFile" + nonSubfolderDiskFiles +
(Harness.Compiler.isDTS(fileName) ? ".d.ts" :
Harness.Compiler.isJS(fileName) ? ".js" : ".js.map");
nonSubfolderDiskFiles++;
}
if (Harness.Compiler.isJS(fileName)) {
// Make sure if there is URl we have it cleaned up
var indexOfSourceMapUrl = data.lastIndexOf("//# sourceMappingURL=");
const indexOfSourceMapUrl = data.lastIndexOf(`//# ${"sourceMappingURL"}=`); // This line can be seen as a sourceMappingURL comment
if (indexOfSourceMapUrl !== -1) {
data = data.substring(0, indexOfSourceMapUrl + 21) + cleanProjectUrl(data.substring(indexOfSourceMapUrl + 21));
}
}
else if (Harness.Compiler.isJSMap(fileName)) {
// Make sure sources list is cleaned
var sourceMapData = JSON.parse(data);
for (var i = 0; i < sourceMapData.sources.length; i++) {
const sourceMapData = JSON.parse(data);
for (let i = 0; i < sourceMapData.sources.length; i++) {
sourceMapData.sources[i] = cleanProjectUrl(sourceMapData.sources[i]);
}
sourceMapData.sourceRoot = cleanProjectUrl(sourceMapData.sourceRoot);
data = JSON.stringify(sourceMapData);
}
var outputFilePath = getProjectOutputFolder(diskRelativeName, moduleKind);
const outputFilePath = getProjectOutputFolder(diskRelativeName, moduleKind);
// Actual writing of file as in tc.ts
function ensureDirectoryStructure(directoryname: string) {
if (directoryname) {
if (!ts.sys.directoryExists(directoryname)) {
if (!Harness.IO.directoryExists(directoryname)) {
ensureDirectoryStructure(ts.getDirectoryPath(directoryname));
ts.sys.createDirectory(directoryname);
Harness.IO.createDirectory(directoryname);
}
}
}
ensureDirectoryStructure(ts.getDirectoryPath(ts.normalizePath(outputFilePath)));
ts.sys.writeFile(outputFilePath, data, writeByteOrderMark);
Harness.IO.writeFile(outputFilePath, data);
outputFiles.push({ emittedFileName: fileName, code: data, fileName: diskRelativeName, writeByteOrderMark: writeByteOrderMark });
}
}
function compileCompileDTsFiles(compilerResult: BatchCompileProjectTestCaseResult) {
var allInputFiles: { emittedFileName: string; code: string; }[] = [];
var compilerOptions = compilerResult.program.getCompilerOptions();
const allInputFiles: { emittedFileName: string; code: string; }[] = [];
if (!compilerResult.program) {
return;
}
const compilerOptions = compilerResult.program.getCompilerOptions();
ts.forEach(compilerResult.program.getSourceFiles(), sourceFile => {
if (Harness.Compiler.isDTS(sourceFile.fileName)) {
if (sourceFile.isDeclarationFile) {
allInputFiles.unshift({ emittedFileName: sourceFile.fileName, code: sourceFile.text });
}
else if (ts.shouldEmitToOwnFile(sourceFile, compilerResult.program.getCompilerOptions())) {
else if (!(compilerOptions.outFile || compilerOptions.out)) {
let emitOutputFilePathWithoutExtension: string = undefined;
if (compilerOptions.outDir) {
var sourceFilePath = ts.getNormalizedAbsolutePath(sourceFile.fileName, compilerResult.program.getCurrentDirectory());
let sourceFilePath = ts.getNormalizedAbsolutePath(sourceFile.fileName, compilerResult.program.getCurrentDirectory());
sourceFilePath = sourceFilePath.replace(compilerResult.program.getCommonSourceDirectory(), "");
var emitOutputFilePathWithoutExtension = ts.removeFileExtension(ts.combinePaths(compilerOptions.outDir, sourceFilePath));
emitOutputFilePathWithoutExtension = ts.removeFileExtension(ts.combinePaths(compilerOptions.outDir, sourceFilePath));
}
else {
var emitOutputFilePathWithoutExtension = ts.removeFileExtension(sourceFile.fileName);
emitOutputFilePathWithoutExtension = ts.removeFileExtension(sourceFile.fileName);
}
var outputDtsFileName = emitOutputFilePathWithoutExtension + ".d.ts";
allInputFiles.unshift(findOutpuDtsFile(outputDtsFileName));
const outputDtsFileName = emitOutputFilePathWithoutExtension + ".d.ts";
const file = findOutputDtsFile(outputDtsFileName);
if (file) {
allInputFiles.unshift(file);
}
}
else {
var outputDtsFileName = ts.removeFileExtension(compilerOptions.out) + ".d.ts";
var outputDtsFile = findOutpuDtsFile(outputDtsFileName);
const outputDtsFileName = ts.removeFileExtension(compilerOptions.outFile || compilerOptions.out) + ".d.ts";
const outputDtsFile = findOutputDtsFile(outputDtsFileName);
if (!ts.contains(allInputFiles, outputDtsFile)) {
allInputFiles.unshift(outputDtsFile);
}
}
});
return compileProjectFiles(compilerResult.moduleKind,getInputFiles, getSourceFileText, writeFile);
// Dont allow config files since we are compiling existing source options
return compileProjectFiles(compilerResult.moduleKind, getInputFiles, getSourceFileText, writeFile, compilerResult.compilerOptions);
function findOutpuDtsFile(fileName: string) {
function findOutputDtsFile(fileName: string) {
return ts.forEach(compilerResult.outputFiles, outputFile => outputFile.emittedFileName === fileName ? outputFile : undefined);
}
function getInputFiles() {
return ts.map(allInputFiles, outputFile => outputFile.emittedFileName);
}
function getSourceFileText(fileName: string): string {
return ts.forEach(allInputFiles, inputFile => inputFile.emittedFileName === fileName ? inputFile.code : undefined);
for (const inputFile of allInputFiles) {
const isMatchingFile = ts.isRootedDiskPath(fileName)
? ts.getNormalizedAbsolutePath(inputFile.emittedFileName, getCurrentDirectory()) === fileName
: inputFile.emittedFileName === fileName;
if (isMatchingFile) {
return inputFile.code;
}
}
return undefined;
}
function writeFile(fileName: string, data: string, writeByteOrderMark: boolean) {
function writeFile() {
}
}
function getErrorsBaseline(compilerResult: CompileProjectFilesResult) {
var inputFiles = ts.map(ts.filter(compilerResult.program.getSourceFiles(),
sourceFile => sourceFile.fileName !== "lib.d.ts"),
const inputFiles = compilerResult.program ? ts.map(ts.filter(compilerResult.program.getSourceFiles(),
sourceFile => !Harness.isDefaultLibraryFile(sourceFile.fileName)),
sourceFile => {
return { unitName: sourceFile.fileName, content: sourceFile.text };
});
return {
unitName: ts.isRootedDiskPath(sourceFile.fileName) ?
RunnerBase.removeFullPaths(sourceFile.fileName) :
sourceFile.fileName,
content: sourceFile.text
};
}) : [];
return Harness.Compiler.getErrorBaseline(inputFiles, compilerResult.errors);
}
var name = 'Compiling project for ' + testCase.scenario + ': testcase ' + testCaseFileName;
const name = "Compiling project for " + testCase.scenario + ": testcase " + testCaseFileName;
describe(name, () => {
function verifyCompilerResults(moduleKind: ts.ModuleKind) {
function getCompilerResolutionInfo() {
var resolutionInfo: ProjectRunnerTestCaseResolutionInfo = {
scenario: testCase.scenario,
projectRoot: testCase.projectRoot,
inputFiles: testCase.inputFiles,
out: testCase.out,
outDir: testCase.outDir,
sourceMap: testCase.sourceMap,
mapRoot: testCase.mapRoot,
resolveMapRoot: testCase.resolveMapRoot,
sourceRoot: testCase.sourceRoot,
resolveSourceRoot: testCase.resolveSourceRoot,
declaration: testCase.declaration,
baselineCheck: testCase.baselineCheck,
runTest: testCase.runTest,
bug: testCase.bug,
rootDir: testCase.rootDir,
resolvedInputFiles: ts.map(compilerResult.program.getSourceFiles(), inputFile => inputFile.fileName),
emittedFiles: ts.map(compilerResult.outputFiles, outputFile => outputFile.emittedFileName)
};
describe("Projects tests", () => {
describe(name, () => {
function verifyCompilerResults(moduleKind: ts.ModuleKind) {
let compilerResult: BatchCompileProjectTestCaseResult;
return resolutionInfo;
}
function getCompilerResolutionInfo() {
const resolutionInfo: ProjectRunnerTestCaseResolutionInfo & ts.CompilerOptions = JSON.parse(JSON.stringify(testCase));
resolutionInfo.resolvedInputFiles = ts.map(compilerResult.program.getSourceFiles(), inputFile => {
return ts.convertToRelativePath(inputFile.fileName, getCurrentDirectory(), path => Harness.Compiler.getCanonicalFileName(path));
});
resolutionInfo.emittedFiles = ts.map(compilerResult.outputFiles, outputFile => {
return ts.convertToRelativePath(outputFile.emittedFileName, getCurrentDirectory(), path => Harness.Compiler.getCanonicalFileName(path));
});
return resolutionInfo;
}
var compilerResult: BatchCompileProjectTestCaseResult;
it(name + ": " + moduleNameToString(moduleKind) , () => {
// Compile using node
compilerResult = batchCompilerProjectTestCase(moduleKind);
});
it('Resolution information of (' + moduleNameToString(moduleKind) + '): ' + testCaseFileName, () => {
Harness.Baseline.runBaseline('Resolution information of (' + moduleNameToString(compilerResult.moduleKind) + '): ' + testCaseFileName, getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + '.json', () => {
return JSON.stringify(getCompilerResolutionInfo(), undefined, " ");
it(name + ": " + moduleNameToString(moduleKind), () => {
// Compile using node
compilerResult = batchCompilerProjectTestCase(moduleKind);
});
});
it('Errors for (' + moduleNameToString(moduleKind) + '): ' + testCaseFileName, () => {
if (compilerResult.errors.length) {
Harness.Baseline.runBaseline('Errors for (' + moduleNameToString(compilerResult.moduleKind) + '): ' + testCaseFileName, getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + '.errors.txt', () => {
return getErrorsBaseline(compilerResult);
it("Resolution information of (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".json", () => {
return JSON.stringify(getCompilerResolutionInfo(), undefined, " ");
});
}
});
});
it('Baseline of emitted result (' + moduleNameToString(moduleKind) + '): ' + testCaseFileName, () => {
if (testCase.baselineCheck) {
ts.forEach(compilerResult.outputFiles, outputFile => {
Harness.Baseline.runBaseline('Baseline of emitted result (' + moduleNameToString(compilerResult.moduleKind) + '): ' + testCaseFileName, getBaselineFolder(compilerResult.moduleKind) + outputFile.fileName, () => {
try {
return ts.sys.readFile(getProjectOutputFolder(outputFile.fileName, compilerResult.moduleKind));
}
catch (e) {
return undefined;
}
});
});
}
});
it('SourceMapRecord for (' + moduleNameToString(moduleKind) + '): ' + testCaseFileName, () => {
if (compilerResult.sourceMapData) {
Harness.Baseline.runBaseline('SourceMapRecord for (' + moduleNameToString(compilerResult.moduleKind) + '): ' + testCaseFileName, getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + '.sourcemap.txt', () => {
return Harness.SourceMapRecoder.getSourceMapRecord(compilerResult.sourceMapData, compilerResult.program,
ts.filter(compilerResult.outputFiles, outputFile => Harness.Compiler.isJS(outputFile.emittedFileName)));
});
}
});
// Verify that all the generated .d.ts files compile
it('Errors in generated Dts files for (' + moduleNameToString(moduleKind) + '): ' + testCaseFileName, () => {
if (!compilerResult.errors.length && testCase.declaration) {
var dTsCompileResult = compileCompileDTsFiles(compilerResult);
if (dTsCompileResult.errors.length) {
Harness.Baseline.runBaseline('Errors in generated Dts files for (' + moduleNameToString(compilerResult.moduleKind) + '): ' + testCaseFileName, getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + '.dts.errors.txt', () => {
return getErrorsBaseline(dTsCompileResult);
it("Errors for (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
if (compilerResult.errors.length) {
Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".errors.txt", () => {
return getErrorsBaseline(compilerResult);
});
}
}
});
});
it("Baseline of emitted result (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
if (testCase.baselineCheck) {
const errs: Error[] = [];
ts.forEach(compilerResult.outputFiles, outputFile => {
// There may be multiple files with different baselines. Run all and report at the end, else
// it stops copying the remaining emitted files from 'local/projectOutput' to 'local/project'.
try {
Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + outputFile.fileName, () => {
try {
return Harness.IO.readFile(getProjectOutputFolder(outputFile.fileName, compilerResult.moduleKind));
}
catch (e) {
return undefined;
}
});
}
catch (e) {
errs.push(e);
}
});
if (errs.length) {
throw Error(errs.join("\n "));
}
}
});
// it("SourceMapRecord for (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
// if (compilerResult.sourceMapData) {
// Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".sourcemap.txt", () => {
// return Harness.SourceMapRecorder.getSourceMapRecord(compilerResult.sourceMapData, compilerResult.program,
// ts.filter(compilerResult.outputFiles, outputFile => Harness.Compiler.isJS(outputFile.emittedFileName)));
// });
// }
// });
// Verify that all the generated .d.ts files compile
it("Errors in generated Dts files for (" + moduleNameToString(moduleKind) + "): " + testCaseFileName, () => {
if (!compilerResult.errors.length && testCase.declaration) {
const dTsCompileResult = compileCompileDTsFiles(compilerResult);
if (dTsCompileResult && dTsCompileResult.errors.length) {
Harness.Baseline.runBaseline(getBaselineFolder(compilerResult.moduleKind) + testCaseJustName + ".dts.errors.txt", () => {
return getErrorsBaseline(dTsCompileResult);
});
}
}
});
after(() => {
compilerResult = undefined;
});
}
verifyCompilerResults(ts.ModuleKind.CommonJS);
verifyCompilerResults(ts.ModuleKind.AMD);
after(() => {
compilerResult = undefined;
// Mocha holds onto the closure environment of the describe callback even after the test is done.
// Therefore we have to clean out large objects after the test is done.
testCase = undefined;
testFileText = undefined;
testCaseJustName = undefined;
});
}
verifyCompilerResults(ts.ModuleKind.CommonJS);
verifyCompilerResults(ts.ModuleKind.AMD);
after(() => {
// Mocha holds onto the closure environment of the describe callback even after the test is done.
// Therefore we have to clean out large objects after the test is done.
testCase = undefined;
testFileText = undefined;
testCaseJustName = undefined;
});
});
}
}
}
+154 -31
View File
@@ -1,6 +1,6 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -13,71 +13,158 @@
// limitations under the License.
//
/// <reference path='test262Runner.ts' />
/// <reference path='compilerRunner.ts' />
/// <reference path='fourslashRunner.ts' />
/// <reference path='projectsRunner.ts' />
/// <reference path='rwcRunner.ts' />
/// <reference path='harness.ts' />
/// <reference path="test262Runner.ts" />
/// <reference path="compilerRunner.ts" />
/// <reference path="fourslashRunner.ts" />
/// <reference path="projectsRunner.ts" />
/// <reference path="rwcRunner.ts" />
/// <reference path="harness.ts" />
let runners: RunnerBase[] = [];
let iterations = 1;
function runTests(runners: RunnerBase[]) {
for (var i = iterations; i > 0; i--) {
for (var j = 0; j < runners.length; j++) {
for (let i = iterations; i > 0; i--) {
for (let j = 0; j < runners.length; j++) {
runners[j].initializeTests();
}
}
}
var runners: RunnerBase[] = [];
var iterations: number = 1;
function tryGetConfig(args: string[]) {
const prefix = "--config=";
const configPath = ts.forEach(args, arg => arg.lastIndexOf(prefix, 0) === 0 && arg.substr(prefix.length));
// strip leading and trailing quotes from the path (necessary on Windows since shell does not do it automatically)
return configPath && configPath.replace(/(^[\"'])|([\"']$)/g, "");
}
function createRunner(kind: TestRunnerKind): RunnerBase {
switch (kind) {
case "conformance":
return new CompilerBaselineRunner(CompilerTestType.Conformance);
case "compiler":
return new CompilerBaselineRunner(CompilerTestType.Regressions);
case "fourslash":
return new FourSlashRunner(FourSlashTestType.Native);
case "fourslash-shims":
return new FourSlashRunner(FourSlashTestType.Shims);
case "fourslash-shims-pp":
return new FourSlashRunner(FourSlashTestType.ShimsWithPreprocess);
case "fourslash-server":
return new FourSlashRunner(FourSlashTestType.Server);
case "project":
return new ProjectRunner();
case "rwc":
return new RWCRunner();
case "test262":
return new Test262BaselineRunner();
}
}
if (Harness.IO.tryEnableSourceMapsForHost && /^development$/i.test(Harness.IO.getEnvironmentVariable("NODE_ENV"))) {
Harness.IO.tryEnableSourceMapsForHost();
}
// users can define tests to run in mytest.config that will override cmd line args, otherwise use cmd line args (test.config), otherwise no options
var mytestconfig = 'mytest.config';
var testconfig = 'test.config';
var testConfigFile =
Harness.IO.fileExists(mytestconfig) ? Harness.IO.readFile(mytestconfig) :
(Harness.IO.fileExists(testconfig) ? Harness.IO.readFile(testconfig) : '');
if (testConfigFile !== '') {
var testConfig = JSON.parse(testConfigFile);
const mytestconfigFileName = "mytest.config";
const testconfigFileName = "test.config";
const customConfig = tryGetConfig(Harness.IO.args());
let testConfigContent =
customConfig && Harness.IO.fileExists(customConfig)
? Harness.IO.readFile(customConfig)
: Harness.IO.fileExists(mytestconfigFileName)
? Harness.IO.readFile(mytestconfigFileName)
: Harness.IO.fileExists(testconfigFileName) ? Harness.IO.readFile(testconfigFileName) : "";
let taskConfigsFolder: string;
let workerCount: number;
let runUnitTests = true;
interface TestConfig {
light?: boolean;
taskConfigsFolder?: string;
workerCount?: number;
stackTraceLimit?: number | "full";
tasks?: TaskSet[];
test?: string[];
runUnitTests?: boolean;
}
interface TaskSet {
runner: TestRunnerKind;
files: string[];
}
if (testConfigContent !== "") {
const testConfig = <TestConfig>JSON.parse(testConfigContent);
if (testConfig.light) {
Harness.lightMode = true;
}
if (testConfig.taskConfigsFolder) {
taskConfigsFolder = testConfig.taskConfigsFolder;
}
if (testConfig.runUnitTests !== undefined) {
runUnitTests = testConfig.runUnitTests;
}
if (testConfig.workerCount) {
workerCount = testConfig.workerCount;
}
if (testConfig.tasks) {
for (const taskSet of testConfig.tasks) {
const runner = createRunner(taskSet.runner);
for (const file of taskSet.files) {
runner.addTest(file);
}
runners.push(runner);
}
}
if (testConfig.stackTraceLimit === "full") {
(<any>Error).stackTraceLimit = Infinity;
}
else if ((+testConfig.stackTraceLimit | 0) > 0) {
(<any>Error).stackTraceLimit = testConfig.stackTraceLimit;
}
if (testConfig.test && testConfig.test.length > 0) {
for (let option of testConfig.test) {
for (const option of testConfig.test) {
if (!option) {
continue;
}
switch (option) {
case 'compiler':
case "compiler":
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
runners.push(new CompilerBaselineRunner(CompilerTestType.Regressions));
runners.push(new ProjectRunner());
break;
case 'conformance':
case "conformance":
runners.push(new CompilerBaselineRunner(CompilerTestType.Conformance));
break;
case 'project':
case "project":
runners.push(new ProjectRunner());
break;
case 'fourslash':
case "fourslash":
runners.push(new FourSlashRunner(FourSlashTestType.Native));
break;
case 'fourslash-shims':
case "fourslash-shims":
runners.push(new FourSlashRunner(FourSlashTestType.Shims));
break;
case 'fourslash-server':
case "fourslash-shims-pp":
runners.push(new FourSlashRunner(FourSlashTestType.ShimsWithPreprocess));
break;
case "fourslash-server":
runners.push(new FourSlashRunner(FourSlashTestType.Server));
break;
case 'fourslash-generated':
case "fourslash-generated":
runners.push(new GeneratedFourslashRunner(FourSlashTestType.Native));
break;
case 'rwc':
case "rwc":
runners.push(new RWCRunner());
break;
case 'test262':
case "test262":
runners.push(new Test262BaselineRunner());
break;
}
@@ -98,10 +185,46 @@ if (runners.length === 0) {
// language services
runners.push(new FourSlashRunner(FourSlashTestType.Native));
runners.push(new FourSlashRunner(FourSlashTestType.Shims));
runners.push(new FourSlashRunner(FourSlashTestType.ShimsWithPreprocess));
runners.push(new FourSlashRunner(FourSlashTestType.Server));
//runners.push(new GeneratedFourslashRunner());
// runners.push(new GeneratedFourslashRunner());
}
ts.sys.newLine = '\r\n';
if (taskConfigsFolder) {
// this instance of mocha should only partition work but not run actual tests
runUnitTests = false;
const workerConfigs: TestConfig[] = [];
for (let i = 0; i < workerCount; i++) {
// pass light mode settings to workers
workerConfigs.push({ light: Harness.lightMode, tasks: [] });
}
runTests(runners);
for (const runner of runners) {
const files = runner.enumerateTestFiles();
const chunkSize = Math.floor(files.length / workerCount) + 1; // add extra 1 to prevent missing tests due to rounding
for (let i = 0; i < workerCount; i++) {
const startPos = i * chunkSize;
const len = Math.min(chunkSize, files.length - startPos);
if (len > 0) {
workerConfigs[i].tasks.push({
runner: runner.kind(),
files: files.slice(startPos, startPos + len)
});
}
}
}
for (let i = 0; i < workerCount; i++) {
const config = workerConfigs[i];
// use last worker to run unit tests
config.runUnitTests = i === workerCount - 1;
Harness.IO.writeFile(ts.combinePaths(taskConfigsFolder, `task-config${i}.json`), JSON.stringify(workerConfigs[i]));
}
}
else {
runTests(runners);
}
if (!runUnitTests) {
// patch `describe` to skip unit tests
describe = ts.noop as any;
}
+18 -17
View File
@@ -1,6 +1,11 @@
/// <reference path="harness.ts" />
class RunnerBase {
type TestRunnerKind = CompilerTestKind | FourslashTestKind | "project" | "rwc" | "test262";
type CompilerTestKind = "conformance" | "compiler";
type FourslashTestKind = "fourslash" | "fourslash-shims" | "fourslash-shims-pp" | "fourslash-server";
abstract class RunnerBase {
constructor() { }
// contains the tests to run
@@ -12,30 +17,26 @@ class RunnerBase {
}
public enumerateFiles(folder: string, regex?: RegExp, options?: { recursive: boolean }): string[] {
return Harness.IO.listFiles(Harness.userSpecifiedRoot + folder, regex, { recursive: (options ? options.recursive : false) });
return ts.map(Harness.IO.listFiles(Harness.userSpecifiedRoot + folder, regex, { recursive: (options ? options.recursive : false) }), ts.normalizeSlashes);
}
/** Setup the runner's tests so that they are ready to be executed by the harness
abstract kind(): TestRunnerKind;
abstract enumerateTestFiles(): string[];
/** Setup the runner's tests so that they are ready to be executed by the harness
* The first test should be a describe/it block that sets up the harness's compiler instance appropriately
*/
public initializeTests(): void {
throw new Error('method not implemented');
}
public abstract initializeTests(): void;
/** Replaces instances of full paths with fileNames only */
static removeFullPaths(path: string) {
var fixedPath = path;
// If its a full path (starts with "C:" or "/") replace with just the filename
let fixedPath = /^(\w:|\/)/.test(path) ? ts.getBaseFileName(path) : path;
// full paths either start with a drive letter or / for *nix, shouldn't have \ in the path at this point
var fullPath = /(\w+:|\/)?([\w+\-\.]|\/)*\.tsx?/g;
var fullPathList = fixedPath.match(fullPath);
if (fullPathList) {
fullPathList.forEach((match: string) => fixedPath = fixedPath.replace(match, Harness.Path.getFileName(match)));
}
// when running in the browser the 'full path' is the host name, shows up in error baselines
var localHost = /http:\/localhost:\d+/g;
fixedPath = fixedPath.replace(localHost, '');
const localHost = /http:\/localhost:\d+/g;
fixedPath = fixedPath.replace(localHost, "");
return fixedPath;
}
}
}
+126 -80
View File
@@ -1,47 +1,51 @@
/// <reference path='harness.ts'/>
/// <reference path='runnerbase.ts' />
/// <reference path='loggedIO.ts' />
/// <reference path='..\compiler\commandLineParser.ts'/>
/// <reference path="harness.ts"/>
/// <reference path="runnerbase.ts" />
/// <reference path="loggedIO.ts" />
/// <reference path="..\compiler\commandLineParser.ts"/>
// In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`.
/* tslint:disable:no-null-keyword */
module RWC {
function runWithIOLog(ioLog: IOLog, fn: () => void) {
var oldSys = ts.sys;
namespace RWC {
function runWithIOLog(ioLog: IOLog, fn: (oldIO: Harness.IO) => void) {
const oldIO = Harness.IO;
var wrappedSys = Playback.wrapSystem(ts.sys);
wrappedSys.startReplayFromData(ioLog);
ts.sys = wrappedSys;
const wrappedIO = Playback.wrapIO(oldIO);
wrappedIO.startReplayFromData(ioLog);
Harness.IO = wrappedIO;
try {
fn();
fn(oldIO);
} finally {
wrappedSys.endReplay();
ts.sys = oldSys;
wrappedIO.endReplay();
Harness.IO = oldIO;
}
}
function isTsConfigFile(file: { path: string }): boolean {
const tsConfigFileName = "tsconfig.json";
return file.path.substr(file.path.length - tsConfigFileName.length).toLowerCase() === tsConfigFileName;
}
export function runRWCTest(jsonPath: string) {
describe("Testing a RWC project: " + jsonPath, () => {
var inputFiles: { unitName: string; content: string; }[] = [];
var otherFiles: { unitName: string; content: string; }[] = [];
var compilerResult: Harness.Compiler.CompilerResult;
var compilerOptions: ts.CompilerOptions;
var baselineOpts: Harness.Baseline.BaselineOptions = {
Subfolder: 'rwc',
Baselinefolder: 'internal/baselines'
let inputFiles: Harness.Compiler.TestFile[] = [];
let otherFiles: Harness.Compiler.TestFile[] = [];
let compilerResult: Harness.Compiler.CompilerResult;
let compilerOptions: ts.CompilerOptions;
const baselineOpts: Harness.Baseline.BaselineOptions = {
Subfolder: "rwc",
Baselinefolder: "internal/baselines"
};
var baseName = /(.*)\/(.*).json/.exec(ts.normalizeSlashes(jsonPath))[2];
var currentDirectory: string;
var useCustomLibraryFile: boolean;
const baseName = /(.*)\/(.*).json/.exec(ts.normalizeSlashes(jsonPath))[2];
let currentDirectory: string;
let useCustomLibraryFile: boolean;
after(() => {
// Mocha holds onto the closure environment of the describe callback even after the test is done.
// Therefore we have to clean out large objects after the test is done.
inputFiles = undefined;
otherFiles = undefined;
inputFiles = [];
otherFiles = [];
compilerResult = undefined;
compilerOptions = undefined;
baselineOpts = undefined;
baseName = undefined;
currentDirectory = undefined;
// useCustomLibraryFile is a flag specified in the json object to indicate whether to use built/local/lib.d.ts
// or to use lib.d.ts inside the json object. If the flag is true, use the lib.d.ts inside json file
@@ -49,15 +53,14 @@ module RWC {
useCustomLibraryFile = undefined;
});
it('can compile', () => {
var harnessCompiler = Harness.Compiler.getCompiler();
var opts: ts.ParsedCommandLine;
it("can compile", () => {
let opts: ts.ParsedCommandLine;
var ioLog: IOLog = JSON.parse(Harness.IO.readFile(jsonPath));
const ioLog: IOLog = JSON.parse(Harness.IO.readFile(jsonPath));
currentDirectory = ioLog.currentDirectory;
useCustomLibraryFile = ioLog.useCustomLibraryFile;
runWithIOLog(ioLog, () => {
opts = ts.parseCommandLine(ioLog.arguments);
opts = ts.parseCommandLine(ioLog.arguments, fileName => Harness.IO.readFile(fileName));
assert.equal(opts.errors.length, 0);
// To provide test coverage of output javascript file,
@@ -65,27 +68,47 @@ module RWC {
opts.options.noEmitOnError = false;
});
runWithIOLog(ioLog, () => {
harnessCompiler.reset();
runWithIOLog(ioLog, oldIO => {
let fileNames = opts.fileNames;
const tsconfigFile = ts.forEach(ioLog.filesRead, f => isTsConfigFile(f) ? f : undefined);
if (tsconfigFile) {
const tsconfigFileContents = getHarnessCompilerInputUnit(tsconfigFile.path);
const parsedTsconfigFileContents = ts.parseConfigFileTextToJson(tsconfigFile.path, tsconfigFileContents.content);
const configParseHost: ts.ParseConfigHost = {
useCaseSensitiveFileNames: Harness.IO.useCaseSensitiveFileNames(),
fileExists: Harness.IO.fileExists,
readDirectory: Harness.IO.readDirectory,
readFile: Harness.IO.readFile
};
const configParseResult = ts.parseJsonConfigFileContent(parsedTsconfigFileContents.config, configParseHost, ts.getDirectoryPath(tsconfigFile.path));
fileNames = configParseResult.fileNames;
opts.options = ts.extend(opts.options, configParseResult.options);
}
// Load the files
ts.forEach(opts.fileNames, fileName => {
for (const fileName of fileNames) {
inputFiles.push(getHarnessCompilerInputUnit(fileName));
});
}
// Add files to compilation
for (let fileRead of ioLog.filesRead) {
const isInInputList = (resolvedPath: string) => (inputFile: { unitName: string; content: string; }) => inputFile.unitName === resolvedPath;
for (const fileRead of ioLog.filesRead) {
// Check if the file is already added into the set of input files.
var resolvedPath = ts.normalizeSlashes(ts.sys.resolvePath(fileRead.path));
var inInputList = ts.forEach(inputFiles, inputFile => inputFile.unitName === resolvedPath);
const resolvedPath = ts.normalizeSlashes(Harness.IO.resolvePath(fileRead.path));
const inInputList = ts.forEach(inputFiles, isInInputList(resolvedPath));
if (!Harness.isLibraryFile(fileRead.path)) {
if (isTsConfigFile(fileRead)) {
continue;
}
if (!Harness.isDefaultLibraryFile(fileRead.path)) {
if (inInputList) {
continue;
}
otherFiles.push(getHarnessCompilerInputUnit(fileRead.path));
}
else if (!opts.options.noLib && Harness.isLibraryFile(fileRead.path)){
else if (!opts.options.noLib && Harness.isDefaultLibraryFile(fileRead.path)) {
if (!inInputList) {
// If useCustomLibraryFile is true, we will use lib.d.ts from json object
// otherwise use the lib.d.ts from built/local
@@ -96,7 +119,8 @@ module RWC {
inputFiles.push(getHarnessCompilerInputUnit(fileRead.path));
}
else {
inputFiles.push(Harness.getDefaultLibraryFile());
// set the flag to put default library to the beginning of the list
inputFiles.unshift(Harness.getDefaultLibraryFile(oldIO));
}
}
}
@@ -106,92 +130,106 @@ module RWC {
opts.options.noLib = true;
// Emit the results
compilerOptions = harnessCompiler.compileFiles(
compilerOptions = undefined;
const output = Harness.Compiler.compileFiles(
inputFiles,
otherFiles,
newCompilerResults => { compilerResult = newCompilerResults; },
/*settingsCallback*/ undefined, opts.options,
/* harnessOptions */ undefined,
opts.options,
// Since each RWC json file specifies its current directory in its json file, we need
// to pass this information in explicitly instead of acquiring it from the process.
currentDirectory);
compilerOptions = output.options;
compilerResult = output.result;
});
function getHarnessCompilerInputUnit(fileName: string) {
var unitName = ts.normalizeSlashes(ts.sys.resolvePath(fileName));
function getHarnessCompilerInputUnit(fileName: string): Harness.Compiler.TestFile {
const unitName = ts.normalizeSlashes(Harness.IO.resolvePath(fileName));
let content: string;
try {
var content = ts.sys.readFile(unitName);
content = Harness.IO.readFile(unitName);
}
catch (e) {
// Leave content undefined.
content = Harness.IO.readFile(fileName);
}
return { unitName, content };
}
});
it('has the expected emitted code', () => {
Harness.Baseline.runBaseline('has the expected emitted code', baseName + '.output.js', () => {
it("has the expected emitted code", () => {
Harness.Baseline.runBaseline(`${baseName}.output.js`, () => {
return Harness.Compiler.collateOutputs(compilerResult.files);
}, false, baselineOpts);
}, baselineOpts);
});
it('has the expected declaration file content', () => {
Harness.Baseline.runBaseline('has the expected declaration file content', baseName + '.d.ts', () => {
it("has the expected declaration file content", () => {
Harness.Baseline.runBaseline(`${baseName}.d.ts`, () => {
if (!compilerResult.declFilesCode.length) {
return null;
}
return Harness.Compiler.collateOutputs(compilerResult.declFilesCode);
}, false, baselineOpts);
}, baselineOpts);
});
it('has the expected source maps', () => {
Harness.Baseline.runBaseline('has the expected source maps', baseName + '.map', () => {
it("has the expected source maps", () => {
Harness.Baseline.runBaseline(`${baseName}.map`, () => {
if (!compilerResult.sourceMaps.length) {
return null;
}
return Harness.Compiler.collateOutputs(compilerResult.sourceMaps);
}, false, baselineOpts);
}, baselineOpts);
});
//it('has correct source map record', () => {
// if (compilerOptions.sourceMap) {
// Harness.Baseline.runBaseline('has correct source map record', baseName + '.sourcemap.txt', () => {
// return compilerResult.getSourceMapRecord();
// }, false, baselineOpts);
// }
//});
/*it("has correct source map record", () => {
if (compilerOptions.sourceMap) {
Harness.Baseline.runBaseline(baseName + ".sourcemap.txt", () => {
return compilerResult.getSourceMapRecord();
}, baselineOpts);
}
});*/
it('has the expected errors', () => {
Harness.Baseline.runBaseline('has the expected errors', baseName + '.errors.txt', () => {
it("has the expected errors", () => {
Harness.Baseline.runBaseline(`${baseName}.errors.txt`, () => {
if (compilerResult.errors.length === 0) {
return null;
}
return Harness.Compiler.getErrorBaseline(inputFiles.concat(otherFiles), compilerResult.errors);
}, false, baselineOpts);
// Do not include the library in the baselines to avoid noise
const baselineFiles = inputFiles.concat(otherFiles).filter(f => !Harness.isDefaultLibraryFile(f.unitName));
const errors = compilerResult.errors.filter(e => e.file && !Harness.isDefaultLibraryFile(e.file.fileName));
return Harness.Compiler.getErrorBaseline(baselineFiles, errors);
}, baselineOpts);
});
// Ideally, a generated declaration file will have no errors. But we allow generated
// declaration file errors as part of the baseline.
it('has the expected errors in generated declaration files', () => {
it("has the expected errors in generated declaration files", () => {
if (compilerOptions.declaration && !compilerResult.errors.length) {
Harness.Baseline.runBaseline('has the expected errors in generated declaration files', baseName + '.dts.errors.txt', () => {
var declFileCompilationResult = Harness.Compiler.getCompiler().compileDeclarationFiles(inputFiles, otherFiles, compilerResult,
/*settingscallback*/ undefined, compilerOptions, currentDirectory);
Harness.Baseline.runBaseline(`${baseName}.dts.errors.txt`, () => {
const declFileCompilationResult = Harness.Compiler.compileDeclarationFiles(
inputFiles, otherFiles, compilerResult, /*harnessSettings*/ undefined, compilerOptions, currentDirectory);
if (declFileCompilationResult.declResult.errors.length === 0) {
return null;
}
return Harness.Compiler.minimalDiagnosticsToString(declFileCompilationResult.declResult.errors) +
ts.sys.newLine + ts.sys.newLine +
Harness.IO.newLine() + Harness.IO.newLine() +
Harness.Compiler.getErrorBaseline(declFileCompilationResult.declInputFiles.concat(declFileCompilationResult.declOtherFiles), declFileCompilationResult.declResult.errors);
}, false, baselineOpts);
}, baselineOpts);
}
});
// TODO: Type baselines (need to refactor out from compilerRunner)
it("has the expected types", () => {
// We don't need to pass the extension here because "doTypeAndSymbolBaseline" will append appropriate extension of ".types" or ".symbols"
Harness.Compiler.doTypeAndSymbolBaseline(baseName, compilerResult, inputFiles
.concat(otherFiles)
.filter(file => !!compilerResult.program.getSourceFile(file.unitName))
.filter(e => !Harness.isDefaultLibraryFile(e.unitName)), baselineOpts);
});
});
}
}
@@ -199,13 +237,21 @@ module RWC {
class RWCRunner extends RunnerBase {
private static sourcePath = "internal/cases/rwc/";
public enumerateTestFiles() {
return Harness.IO.listFiles(RWCRunner.sourcePath, /.+\.json$/);
}
public kind(): TestRunnerKind {
return "rwc";
}
/** Setup the runner's tests so that they are ready to be executed by the harness
* The first test should be a describe/it block that sets up the harness's compiler instance appropriately
*/
public initializeTests(): void {
// Read in and evaluate the test list
var testList = Harness.IO.listFiles(RWCRunner.sourcePath, /.+\.json$/);
for (var i = 0; i < testList.length; i++) {
const testList = this.enumerateTestFiles();
for (let i = 0; i < testList.length; i++) {
this.runTest(testList[i]);
}
}
+94 -94
View File
@@ -1,6 +1,6 @@
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
@@ -13,22 +13,22 @@
// limitations under the License.
//
///<reference path='harness.ts'/>
///<reference path="harness.ts"/>
module Harness.SourceMapRecoder {
namespace Harness.SourceMapRecorder {
interface SourceMapSpanWithDecodeErrors {
sourceMapSpan: ts.SourceMapSpan;
decodeErrors: string[];
}
module SourceMapDecoder {
var sourceMapMappings: string;
var sourceMapNames: string[];
var decodingIndex: number;
var prevNameIndex: number;
var decodeOfEncodedMapping: ts.SourceMapSpan;
var errorDecodeOfEncodedMapping: string;
namespace SourceMapDecoder {
let sourceMapMappings: string;
let sourceMapNames: string[];
let decodingIndex: number;
let prevNameIndex: number;
let decodeOfEncodedMapping: ts.SourceMapSpan;
let errorDecodeOfEncodedMapping: string;
export function initializeSourceMapDecoding(sourceMapData: ts.SourceMapData) {
sourceMapMappings = sourceMapData.sourceMapMappings;
@@ -50,11 +50,11 @@ module Harness.SourceMapRecoder {
return true;
}
if (sourceMapMappings.charAt(decodingIndex) == ',') {
if (sourceMapMappings.charAt(decodingIndex) === ",") {
return true;
}
if (sourceMapMappings.charAt(decodingIndex) == ';') {
if (sourceMapMappings.charAt(decodingIndex) === ";") {
return true;
}
@@ -82,9 +82,9 @@ module Harness.SourceMapRecoder {
return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(sourceMapMappings.charAt(decodingIndex));
}
var moreDigits = true;
var shiftCount = 0;
var value = 0;
let moreDigits = true;
let shiftCount = 0;
let value = 0;
for (; moreDigits; decodingIndex++) {
if (createErrorIfCondition(decodingIndex >= sourceMapMappings.length, "Error in decoding base64VLQFormatDecode, past the mapping string")) {
@@ -92,7 +92,7 @@ module Harness.SourceMapRecoder {
}
// 6 digit number
var currentByte = base64FormatDecode();
const currentByte = base64FormatDecode();
// If msb is set, we still have more bits to continue
moreDigits = (currentByte & 32) !== 0;
@@ -117,7 +117,7 @@ module Harness.SourceMapRecoder {
}
while (decodingIndex < sourceMapMappings.length) {
if (sourceMapMappings.charAt(decodingIndex) == ';') {
if (sourceMapMappings.charAt(decodingIndex) === ";") {
// New line
decodeOfEncodedMapping.emittedLine++;
decodeOfEncodedMapping.emittedColumn = 1;
@@ -125,7 +125,7 @@ module Harness.SourceMapRecoder {
continue;
}
if (sourceMapMappings.charAt(decodingIndex) == ',') {
if (sourceMapMappings.charAt(decodingIndex) === ",") {
// Next entry is on same line - no action needed
decodingIndex++;
continue;
@@ -143,7 +143,7 @@ module Harness.SourceMapRecoder {
return { error: errorDecodeOfEncodedMapping, sourceMapSpan: decodeOfEncodedMapping };
}
// 2. Relative sourceIndex
// 2. Relative sourceIndex
decodeOfEncodedMapping.sourceIndex += base64VLQFormatDecode();
// Incorrect sourceIndex dont support this map
if (createErrorIfCondition(decodeOfEncodedMapping.sourceIndex < 0, "Invalid sourceIndex found")) {
@@ -165,14 +165,13 @@ module Harness.SourceMapRecoder {
return { error: errorDecodeOfEncodedMapping, sourceMapSpan: decodeOfEncodedMapping };
}
// 4. Relative sourceColumn 0 based
// 4. Relative sourceColumn 0 based
decodeOfEncodedMapping.sourceColumn += base64VLQFormatDecode();
// Incorrect sourceColumn dont support this map
if (createErrorIfCondition(decodeOfEncodedMapping.sourceColumn < 1, "Invalid sourceLine found")) {
return { error: errorDecodeOfEncodedMapping, sourceMapSpan: decodeOfEncodedMapping };
}
// 5. Check if there is name:
decodeOfEncodedMapping.nameIndex = -1;
if (!isSourceMappingSegmentEnd()) {
prevNameIndex += base64VLQFormatDecode();
decodeOfEncodedMapping.nameIndex = prevNameIndex;
@@ -190,7 +189,7 @@ module Harness.SourceMapRecoder {
return { error: errorDecodeOfEncodedMapping, sourceMapSpan: decodeOfEncodedMapping };
}
createErrorIfCondition(true, "No encoded entry found");
createErrorIfCondition(/*condition*/ true, "No encoded entry found");
}
export function hasCompletedDecoding() {
@@ -202,23 +201,23 @@ module Harness.SourceMapRecoder {
}
}
module SourceMapSpanWriter {
var sourceMapRecoder: Compiler.WriterAggregator;
var sourceMapSources: string[];
var sourceMapNames: string[];
namespace SourceMapSpanWriter {
let sourceMapRecorder: Compiler.WriterAggregator;
let sourceMapSources: string[];
let sourceMapNames: string[];
var jsFile: Compiler.GeneratedFile;
var jsLineMap: number[];
var tsCode: string;
var tsLineMap: number[];
let jsFile: Compiler.GeneratedFile;
let jsLineMap: number[];
let tsCode: string;
let tsLineMap: number[];
var spansOnSingleLine: SourceMapSpanWithDecodeErrors[];
var prevWrittenSourcePos: number;
var prevWrittenJsLine: number;
var spanMarkerContinues: boolean;
let spansOnSingleLine: SourceMapSpanWithDecodeErrors[];
let prevWrittenSourcePos: number;
let prevWrittenJsLine: number;
let spanMarkerContinues: boolean;
export function intializeSourceMapSpanWriter(sourceMapRecordWriter: Compiler.WriterAggregator, sourceMapData: ts.SourceMapData, currentJsFile: Compiler.GeneratedFile) {
sourceMapRecoder = sourceMapRecordWriter;
export function initializeSourceMapSpanWriter(sourceMapRecordWriter: Compiler.WriterAggregator, sourceMapData: ts.SourceMapData, currentJsFile: Compiler.GeneratedFile) {
sourceMapRecorder = sourceMapRecordWriter;
sourceMapSources = sourceMapData.sourceMapSources;
sourceMapNames = sourceMapData.sourceMapNames;
@@ -232,24 +231,24 @@ module Harness.SourceMapRecoder {
SourceMapDecoder.initializeSourceMapDecoding(sourceMapData);
sourceMapRecoder.WriteLine("===================================================================");
sourceMapRecoder.WriteLine("JsFile: " + sourceMapData.sourceMapFile);
sourceMapRecoder.WriteLine("mapUrl: " + sourceMapData.jsSourceMappingURL);
sourceMapRecoder.WriteLine("sourceRoot: " + sourceMapData.sourceMapSourceRoot);
sourceMapRecoder.WriteLine("sources: " + sourceMapData.sourceMapSources);
sourceMapRecorder.WriteLine("===================================================================");
sourceMapRecorder.WriteLine("JsFile: " + sourceMapData.sourceMapFile);
sourceMapRecorder.WriteLine("mapUrl: " + sourceMapData.jsSourceMappingURL);
sourceMapRecorder.WriteLine("sourceRoot: " + sourceMapData.sourceMapSourceRoot);
sourceMapRecorder.WriteLine("sources: " + sourceMapData.sourceMapSources);
if (sourceMapData.sourceMapSourcesContent) {
sourceMapRecoder.WriteLine("sourcesContent: " + JSON.stringify(sourceMapData.sourceMapSourcesContent));
sourceMapRecorder.WriteLine("sourcesContent: " + JSON.stringify(sourceMapData.sourceMapSourcesContent));
}
sourceMapRecoder.WriteLine("===================================================================");
sourceMapRecorder.WriteLine("===================================================================");
}
function getSourceMapSpanString(mapEntry: ts.SourceMapSpan, getAbsentNameIndex?: boolean) {
var mapString = "Emitted(" + mapEntry.emittedLine + ", " + mapEntry.emittedColumn + ") Source(" + mapEntry.sourceLine + ", " + mapEntry.sourceColumn + ") + SourceIndex(" + mapEntry.sourceIndex + ")";
let mapString = "Emitted(" + mapEntry.emittedLine + ", " + mapEntry.emittedColumn + ") Source(" + mapEntry.sourceLine + ", " + mapEntry.sourceColumn + ") + SourceIndex(" + mapEntry.sourceIndex + ")";
if (mapEntry.nameIndex >= 0 && mapEntry.nameIndex < sourceMapNames.length) {
mapString += " name (" + sourceMapNames[mapEntry.nameIndex] + ")";
}
else {
if (mapEntry.nameIndex !== -1 || getAbsentNameIndex) {
if ((mapEntry.nameIndex && mapEntry.nameIndex !== -1) || getAbsentNameIndex) {
mapString += " nameIndex (" + mapEntry.nameIndex + ")";
}
}
@@ -259,8 +258,8 @@ module Harness.SourceMapRecoder {
export function recordSourceMapSpan(sourceMapSpan: ts.SourceMapSpan) {
// verify the decoded span is same as the new span
var decodeResult = SourceMapDecoder.decodeNextEncodedSourceMapSpan();
var decodedErrors: string[];
const decodeResult = SourceMapDecoder.decodeNextEncodedSourceMapSpan();
let decodedErrors: string[];
if (decodeResult.error
|| decodeResult.sourceMapSpan.emittedLine !== sourceMapSpan.emittedLine
|| decodeResult.sourceMapSpan.emittedColumn !== sourceMapSpan.emittedColumn
@@ -278,7 +277,7 @@ module Harness.SourceMapRecoder {
}
if (spansOnSingleLine.length && spansOnSingleLine[0].sourceMapSpan.emittedLine !== sourceMapSpan.emittedLine) {
// On different line from the one that we have been recording till now,
// On different line from the one that we have been recording till now,
writeRecordedSpans();
spansOnSingleLine = [{ sourceMapSpan: sourceMapSpan, decodeErrors: decodedErrors }];
}
@@ -292,10 +291,10 @@ module Harness.SourceMapRecoder {
recordSourceMapSpan(sourceMapSpan);
assert.isTrue(spansOnSingleLine.length === 1);
sourceMapRecoder.WriteLine("-------------------------------------------------------------------");
sourceMapRecoder.WriteLine("emittedFile:" + jsFile.fileName);
sourceMapRecoder.WriteLine("sourceFile:" + sourceMapSources[spansOnSingleLine[0].sourceMapSpan.sourceIndex]);
sourceMapRecoder.WriteLine("-------------------------------------------------------------------");
sourceMapRecorder.WriteLine("-------------------------------------------------------------------");
sourceMapRecorder.WriteLine("emittedFile:" + jsFile.fileName);
sourceMapRecorder.WriteLine("sourceFile:" + sourceMapSources[spansOnSingleLine[0].sourceMapSpan.sourceIndex]);
sourceMapRecorder.WriteLine("-------------------------------------------------------------------");
tsLineMap = ts.computeLineStarts(newSourceFileCode);
tsCode = newSourceFileCode;
@@ -307,8 +306,8 @@ module Harness.SourceMapRecoder {
writeRecordedSpans();
if (!SourceMapDecoder.hasCompletedDecoding()) {
sourceMapRecoder.WriteLine("!!!! **** There are more source map entries in the sourceMap's mapping than what was encoded");
sourceMapRecoder.WriteLine("!!!! **** Remaining decoded string: " + SourceMapDecoder.getRemainingDecodeString());
sourceMapRecorder.WriteLine("!!!! **** There are more source map entries in the sourceMap's mapping than what was encoded");
sourceMapRecorder.WriteLine("!!!! **** Remaining decoded string: " + SourceMapDecoder.getRemainingDecodeString());
}
@@ -317,26 +316,28 @@ module Harness.SourceMapRecoder {
}
function getTextOfLine(line: number, lineMap: number[], code: string) {
var startPos = lineMap[line];
var endPos = lineMap[line + 1];
const startPos = lineMap[line];
const endPos = lineMap[line + 1];
return code.substring(startPos, endPos);
}
function writeJsFileLines(endJsLine: number) {
for (; prevWrittenJsLine < endJsLine; prevWrittenJsLine++) {
sourceMapRecoder.Write(">>>" + getTextOfLine(prevWrittenJsLine, jsLineMap, jsFile.code));
sourceMapRecorder.Write(">>>" + getTextOfLine(prevWrittenJsLine, jsLineMap, jsFile.code));
}
}
function writeRecordedSpans() {
const markerIds: string[] = [];
function getMarkerId(markerIndex: number) {
var markerId = "";
let markerId = "";
if (spanMarkerContinues) {
assert.isTrue(markerIndex === 0);
markerId = "1->";
}
else {
var markerId = "" + (markerIndex + 1);
markerId = "" + (markerIndex + 1);
if (markerId.length < 2) {
markerId = markerId + " ";
}
@@ -345,41 +346,41 @@ module Harness.SourceMapRecoder {
return markerId;
}
var prevEmittedCol: number;
let prevEmittedCol: number;
function iterateSpans(fn: (currentSpan: SourceMapSpanWithDecodeErrors, index: number) => void) {
prevEmittedCol = 1;
for (var i = 0; i < spansOnSingleLine.length; i++) {
for (let i = 0; i < spansOnSingleLine.length; i++) {
fn(spansOnSingleLine[i], i);
prevEmittedCol = spansOnSingleLine[i].sourceMapSpan.emittedColumn;
}
}
function writeSourceMapIndent(indentLength: number, indentPrefix: string) {
sourceMapRecoder.Write(indentPrefix);
for (var i = 1; i < indentLength; i++) {
sourceMapRecoder.Write(" ");
sourceMapRecorder.Write(indentPrefix);
for (let i = 1; i < indentLength; i++) {
sourceMapRecorder.Write(" ");
}
}
function writeSourceMapMarker(currentSpan: SourceMapSpanWithDecodeErrors, index: number, endColumn = currentSpan.sourceMapSpan.emittedColumn, endContinues?: boolean) {
var markerId = getMarkerId(index);
const markerId = getMarkerId(index);
markerIds.push(markerId);
writeSourceMapIndent(prevEmittedCol, markerId);
for (var i = prevEmittedCol; i < endColumn; i++) {
sourceMapRecoder.Write("^");
for (let i = prevEmittedCol; i < endColumn; i++) {
sourceMapRecorder.Write("^");
}
if (endContinues) {
sourceMapRecoder.Write("->");
sourceMapRecorder.Write("->");
}
sourceMapRecoder.WriteLine("");
sourceMapRecorder.WriteLine("");
spanMarkerContinues = endContinues;
}
function writeSourceMapSourceText(currentSpan: SourceMapSpanWithDecodeErrors, index: number) {
var sourcePos = tsLineMap[currentSpan.sourceMapSpan.sourceLine - 1] + (currentSpan.sourceMapSpan.sourceColumn - 1);
var sourceText = "";
const sourcePos = tsLineMap[currentSpan.sourceMapSpan.sourceLine - 1] + (currentSpan.sourceMapSpan.sourceColumn - 1);
let sourceText = "";
if (prevWrittenSourcePos < sourcePos) {
// Position that goes forward, get text
sourceText = tsCode.substring(prevWrittenSourcePos, sourcePos);
@@ -387,18 +388,18 @@ module Harness.SourceMapRecoder {
if (currentSpan.decodeErrors) {
// If there are decode errors, write
for (var i = 0; i < currentSpan.decodeErrors.length; i++) {
for (let i = 0; i < currentSpan.decodeErrors.length; i++) {
writeSourceMapIndent(prevEmittedCol, markerIds[index]);
sourceMapRecoder.WriteLine(currentSpan.decodeErrors[i]);
sourceMapRecorder.WriteLine(currentSpan.decodeErrors[i]);
}
}
var tsCodeLineMap = ts.computeLineStarts(sourceText);
for (var i = 0; i < tsCodeLineMap.length; i++) {
const tsCodeLineMap = ts.computeLineStarts(sourceText);
for (let i = 0; i < tsCodeLineMap.length; i++) {
writeSourceMapIndent(prevEmittedCol, i === 0 ? markerIds[index] : " >");
sourceMapRecoder.Write(getTextOfLine(i, tsCodeLineMap, sourceText));
sourceMapRecorder.Write(getTextOfLine(i, tsCodeLineMap, sourceText));
if (i === tsCodeLineMap.length - 1) {
sourceMapRecoder.WriteLine("");
sourceMapRecorder.WriteLine("");
}
}
@@ -406,23 +407,22 @@ module Harness.SourceMapRecoder {
}
function writeSpanDetails(currentSpan: SourceMapSpanWithDecodeErrors, index: number) {
sourceMapRecoder.WriteLine(markerIds[index] + getSourceMapSpanString(currentSpan.sourceMapSpan));
sourceMapRecorder.WriteLine(markerIds[index] + getSourceMapSpanString(currentSpan.sourceMapSpan));
}
if (spansOnSingleLine.length) {
var currentJsLine = spansOnSingleLine[0].sourceMapSpan.emittedLine;
const currentJsLine = spansOnSingleLine[0].sourceMapSpan.emittedLine;
// Write js line
writeJsFileLines(currentJsLine);
// Emit markers
var markerIds: string[] = [];
iterateSpans(writeSourceMapMarker);
var jsFileText = getTextOfLine(currentJsLine, jsLineMap, jsFile.code);
const jsFileText = getTextOfLine(currentJsLine, jsLineMap, jsFile.code);
if (prevEmittedCol < jsFileText.length) {
// There is remaining text on this line that will be part of next source span so write marker that continues
writeSourceMapMarker(undefined, spansOnSingleLine.length, /*endColumn*/ jsFileText.length, /*endContinues*/ true);
writeSourceMapMarker(/*currentSpan*/ undefined, spansOnSingleLine.length, /*endColumn*/ jsFileText.length, /*endContinues*/ true);
}
// Emit Source text
@@ -431,22 +431,22 @@ module Harness.SourceMapRecoder {
// Emit column number etc
iterateSpans(writeSpanDetails);
sourceMapRecoder.WriteLine("---");
sourceMapRecorder.WriteLine("---");
}
}
}
export function getSourceMapRecord(sourceMapDataList: ts.SourceMapData[], program: ts.Program, jsFiles: Compiler.GeneratedFile[]) {
var sourceMapRecoder = new Compiler.WriterAggregator();
const sourceMapRecorder = new Compiler.WriterAggregator();
for (var i = 0; i < sourceMapDataList.length; i++) {
var sourceMapData = sourceMapDataList[i];
var prevSourceFile: ts.SourceFile = null;
for (let i = 0; i < sourceMapDataList.length; i++) {
const sourceMapData = sourceMapDataList[i];
let prevSourceFile: ts.SourceFile;
SourceMapSpanWriter.intializeSourceMapSpanWriter(sourceMapRecoder, sourceMapData, jsFiles[i]);
for (var j = 0; j < sourceMapData.sourceMapDecodedMappings.length; j++) {
var decodedSourceMapping = sourceMapData.sourceMapDecodedMappings[j];
var currentSourceFile = program.getSourceFile(sourceMapData.inputSourceFileNames[decodedSourceMapping.sourceIndex]);
SourceMapSpanWriter.initializeSourceMapSpanWriter(sourceMapRecorder, sourceMapData, jsFiles[i]);
for (let j = 0; j < sourceMapData.sourceMapDecodedMappings.length; j++) {
const decodedSourceMapping = sourceMapData.sourceMapDecodedMappings[j];
const currentSourceFile = program.getSourceFile(sourceMapData.inputSourceFileNames[decodedSourceMapping.sourceIndex]);
if (currentSourceFile !== prevSourceFile) {
SourceMapSpanWriter.recordNewSourceFileSpan(decodedSourceMapping, currentSourceFile.text);
prevSourceFile = currentSourceFile;
@@ -455,9 +455,9 @@ module Harness.SourceMapRecoder {
SourceMapSpanWriter.recordSourceMapSpan(decodedSourceMapping);
}
}
SourceMapSpanWriter.close();// If the last spans werent emitted, emit them
SourceMapSpanWriter.close(); // If the last spans werent emitted, emit them
}
sourceMapRecoder.Close();
return sourceMapRecoder.lines.join('\r\n');
sourceMapRecorder.Close();
return sourceMapRecorder.lines.join("\r\n");
}
}
+52 -39
View File
@@ -1,12 +1,14 @@
/// <reference path='harness.ts' />
/// <reference path='runnerbase.ts' />
/// <reference path="harness.ts" />
/// <reference path="runnerbase.ts" />
// In harness baselines, null is different than undefined. See `generateActual` in `harness.ts`.
/* tslint:disable:no-null-keyword */
class Test262BaselineRunner extends RunnerBase {
private static basePath = 'internal/cases/test262';
private static helpersFilePath = 'tests/cases/test262-harness/helpers.d.ts';
private static helperFile = {
private static basePath = "internal/cases/test262";
private static helpersFilePath = "tests/cases/test262-harness/helpers.d.ts";
private static helperFile: Harness.Compiler.TestFile = {
unitName: Test262BaselineRunner.helpersFilePath,
content: Harness.IO.readFile(Test262BaselineRunner.helpersFilePath)
content: Harness.IO.readFile(Test262BaselineRunner.helpersFilePath),
};
private static testFileExtensionRegex = /\.js$/;
private static options: ts.CompilerOptions = {
@@ -15,8 +17,8 @@ class Test262BaselineRunner extends RunnerBase {
module: ts.ModuleKind.CommonJS
};
private static baselineOptions: Harness.Baseline.BaselineOptions = {
Subfolder: 'test262',
Baselinefolder: 'internal/baselines'
Subfolder: "test262",
Baselinefolder: "internal/baselines"
};
private static getTestFilePath(filename: string): string {
@@ -24,23 +26,23 @@ class Test262BaselineRunner extends RunnerBase {
}
private runTest(filePath: string) {
describe('test262 test for ' + filePath, () => {
describe("test262 test for " + filePath, () => {
// Mocha holds onto the closure environment of the describe callback even after the test is done.
// Everything declared here should be cleared out in the "after" callback.
var testState: {
let testState: {
filename: string;
compilerResult: Harness.Compiler.CompilerResult;
inputFiles: { unitName: string; content: string }[];
program: ts.Program;
inputFiles: Harness.Compiler.TestFile[];
};
before(() => {
var content = Harness.IO.readFile(filePath);
var testFilename = ts.removeFileExtension(filePath).replace(/\//g, '_') + ".test";
var testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(content, testFilename);
const content = Harness.IO.readFile(filePath);
const testFilename = ts.removeFileExtension(filePath).replace(/\//g, "_") + ".test";
const testCaseContent = Harness.TestCaseParser.makeUnitsFromTest(content, testFilename);
var inputFiles = testCaseContent.testUnitData.map(unit => {
return { unitName: Test262BaselineRunner.getTestFilePath(unit.name), content: unit.content };
const inputFiles: Harness.Compiler.TestFile[] = testCaseContent.testUnitData.map(unit => {
const unitName = Test262BaselineRunner.getTestFilePath(unit.name);
return { unitName, content: unit.content };
});
// Emit the results
@@ -48,61 +50,72 @@ class Test262BaselineRunner extends RunnerBase {
filename: testFilename,
inputFiles: inputFiles,
compilerResult: undefined,
program: undefined,
};
Harness.Compiler.getCompiler().compileFiles([Test262BaselineRunner.helperFile].concat(inputFiles), /*otherFiles*/ [], (compilerResult, program) => {
testState.compilerResult = compilerResult;
testState.program = program;
}, /*settingsCallback*/ undefined, Test262BaselineRunner.options);
const output = Harness.Compiler.compileFiles(
[Test262BaselineRunner.helperFile].concat(inputFiles),
/*otherFiles*/ [],
/* harnessOptions */ undefined,
Test262BaselineRunner.options,
/* currentDirectory */ undefined
);
testState.compilerResult = output.result;
});
after(() => {
testState = undefined;
});
it('has the expected emitted code', () => {
Harness.Baseline.runBaseline('has the expected emitted code', testState.filename + '.output.js', () => {
var files = testState.compilerResult.files.filter(f=> f.fileName !== Test262BaselineRunner.helpersFilePath);
it("has the expected emitted code", () => {
Harness.Baseline.runBaseline(testState.filename + ".output.js", () => {
const files = testState.compilerResult.files.filter(f => f.fileName !== Test262BaselineRunner.helpersFilePath);
return Harness.Compiler.collateOutputs(files);
}, false, Test262BaselineRunner.baselineOptions);
}, Test262BaselineRunner.baselineOptions);
});
it('has the expected errors', () => {
Harness.Baseline.runBaseline('has the expected errors', testState.filename + '.errors.txt', () => {
var errors = testState.compilerResult.errors;
it("has the expected errors", () => {
Harness.Baseline.runBaseline(testState.filename + ".errors.txt", () => {
const errors = testState.compilerResult.errors;
if (errors.length === 0) {
return null;
}
return Harness.Compiler.getErrorBaseline(testState.inputFiles, errors);
}, false, Test262BaselineRunner.baselineOptions);
}, Test262BaselineRunner.baselineOptions);
});
it('satisfies invariants', () => {
var sourceFile = testState.program.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename));
it("satisfies invariants", () => {
const sourceFile = testState.compilerResult.program.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename));
Utils.assertInvariants(sourceFile, /*parent:*/ undefined);
});
it('has the expected AST',() => {
Harness.Baseline.runBaseline('has the expected AST', testState.filename + '.AST.txt',() => {
var sourceFile = testState.program.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename));
it("has the expected AST", () => {
Harness.Baseline.runBaseline(testState.filename + ".AST.txt", () => {
const sourceFile = testState.compilerResult.program.getSourceFile(Test262BaselineRunner.getTestFilePath(testState.filename));
return Utils.sourceFileToJSON(sourceFile);
}, false, Test262BaselineRunner.baselineOptions);
}, Test262BaselineRunner.baselineOptions);
});
});
}
public kind(): TestRunnerKind {
return "test262";
}
public enumerateTestFiles() {
return ts.map(this.enumerateFiles(Test262BaselineRunner.basePath, Test262BaselineRunner.testFileExtensionRegex, { recursive: true }), ts.normalizePath);
}
public initializeTests() {
// this will set up a series of describe/it blocks to run between the setup and cleanup phases
if (this.tests.length === 0) {
var testFiles = this.enumerateFiles(Test262BaselineRunner.basePath, Test262BaselineRunner.testFileExtensionRegex, { recursive: true });
const testFiles = this.enumerateTestFiles();
testFiles.forEach(fn => {
this.runTest(ts.normalizePath(fn));
this.runTest(fn);
});
}
else {
this.tests.forEach(test => this.runTest(test));
}
}
}
}
+132
View File
@@ -0,0 +1,132 @@
{
"extends": "../tsconfig-base",
"compilerOptions": {
"removeComments": false,
"outFile": "../../built/local/run.js",
"declaration": false,
"types": [
"node", "mocha", "chai"
],
"lib": [
"es6",
"scripthost"
]
},
"files": [
"../compiler/core.ts",
"../compiler/performance.ts",
"../compiler/sys.ts",
"../compiler/types.ts",
"../compiler/scanner.ts",
"../compiler/parser.ts",
"../compiler/utilities.ts",
"../compiler/binder.ts",
"../compiler/checker.ts",
"../compiler/factory.ts",
"../compiler/visitor.ts",
"../compiler/transformers/ts.ts",
"../compiler/transformers/jsx.ts",
"../compiler/transformers/esnext.ts",
"../compiler/transformers/es2017.ts",
"../compiler/transformers/es2016.ts",
"../compiler/transformers/es2015.ts",
"../compiler/transformers/es5.ts",
"../compiler/transformers/generators.ts",
"../compiler/transformers/es5.ts",
"../compiler/transformers/destructuring.ts",
"../compiler/transformers/module/module.ts",
"../compiler/transformers/module/system.ts",
"../compiler/transformers/module/es2015.ts",
"../compiler/transformer.ts",
"../compiler/comments.ts",
"../compiler/sourcemap.ts",
"../compiler/declarationEmitter.ts",
"../compiler/emitter.ts",
"../compiler/program.ts",
"../compiler/commandLineParser.ts",
"../compiler/diagnosticInformationMap.generated.ts",
"../services/breakpoints.ts",
"../services/navigateTo.ts",
"../services/navigationBar.ts",
"../services/outliningElementsCollector.ts",
"../services/patternMatcher.ts",
"../services/services.ts",
"../services/shims.ts",
"../services/signatureHelp.ts",
"../services/utilities.ts",
"../services/jsTyping.ts",
"../services/formatting/formatting.ts",
"../services/formatting/formattingContext.ts",
"../services/formatting/formattingRequestKind.ts",
"../services/formatting/formattingScanner.ts",
"../services/formatting/references.ts",
"../services/formatting/rule.ts",
"../services/formatting/ruleAction.ts",
"../services/formatting/ruleDescriptor.ts",
"../services/formatting/ruleFlag.ts",
"../services/formatting/ruleOperation.ts",
"../services/formatting/ruleOperationContext.ts",
"../services/formatting/rules.ts",
"../services/formatting/rulesMap.ts",
"../services/formatting/rulesProvider.ts",
"../services/formatting/smartIndenter.ts",
"../services/formatting/tokenRange.ts",
"../services/codeFixProvider.ts",
"../services/codefixes/fixes.ts",
"../services/codefixes/fixExtendsInterfaceBecomesImplements.ts",
"../services/codefixes/fixClassIncorrectlyImplementsInterface.ts",
"../services/codefixes/fixClassDoesntImplementInheritedAbstractMember.ts",
"../services/codefixes/fixClassSuperMustPrecedeThisAccess.ts",
"../services/codefixes/fixConstructorForDerivedNeedSuperCall.ts",
"../services/codefixes/helpers.ts",
"../services/codefixes/importFixes.ts",
"../services/codefixes/unusedIdentifierFixes.ts",
"../services/codefixes/disableJsDiagnostics.ts",
"harness.ts",
"sourceMapRecorder.ts",
"harnessLanguageService.ts",
"fourslash.ts",
"runnerbase.ts",
"compilerRunner.ts",
"typeWriter.ts",
"fourslashRunner.ts",
"projectsRunner.ts",
"loggedIO.ts",
"rwcRunner.ts",
"test262Runner.ts",
"runner.ts",
"../server/protocol.ts",
"../server/session.ts",
"../server/client.ts",
"../server/editorServices.ts",
"./unittests/incrementalParser.ts",
"./unittests/jsDocParsing.ts",
"./unittests/services/colorization.ts",
"./unittests/services/documentRegistry.ts",
"./unittests/services/preProcessFile.ts",
"./unittests/services/patternMatcher.ts",
"./unittests/session.ts",
"./unittests/versionCache.ts",
"./unittests/convertToBase64.ts",
"./unittests/transpile.ts",
"./unittests/reuseProgramStructure.ts",
"./unittests/cachingInServerLSHost.ts",
"./unittests/moduleResolution.ts",
"./unittests/tsconfigParsing.ts",
"./unittests/commandLineParsing.ts",
"./unittests/configurationExtension.ts",
"./unittests/convertCompilerOptionsFromJson.ts",
"./unittests/convertTypeAcquisitionFromJson.ts",
"./unittests/tsserverProjectSystem.ts",
"./unittests/matchFiles.ts",
"./unittests/initializeTSConfig.ts",
"./unittests/compileOnSave.ts",
"./unittests/typingsInstaller.ts",
"./unittests/projectErrors.ts",
"./unittests/printer.ts",
"./unittests/transform.ts",
"./unittests/customTransforms.ts",
"./unittests/textChanges.ts"
]
}
+18 -16
View File
@@ -13,7 +13,7 @@ class TypeWriterWalker {
private checker: ts.TypeChecker;
constructor(private program: ts.Program, fullTypeCheck: boolean) {
// Consider getting both the diagnostics checker and the non-diagnostics checker to verify
// Consider getting both the diagnostics checker and the non-diagnostics checker to verify
// they are consistent.
this.checker = fullTypeCheck
? program.getDiagnosticsProducingTypeChecker()
@@ -21,7 +21,7 @@ class TypeWriterWalker {
}
public getTypeAndSymbols(fileName: string): TypeWriterResult[] {
var sourceFile = this.program.getSourceFile(fileName);
const sourceFile = this.program.getSourceFile(fileName);
this.currentSourceFile = sourceFile;
this.results = [];
this.visitNode(sourceFile);
@@ -29,7 +29,7 @@ class TypeWriterWalker {
}
private visitNode(node: ts.Node): void {
if (ts.isExpression(node) || node.kind === ts.SyntaxKind.Identifier) {
if (ts.isPartOfExpression(node) || node.kind === ts.SyntaxKind.Identifier) {
this.logTypeAndSymbol(node);
}
@@ -37,27 +37,29 @@ class TypeWriterWalker {
}
private logTypeAndSymbol(node: ts.Node): void {
var actualPos = ts.skipTrivia(this.currentSourceFile.text, node.pos);
var lineAndCharacter = this.currentSourceFile.getLineAndCharacterOfPosition(actualPos);
var sourceText = ts.getTextOfNodeFromSourceText(this.currentSourceFile.text, node);
const actualPos = ts.skipTrivia(this.currentSourceFile.text, node.pos);
const lineAndCharacter = this.currentSourceFile.getLineAndCharacterOfPosition(actualPos);
const sourceText = ts.getTextOfNodeFromSourceText(this.currentSourceFile.text, node);
// Workaround to ensure we output 'C' instead of 'typeof C' for base class expressions
// var type = this.checker.getTypeAtLocation(node);
var type = node.parent && ts.isExpressionWithTypeArgumentsInClassExtendsClause(node.parent) && this.checker.getTypeAtLocation(node.parent) || this.checker.getTypeAtLocation(node);
// let type = this.checker.getTypeAtLocation(node);
const type = node.parent && ts.isExpressionWithTypeArgumentsInClassExtendsClause(node.parent) && this.checker.getTypeAtLocation(node.parent) || this.checker.getTypeAtLocation(node);
ts.Debug.assert(type !== undefined, "type doesn't exist");
var symbol = this.checker.getSymbolAtLocation(node);
const symbol = this.checker.getSymbolAtLocation(node);
var typeString = this.checker.typeToString(type, node.parent, ts.TypeFormatFlags.NoTruncation);
var symbolString: string;
const typeString = this.checker.typeToString(type, node.parent, ts.TypeFormatFlags.NoTruncation);
let symbolString: string;
if (symbol) {
symbolString = "Symbol(" + this.checker.symbolToString(symbol, node.parent);
if (symbol.declarations) {
for (let declaration of symbol.declarations) {
for (const declaration of symbol.declarations) {
symbolString += ", ";
let declSourceFile = declaration.getSourceFile();
let declLineAndCharacter = declSourceFile.getLineAndCharacterOfPosition(declaration.pos);
symbolString += `Decl(${ ts.getBaseFileName(declSourceFile.fileName) }, ${ declLineAndCharacter.line }, ${ declLineAndCharacter.character })`
const declSourceFile = declaration.getSourceFile();
const declLineAndCharacter = declSourceFile.getLineAndCharacterOfPosition(declaration.pos);
const fileName = ts.getBaseFileName(declSourceFile.fileName);
const isLibFile = /lib(.*)\.d\.ts/i.test(fileName);
symbolString += `Decl(${ fileName }, ${ isLibFile ? "--" : declLineAndCharacter.line }, ${ isLibFile ? "--" : declLineAndCharacter.character })`;
}
}
symbolString += ")";
@@ -71,4 +73,4 @@ class TypeWriterWalker {
symbol: symbolString
});
}
}
}
@@ -0,0 +1,213 @@
/// <reference path="..\harness.ts" />
namespace ts {
interface File {
name: string;
content: string;
}
function createDefaultServerHost(fileMap: Map<File>): server.ServerHost {
const existingDirectories = createMap<boolean>();
forEachKey(fileMap, name => {
let dir = getDirectoryPath(name);
let previous: string;
do {
existingDirectories.set(dir, true);
previous = dir;
dir = getDirectoryPath(dir);
} while (dir !== previous);
});
return {
args: <string[]>[],
newLine: "\r\n",
useCaseSensitiveFileNames: false,
write: noop,
readFile: path => {
const file = fileMap.get(path);
return file && file.content;
},
writeFile: notImplemented,
resolvePath: notImplemented,
fileExists: path => fileMap.has(path),
directoryExists: path => existingDirectories.get(path) || false,
createDirectory: noop,
getExecutingFilePath: () => "",
getCurrentDirectory: () => "",
getDirectories: () => [],
getEnvironmentVariable: () => "",
readDirectory: notImplemented,
exit: noop,
watchFile: () => ({
close: noop
}),
watchDirectory: () => ({
close: noop
}),
setTimeout,
clearTimeout,
setImmediate: typeof setImmediate !== "undefined" ? setImmediate : action => setTimeout(action, 0),
clearImmediate: typeof clearImmediate !== "undefined" ? clearImmediate : clearTimeout,
createHash: s => s
};
}
function createProject(rootFile: string, serverHost: server.ServerHost): { project: server.Project, rootScriptInfo: server.ScriptInfo } {
const logger: server.Logger = {
close: noop,
hasLevel: () => false,
loggingEnabled: () => false,
perftrc: noop,
info: noop,
startGroup: noop,
endGroup: noop,
msg: noop,
getLogFileName: (): string => undefined
};
const svcOpts: server.ProjectServiceOptions = {
host: serverHost,
logger,
cancellationToken: { isCancellationRequested: () => false },
useSingleInferredProject: false,
typingsInstaller: undefined
};
const projectService = new server.ProjectService(svcOpts);
const rootScriptInfo = projectService.getOrCreateScriptInfo(rootFile, /* openedByClient */ true, /*containingProject*/ undefined);
const project = projectService.createInferredProjectWithRootFileIfNecessary(rootScriptInfo);
project.setCompilerOptions({ module: ts.ModuleKind.AMD } );
return {
project,
rootScriptInfo
};
}
describe("Caching in LSHost", () => {
it("works using legacy resolution logic", () => {
const root: File = {
name: "c:/d/f0.ts",
content: `import {x} from "f1"`
};
const imported: File = {
name: "c:/f1.ts",
content: `foo()`
};
const serverHost = createDefaultServerHost(createMapFromTemplate({ [root.name]: root, [imported.name]: imported }));
const { project, rootScriptInfo } = createProject(root.name, serverHost);
// ensure that imported file was found
let diags = project.getLanguageService().getSemanticDiagnostics(imported.name);
assert.equal(diags.length, 1);
const originalFileExists = serverHost.fileExists;
{
// patch fileExists to make sure that disk is not touched
serverHost.fileExists = notImplemented;
const newContent = `import {x} from "f1"
var x: string = 1;`;
rootScriptInfo.editContent(0, root.content.length, newContent);
// trigger synchronization to make sure that import will be fetched from the cache
diags = project.getLanguageService().getSemanticDiagnostics(imported.name);
// ensure file has correct number of errors after edit
assert.equal(diags.length, 1);
}
{
let fileExistsIsCalled = false;
serverHost.fileExists = (fileName): boolean => {
if (fileName === "lib.d.ts") {
return false;
}
fileExistsIsCalled = true;
assert.isTrue(fileName.indexOf("/f2.") !== -1);
return originalFileExists.call(serverHost, fileName);
};
const newContent = `import {x} from "f2"`;
rootScriptInfo.editContent(0, root.content.length, newContent);
try {
// trigger synchronization to make sure that LSHost will try to find 'f2' module on disk
project.getLanguageService().getSemanticDiagnostics(imported.name);
assert.isTrue(false, `should not find file '${imported.name}'`);
}
catch (e) {
assert.isTrue(e.message.indexOf(`Could not find file: '${imported.name}'.`) === 0);
}
assert.isTrue(fileExistsIsCalled);
}
{
let fileExistsCalled = false;
serverHost.fileExists = (fileName): boolean => {
if (fileName === "lib.d.ts") {
return false;
}
fileExistsCalled = true;
assert.isTrue(fileName.indexOf("/f1.") !== -1);
return originalFileExists.call(serverHost, fileName);
};
const newContent = `import {x} from "f1"`;
rootScriptInfo.editContent(0, root.content.length, newContent);
project.getLanguageService().getSemanticDiagnostics(imported.name);
assert.isTrue(fileExistsCalled);
// setting compiler options discards module resolution cache
fileExistsCalled = false;
const compilerOptions = ts.clone(project.getCompilerOptions());
compilerOptions.target = ts.ScriptTarget.ES5;
project.setCompilerOptions(compilerOptions);
project.getLanguageService().getSemanticDiagnostics(imported.name);
assert.isTrue(fileExistsCalled);
}
});
it("loads missing files from disk", () => {
const root: File = {
name: `c:/foo.ts`,
content: `import {x} from "bar"`
};
const imported: File = {
name: `c:/bar.d.ts`,
content: `export var y = 1`
};
const fileMap = createMapFromTemplate({ [root.name]: root });
const serverHost = createDefaultServerHost(fileMap);
const originalFileExists = serverHost.fileExists;
let fileExistsCalledForBar = false;
serverHost.fileExists = fileName => {
if (fileName === "lib.d.ts") {
return false;
}
if (!fileExistsCalledForBar) {
fileExistsCalledForBar = fileName.indexOf("/bar.") !== -1;
}
return originalFileExists.call(serverHost, fileName);
};
const { project, rootScriptInfo } = createProject(root.name, serverHost);
let diags = project.getLanguageService().getSemanticDiagnostics(root.name);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called");
assert.isTrue(diags.length === 1, "one diagnostic expected");
assert.isTrue(typeof diags[0].messageText === "string" && ((<string>diags[0].messageText).indexOf("Cannot find module") === 0), "should be 'cannot find module' message");
fileMap.set(imported.name, imported);
fileExistsCalledForBar = false;
rootScriptInfo.editContent(0, root.content.length, `import {y} from "bar"`);
diags = project.getLanguageService().getSemanticDiagnostics(root.name);
assert.isTrue(fileExistsCalledForBar, "'fileExists' should be called.");
assert.isTrue(diags.length === 0, "The import should succeed once the imported file appears on disk.");
});
});
}
+375
View File
@@ -0,0 +1,375 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\..\compiler\commandLineParser.ts" />
namespace ts {
describe("parseCommandLine", () => {
function assertParseResult(commandLine: string[], expectedParsedCommandLine: ts.ParsedCommandLine) {
const parsed = ts.parseCommandLine(commandLine);
const parsedCompilerOptions = JSON.stringify(parsed.options);
const expectedCompilerOptions = JSON.stringify(expectedParsedCommandLine.options);
assert.equal(parsedCompilerOptions, expectedCompilerOptions);
const parsedErrors = parsed.errors;
const expectedErrors = expectedParsedCommandLine.errors;
assert.isTrue(parsedErrors.length === expectedErrors.length, `Expected error: ${JSON.stringify(expectedErrors)}. Actual error: ${JSON.stringify(parsedErrors)}.`);
for (let i = 0; i < parsedErrors.length; i++) {
const parsedError = parsedErrors[i];
const expectedError = expectedErrors[i];
assert.equal(parsedError.code, expectedError.code);
assert.equal(parsedError.category, expectedError.category);
assert.equal(parsedError.messageText, expectedError.messageText);
}
const parsedFileNames = parsed.fileNames;
const expectedFileNames = expectedParsedCommandLine.fileNames;
assert.isTrue(parsedFileNames.length === expectedFileNames.length, `Expected fileNames: [${JSON.stringify(expectedFileNames)}]. Actual fileNames: [${JSON.stringify(parsedFileNames)}].`);
for (let i = 0; i < parsedFileNames.length; i++) {
const parsedFileName = parsedFileNames[i];
const expectedFileName = expectedFileNames[i];
assert.equal(parsedFileName, expectedFileName);
}
}
it("Parse single option of library flag ", () => {
// --lib es6 0.ts
assertParseResult(["--lib", "es6", "0.ts"],
{
errors: [],
fileNames: ["0.ts"],
options: {
lib: ["lib.es2015.d.ts"]
}
});
});
it("Parse multiple options of library flags ", () => {
// --lib es5,es2015.symbol.wellknown 0.ts
assertParseResult(["--lib", "es5,es2015.symbol.wellknown", "0.ts"],
{
errors: [],
fileNames: ["0.ts"],
options: {
lib: ["lib.es5.d.ts", "lib.es2015.symbol.wellknown.d.ts"]
}
});
});
it("Parse invalid option of library flags ", () => {
// --lib es5,invalidOption 0.ts
assertParseResult(["--lib", "es5,invalidOption", "0.ts"],
{
errors: [{
messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'esnext.asynciterable'.",
category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category,
code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
file: undefined,
start: undefined,
length: undefined,
}],
fileNames: ["0.ts"],
options: {
lib: ["lib.es5.d.ts"]
}
});
});
it("Parse empty options of --jsx ", () => {
// 0.ts --jsx
assertParseResult(["0.ts", "--jsx"],
{
errors: [{
messageText: "Compiler option 'jsx' expects an argument.",
category: ts.Diagnostics.Compiler_option_0_expects_an_argument.category,
code: ts.Diagnostics.Compiler_option_0_expects_an_argument.code,
file: undefined,
start: undefined,
length: undefined,
}, {
messageText: "Argument for '--jsx' option must be: 'preserve', 'react-native', 'react'.",
category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category,
code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
file: undefined,
start: undefined,
length: undefined,
}],
fileNames: ["0.ts"],
options: {}
});
});
it("Parse empty options of --module ", () => {
// 0.ts --
assertParseResult(["0.ts", "--module"],
{
errors: [{
messageText: "Compiler option 'module' expects an argument.",
category: ts.Diagnostics.Compiler_option_0_expects_an_argument.category,
code: ts.Diagnostics.Compiler_option_0_expects_an_argument.code,
file: undefined,
start: undefined,
length: undefined,
}, {
messageText: "Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015'.",
category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category,
code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
file: undefined,
start: undefined,
length: undefined,
}],
fileNames: ["0.ts"],
options: {}
});
});
it("Parse empty options of --newLine ", () => {
// 0.ts --newLine
assertParseResult(["0.ts", "--newLine"],
{
errors: [{
messageText: "Compiler option 'newLine' expects an argument.",
category: ts.Diagnostics.Compiler_option_0_expects_an_argument.category,
code: ts.Diagnostics.Compiler_option_0_expects_an_argument.code,
file: undefined,
start: undefined,
length: undefined,
}, {
messageText: "Argument for '--newLine' option must be: 'crlf', 'lf'.",
category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category,
code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
file: undefined,
start: undefined,
length: undefined,
}],
fileNames: ["0.ts"],
options: {}
});
});
it("Parse empty options of --target ", () => {
// 0.ts --target
assertParseResult(["0.ts", "--target"],
{
errors: [{
messageText: "Compiler option 'target' expects an argument.",
category: ts.Diagnostics.Compiler_option_0_expects_an_argument.category,
code: ts.Diagnostics.Compiler_option_0_expects_an_argument.code,
file: undefined,
start: undefined,
length: undefined,
}, {
messageText: "Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'esnext'.",
category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category,
code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
file: undefined,
start: undefined,
length: undefined,
}],
fileNames: ["0.ts"],
options: {}
});
});
it("Parse empty options of --moduleResolution ", () => {
// 0.ts --moduleResolution
assertParseResult(["0.ts", "--moduleResolution"],
{
errors: [{
messageText: "Compiler option 'moduleResolution' expects an argument.",
category: ts.Diagnostics.Compiler_option_0_expects_an_argument.category,
code: ts.Diagnostics.Compiler_option_0_expects_an_argument.code,
file: undefined,
start: undefined,
length: undefined,
}, {
messageText: "Argument for '--moduleResolution' option must be: 'node', 'classic'.",
category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category,
code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
file: undefined,
start: undefined,
length: undefined,
}],
fileNames: ["0.ts"],
options: {}
});
});
it("Parse empty options of --lib ", () => {
// 0.ts --lib
assertParseResult(["0.ts", "--lib"],
{
errors: [{
messageText: "Compiler option 'lib' expects an argument.",
category: ts.Diagnostics.Compiler_option_0_expects_an_argument.category,
code: ts.Diagnostics.Compiler_option_0_expects_an_argument.code,
file: undefined,
start: undefined,
length: undefined,
}],
fileNames: ["0.ts"],
options: {
lib: []
}
});
});
it("Parse empty string of --lib ", () => {
// 0.ts --lib
// This test is an error because the empty string is falsey
assertParseResult(["0.ts", "--lib", ""],
{
errors: [{
messageText: "Compiler option 'lib' expects an argument.",
category: ts.Diagnostics.Compiler_option_0_expects_an_argument.category,
code: ts.Diagnostics.Compiler_option_0_expects_an_argument.code,
file: undefined,
start: undefined,
length: undefined,
}],
fileNames: ["0.ts"],
options: {
lib: []
}
});
});
it("Parse immediately following command line argument of --lib ", () => {
// 0.ts --lib
assertParseResult(["0.ts", "--lib", "--sourcemap"],
{
errors: [],
fileNames: ["0.ts"],
options: {
lib: [],
sourceMap: true
}
});
});
it("Parse --lib option with extra comma ", () => {
// --lib es5, es7 0.ts
assertParseResult(["--lib", "es5,", "es7", "0.ts"],
{
errors: [{
messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'esnext.asynciterable'.",
category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category,
code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
file: undefined,
start: undefined,
length: undefined,
}],
fileNames: ["es7", "0.ts"],
options: {
lib: ["lib.es5.d.ts"]
}
});
});
it("Parse --lib option with trailing white-space ", () => {
// --lib es5, es7 0.ts
assertParseResult(["--lib", "es5, ", "es7", "0.ts"],
{
errors: [{
messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'esnext.asynciterable'.",
category: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.category,
code: ts.Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
file: undefined,
start: undefined,
length: undefined,
}],
fileNames: ["es7", "0.ts"],
options: {
lib: ["lib.es5.d.ts"]
}
});
});
it("Parse multiple compiler flags with input files at the end", () => {
// --lib es5,es2015.symbol.wellknown --target es5 0.ts
assertParseResult(["--lib", "es5,es2015.symbol.wellknown", "--target", "es5", "0.ts"],
{
errors: [],
fileNames: ["0.ts"],
options: {
lib: ["lib.es5.d.ts", "lib.es2015.symbol.wellknown.d.ts"],
target: ts.ScriptTarget.ES5,
}
});
});
it("Parse multiple compiler flags with input files in the middle", () => {
// --module commonjs --target es5 0.ts --lib es5,es2015.symbol.wellknown
assertParseResult(["--module", "commonjs", "--target", "es5", "0.ts", "--lib", "es5,es2015.symbol.wellknown"],
{
errors: [],
fileNames: ["0.ts"],
options: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5,
lib: ["lib.es5.d.ts", "lib.es2015.symbol.wellknown.d.ts"],
}
});
});
it("Parse multiple library compiler flags ", () => {
// --module commonjs --target es5 --lib es5 0.ts --library es2015.array,es2015.symbol.wellknown
assertParseResult(["--module", "commonjs", "--target", "es5", "--lib", "es5", "0.ts", "--lib", "es2015.core, es2015.symbol.wellknown "],
{
errors: [],
fileNames: ["0.ts"],
options: {
module: ts.ModuleKind.CommonJS,
target: ts.ScriptTarget.ES5,
lib: ["lib.es2015.core.d.ts", "lib.es2015.symbol.wellknown.d.ts"],
}
});
});
it("Parse explicit boolean flag value", () => {
assertParseResult(["--strictNullChecks", "false", "0.ts"],
{
errors: [],
fileNames: ["0.ts"],
options: {
strictNullChecks: false,
}
});
});
it("Parse non boolean argument after boolean flag", () => {
assertParseResult(["--noImplicitAny", "t", "0.ts"],
{
errors: [],
fileNames: ["t", "0.ts"],
options: {
noImplicitAny: true,
}
});
});
it("Parse implicit boolean flag value", () => {
assertParseResult(["--strictNullChecks"],
{
errors: [],
fileNames: [],
options: {
strictNullChecks: true,
}
});
});
});
}
+604
View File
@@ -0,0 +1,604 @@
/// <reference path="../harness.ts" />
/// <reference path="./tsserverProjectSystem.ts" />
/// <reference path="../../server/typingsInstaller/typingsInstaller.ts" />
namespace ts.projectSystem {
import CommandNames = server.CommandNames;
const nullCancellationToken = server.nullCancellationToken;
function createTestTypingsInstaller(host: server.ServerHost) {
return new TestTypingsInstaller("/a/data/", /*throttleLimit*/5, host);
}
describe("CompileOnSave affected list", () => {
function sendAffectedFileRequestAndCheckResult(session: server.Session, request: server.protocol.Request, expectedFileList: { projectFileName: string, files: FileOrFolder[] }[]) {
const response: server.protocol.CompileOnSaveAffectedFileListSingleProject[] = session.executeCommand(request).response;
const actualResult = response.sort((list1, list2) => compareStrings(list1.projectFileName, list2.projectFileName));
expectedFileList = expectedFileList.sort((list1, list2) => compareStrings(list1.projectFileName, list2.projectFileName));
assert.equal(actualResult.length, expectedFileList.length, `Actual result project number is different from the expected project number`);
for (let i = 0; i < actualResult.length; i++) {
const actualResultSingleProject = actualResult[i];
const expectedResultSingleProject = expectedFileList[i];
assert.equal(actualResultSingleProject.projectFileName, expectedResultSingleProject.projectFileName, `Actual result contains different projects than the expected result`);
const actualResultSingleProjectFileNameList = actualResultSingleProject.fileNames.sort();
const expectedResultSingleProjectFileNameList = map(expectedResultSingleProject.files, f => f.path).sort();
assert.isTrue(
arrayIsEqualTo(actualResultSingleProjectFileNameList, expectedResultSingleProjectFileNameList),
`For project ${actualResultSingleProject.projectFileName}, the actual result is ${actualResultSingleProjectFileNameList}, while expected ${expectedResultSingleProjectFileNameList}`);
}
}
function createSession(host: server.ServerHost, typingsInstaller?: server.ITypingsInstaller): server.Session {
const opts: server.SessionOptions = {
host,
cancellationToken: nullCancellationToken,
useSingleInferredProject: false,
typingsInstaller: typingsInstaller || server.nullTypingsInstaller,
byteLength: Utils.byteLength,
hrtime: process.hrtime,
logger: nullLogger,
canUseEvents: false
};
return new server.Session(opts);
}
describe("for configured projects", () => {
let moduleFile1: FileOrFolder;
let file1Consumer1: FileOrFolder;
let file1Consumer2: FileOrFolder;
let moduleFile2: FileOrFolder;
let globalFile3: FileOrFolder;
let configFile: FileOrFolder;
let changeModuleFile1ShapeRequest1: server.protocol.Request;
let changeModuleFile1InternalRequest1: server.protocol.Request;
let changeModuleFile1ShapeRequest2: server.protocol.Request;
// A compile on save affected file request using file1
let moduleFile1FileListRequest: server.protocol.Request;
beforeEach(() => {
moduleFile1 = {
path: "/a/b/moduleFile1.ts",
content: "export function Foo() { };"
};
file1Consumer1 = {
path: "/a/b/file1Consumer1.ts",
content: `import {Foo} from "./moduleFile1"; export var y = 10;`
};
file1Consumer2 = {
path: "/a/b/file1Consumer2.ts",
content: `import {Foo} from "./moduleFile1"; let z = 10;`
};
moduleFile2 = {
path: "/a/b/moduleFile2.ts",
content: `export var Foo4 = 10;`
};
globalFile3 = {
path: "/a/b/globalFile3.ts",
content: `interface GlobalFoo { age: number }`
};
configFile = {
path: "/a/b/tsconfig.json",
content: `{
"compileOnSave": true
}`
};
// Change the content of file1 to `export var T: number;export function Foo() { };`
changeModuleFile1ShapeRequest1 = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 1,
endLine: 1,
endOffset: 1,
insertString: `export var T: number;`
});
// Change the content of file1 to `export var T: number;export function Foo() { };`
changeModuleFile1InternalRequest1 = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 1,
endLine: 1,
endOffset: 1,
insertString: `var T1: number;`
});
// Change the content of file1 to `export var T: number;export function Foo() { };`
changeModuleFile1ShapeRequest2 = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 1,
endLine: 1,
endOffset: 1,
insertString: `export var T2: number;`
});
moduleFile1FileListRequest = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path, projectFileName: configFile.path });
});
it("should contains only itself if a module file's shape didn't change, and all files referencing it if its shape changed", () => {
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1, file1Consumer1], session);
// Send an initial compileOnSave request
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
session.executeCommand(changeModuleFile1ShapeRequest1);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
// Change the content of file1 to `export var T: number;export function Foo() { console.log('hi'); };`
const changeFile1InternalRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 46,
endLine: 1,
endOffset: 46,
insertString: `console.log('hi');`
});
session.executeCommand(changeFile1InternalRequest);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]);
});
it("should be up-to-date with the reference map changes", () => {
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1, file1Consumer1], session);
// Send an initial compileOnSave request
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
// Change file2 content to `let y = Foo();`
const removeFile1Consumer1ImportRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
file: file1Consumer1.path,
line: 1,
offset: 1,
endLine: 1,
endOffset: 28,
insertString: ""
});
session.executeCommand(removeFile1Consumer1ImportRequest);
session.executeCommand(changeModuleFile1ShapeRequest1);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]);
// Add the import statements back to file2
const addFile2ImportRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
file: file1Consumer1.path,
line: 1,
offset: 1,
endLine: 1,
endOffset: 1,
insertString: `import {Foo} from "./moduleFile1";`
});
session.executeCommand(addFile2ImportRequest);
// Change the content of file1 to `export var T2: string;export var T: number;export function Foo() { };`
const changeModuleFile1ShapeRequest2 = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 1,
endLine: 1,
endOffset: 1,
insertString: `export var T2: string;`
});
session.executeCommand(changeModuleFile1ShapeRequest2);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
});
it("should be up-to-date with changes made in non-open files", () => {
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1], session);
// Send an initial compileOnSave request
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
file1Consumer1.content = `let y = 10;`;
host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]);
host.triggerFileWatcherCallback(file1Consumer1.path, /*removed*/ false);
session.executeCommand(changeModuleFile1ShapeRequest1);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer2] }]);
});
it("should be up-to-date with deleted files", () => {
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
session.executeCommand(changeModuleFile1ShapeRequest1);
// Delete file1Consumer2
host.reloadFS([moduleFile1, file1Consumer1, configFile, libFile]);
host.triggerFileWatcherCallback(file1Consumer2.path, /*removed*/ true);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]);
});
it("should be up-to-date with newly created files", () => {
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
const file1Consumer3: FileOrFolder = {
path: "/a/b/file1Consumer3.ts",
content: `import {Foo} from "./moduleFile1"; let y = Foo();`
};
host.reloadFS([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3, globalFile3, configFile, libFile]);
host.triggerDirectoryWatcherCallback(ts.getDirectoryPath(file1Consumer3.path), file1Consumer3.path);
host.runQueuedTimeoutCallbacks();
session.executeCommand(changeModuleFile1ShapeRequest1);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, file1Consumer3] }]);
});
it("should detect changes in non-root files", () => {
moduleFile1 = {
path: "/a/b/moduleFile1.ts",
content: "export function Foo() { };"
};
file1Consumer1 = {
path: "/a/b/file1Consumer1.ts",
content: `import {Foo} from "./moduleFile1"; let y = Foo();`
};
configFile = {
path: "/a/b/tsconfig.json",
content: `{
"compileOnSave": true,
"files": ["${file1Consumer1.path}"]
}`
};
const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1, file1Consumer1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]);
// change file1 shape now, and verify both files are affected
session.executeCommand(changeModuleFile1ShapeRequest1);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1] }]);
// change file1 internal, and verify only file1 is affected
session.executeCommand(changeModuleFile1InternalRequest1);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]);
});
it("should return all files if a global file changed shape", () => {
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([globalFile3], session);
const changeGlobalFile3ShapeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
file: globalFile3.path,
line: 1,
offset: 1,
endLine: 1,
endOffset: 1,
insertString: `var T2: string;`
});
// check after file1 shape changes
session.executeCommand(changeGlobalFile3ShapeRequest);
const globalFile3FileListRequest = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: globalFile3.path });
sendAffectedFileRequestAndCheckResult(session, globalFile3FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2] }]);
});
it("should return empty array if CompileOnSave is not enabled", () => {
configFile = {
path: "/a/b/tsconfig.json",
content: `{}`
};
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, []);
});
it("should save when compileOnSave is enabled in base tsconfig.json", () => {
configFile = {
path: "/a/b/tsconfig.json",
content: `{
"extends": "/a/tsconfig.json"
}`
};
const configFile2: FileOrFolder = {
path: "/a/tsconfig.json",
content: `{
"compileOnSave": true
}`
};
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1, file1Consumer1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
});
it("should always return the file itself if '--isolatedModules' is specified", () => {
configFile = {
path: "/a/b/tsconfig.json",
content: `{
"compileOnSave": true,
"compilerOptions": {
"isolatedModules": true
}
}`
};
const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1], session);
const file1ChangeShapeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 27,
endLine: 1,
endOffset: 27,
insertString: `Point,`
});
session.executeCommand(file1ChangeShapeRequest);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]);
});
it("should always return the file itself if '--out' or '--outFile' is specified", () => {
configFile = {
path: "/a/b/tsconfig.json",
content: `{
"compileOnSave": true,
"compilerOptions": {
"module": "system",
"outFile": "/a/b/out.js"
}
}`
};
const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1], session);
const file1ChangeShapeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
file: moduleFile1.path,
line: 1,
offset: 27,
endLine: 1,
endOffset: 27,
insertString: `Point,`
});
session.executeCommand(file1ChangeShapeRequest);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]);
});
it("should return cascaded affected file list", () => {
const file1Consumer1Consumer1: FileOrFolder = {
path: "/a/b/file1Consumer1Consumer1.ts",
content: `import {y} from "./file1Consumer1";`
};
const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([moduleFile1, file1Consumer1], session);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]);
const changeFile1Consumer1ShapeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
file: file1Consumer1.path,
line: 2,
offset: 1,
endLine: 2,
endOffset: 1,
insertString: `export var T: number;`
});
session.executeCommand(changeModuleFile1ShapeRequest1);
session.executeCommand(changeFile1Consumer1ShapeRequest);
sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]);
});
it("should work fine for files with circular references", () => {
const file1: FileOrFolder = {
path: "/a/b/file1.ts",
content: `
/// <reference path="./file2.ts" />
export var t1 = 10;`
};
const file2: FileOrFolder = {
path: "/a/b/file2.ts",
content: `
/// <reference path="./file1.ts" />
export var t2 = 10;`
};
const host = createServerHost([file1, file2, configFile]);
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([file1, file2], session);
const file1AffectedListRequest = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path });
sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [{ projectFileName: configFile.path, files: [file1, file2] }]);
});
it("should return results for all projects if not specifying projectFileName", () => {
const file1: FileOrFolder = { path: "/a/b/file1.ts", content: "export var t = 10;" };
const file2: FileOrFolder = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` };
const file3: FileOrFolder = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` };
const configFile1: FileOrFolder = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` };
const configFile2: FileOrFolder = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` };
const host = createServerHost([file1, file2, file3, configFile1, configFile2]);
const session = createSession(host);
openFilesForSession([file1, file2, file3], session);
const file1AffectedListRequest = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path });
sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [
{ projectFileName: configFile1.path, files: [file1, file2] },
{ projectFileName: configFile2.path, files: [file1, file3] }
]);
});
it("should detect removed code file", () => {
const referenceFile1: FileOrFolder = {
path: "/a/b/referenceFile1.ts",
content: `
/// <reference path="./moduleFile1.ts" />
export var x = Foo();`
};
const host = createServerHost([moduleFile1, referenceFile1, configFile]);
const session = createSession(host);
openFilesForSession([referenceFile1], session);
host.reloadFS([referenceFile1, configFile]);
host.triggerFileWatcherCallback(moduleFile1.path, /*removed*/ true);
const request = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
sendAffectedFileRequestAndCheckResult(session, request, [
{ projectFileName: configFile.path, files: [referenceFile1] }
]);
const requestForMissingFile = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path });
sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []);
});
it("should detect non-existing code file", () => {
const referenceFile1: FileOrFolder = {
path: "/a/b/referenceFile1.ts",
content: `
/// <reference path="./moduleFile2.ts" />
export var x = Foo();`
};
const host = createServerHost([referenceFile1, configFile]);
const session = createSession(host);
openFilesForSession([referenceFile1], session);
const request = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
sendAffectedFileRequestAndCheckResult(session, request, [
{ projectFileName: configFile.path, files: [referenceFile1] }
]);
});
});
});
describe("EmitFile test", () => {
it("should respect line endings", () => {
test("\n");
test("\r\n");
function test(newLine: string) {
const lines = ["var x = 1;", "var y = 2;"];
const path = "/a/app";
const f = {
path: path + ".ts",
content: lines.join(newLine)
};
const host = createServerHost([f], { newLine });
const session = createSession(host);
session.executeCommand(<server.protocol.OpenRequest>{
seq: 1,
type: "request",
command: "open",
arguments: { file: f.path }
});
session.executeCommand(<server.protocol.CompileOnSaveEmitFileRequest>{
seq: 2,
type: "request",
command: "compileOnSaveEmitFile",
arguments: { file: f.path }
});
const emitOutput = host.readFile(path + ".js");
assert.equal(emitOutput, f.content + newLine, "content of emit output should be identical with the input + newline");
}
});
it("should emit specified file", () => {
const file1 = {
path: "/a/b/f1.ts",
content: `export function Foo() { return 10; }`
};
const file2 = {
path: "/a/b/f2.ts",
content: `import {Foo} from "./f1"; let y = Foo();`
};
const configFile = {
path: "/a/b/tsconfig.json",
content: `{}`
};
const host = createServerHost([file1, file2, configFile, libFile], { newLine: "\r\n" });
const typingsInstaller = createTestTypingsInstaller(host);
const session = createSession(host, typingsInstaller);
openFilesForSession([file1, file2], session);
const compileFileRequest = makeSessionRequest<server.protocol.CompileOnSaveEmitFileRequestArgs>(CommandNames.CompileOnSaveEmitFile, { file: file1.path, projectFileName: configFile.path });
session.executeCommand(compileFileRequest);
const expectedEmittedFileName = "/a/b/f1.js";
assert.isTrue(host.fileExists(expectedEmittedFileName));
assert.equal(host.readFile(expectedEmittedFileName), `"use strict";\r\nexports.__esModule = true;\r\nfunction Foo() { return 10; }\r\nexports.Foo = Foo;\r\n`);
});
it("shoud not emit js files in external projects", () => {
const file1 = {
path: "/a/b/file1.ts",
content: "consonle.log('file1');"
};
// file2 has errors. The emitting should not be blocked.
const file2 = {
path: "/a/b/file2.js",
content: "console.log'file2');"
};
const file3 = {
path: "/a/b/file3.js",
content: "console.log('file3');"
};
const externalProjectName = "externalproject";
const host = createServerHost([file1, file2, file3, libFile]);
const session = createSession(host);
const projectService = session.getProjectService();
projectService.openExternalProject({
rootFiles: toExternalFiles([file1.path, file2.path]),
options: {
allowJs: true,
outFile: "dist.js",
compileOnSave: true
},
projectFileName: externalProjectName
});
const emitRequest = makeSessionRequest<server.protocol.CompileOnSaveEmitFileRequestArgs>(CommandNames.CompileOnSaveEmitFile, { file: file1.path });
session.executeCommand(emitRequest);
const expectedOutFileName = "/a/b/dist.js";
assert.isTrue(host.fileExists(expectedOutFileName));
const outFileContent = host.readFile(expectedOutFileName);
assert.isTrue(outFileContent.indexOf(file1.content) !== -1);
assert.isTrue(outFileContent.indexOf(file2.content) === -1);
assert.isTrue(outFileContent.indexOf(file3.content) === -1);
});
});
}
@@ -0,0 +1,188 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\virtualFileSystem.ts" />
namespace ts {
const testContentsJson = createMapFromTemplate({
"/dev/tsconfig.json": {
extends: "./configs/base",
files: [
"main.ts",
"supplemental.ts"
]
},
"/dev/tsconfig.nostrictnull.json": {
extends: "./tsconfig",
compilerOptions: {
strictNullChecks: false
}
},
"/dev/configs/base.json": {
compilerOptions: {
allowJs: true,
noImplicitAny: true,
strictNullChecks: true
}
},
"/dev/configs/tests.json": {
compilerOptions: {
"preserveConstEnums": true,
"removeComments": false,
"sourceMap": true
},
exclude: [
"../tests/baselines",
"../tests/scenarios"
],
include: [
"../tests/**/*.ts"
]
},
"/dev/circular.json": {
extends: "./circular2",
compilerOptions: {
module: "amd"
}
},
"/dev/circular2.json": {
extends: "./circular",
compilerOptions: {
module: "commonjs"
}
},
"/dev/missing.json": {
extends: "./missing2",
compilerOptions: {
"types": []
}
},
"/dev/failure.json": {
extends: "./failure2.json",
compilerOptions: {
typeRoots: []
}
},
"/dev/failure2.json": {
excludes: ["*.js"]
},
"/dev/configs/first.json": {
extends: "./base",
compilerOptions: {
module: "commonjs"
},
files: ["../main.ts"]
},
"/dev/configs/second.json": {
extends: "./base",
compilerOptions: {
module: "amd"
},
include: ["../supplemental.*"]
},
"/dev/extends.json": { extends: 42 },
"/dev/extends2.json": { extends: "configs/base" },
"/dev/main.ts": "",
"/dev/supplemental.ts": "",
"/dev/tests/unit/spec.ts": "",
"/dev/tests/utils.ts": "",
"/dev/tests/scenarios/first.json": "",
"/dev/tests/baselines/first/output.ts": ""
});
const testContents = mapEntries(testContentsJson, (k, v) => [k, typeof v === "string" ? v : JSON.stringify(v)]);
const caseInsensitiveBasePath = "c:/dev/";
const caseInsensitiveHost = new Utils.MockParseConfigHost(caseInsensitiveBasePath, /*useCaseSensitiveFileNames*/ false, mapEntries(testContents, (key, content) => [`c:${key}`, content]));
const caseSensitiveBasePath = "/dev/";
const caseSensitiveHost = new Utils.MockParseConfigHost(caseSensitiveBasePath, /*useCaseSensitiveFileNames*/ true, testContents);
function verifyDiagnostics(actual: Diagnostic[], expected: {code: number, category: DiagnosticCategory, messageText: string}[]) {
assert.isTrue(expected.length === actual.length, `Expected error: ${JSON.stringify(expected)}. Actual error: ${JSON.stringify(actual)}.`);
for (let i = 0; i < actual.length; i++) {
const actualError = actual[i];
const expectedError = expected[i];
assert.equal(actualError.code, expectedError.code, "Error code mismatch");
assert.equal(actualError.category, expectedError.category, "Category mismatch");
assert.equal(flattenDiagnosticMessageText(actualError.messageText, "\n"), expectedError.messageText);
}
}
describe("Configuration Extension", () => {
forEach<[string, string, Utils.MockParseConfigHost], void>([
["under a case insensitive host", caseInsensitiveBasePath, caseInsensitiveHost],
["under a case sensitive host", caseSensitiveBasePath, caseSensitiveHost]
], ([testName, basePath, host]) => {
function testSuccess(name: string, entry: string, expected: CompilerOptions, expectedFiles: string[]) {
it(name, () => {
const {config, error} = ts.readConfigFile(entry, name => host.readFile(name));
assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n"));
const parsed = ts.parseJsonConfigFileContent(config, host, basePath, {}, entry);
assert(!parsed.errors.length, flattenDiagnosticMessageText(parsed.errors[0] && parsed.errors[0].messageText, "\n"));
expected.configFilePath = entry;
assert.deepEqual(parsed.options, expected);
assert.deepEqual(parsed.fileNames, expectedFiles);
});
}
function testFailure(name: string, entry: string, expectedDiagnostics: {code: number, category: DiagnosticCategory, messageText: string}[]) {
it(name, () => {
const {config, error} = ts.readConfigFile(entry, name => host.readFile(name));
assert(config && !error, flattenDiagnosticMessageText(error && error.messageText, "\n"));
const parsed = ts.parseJsonConfigFileContent(config, host, basePath, {}, entry);
verifyDiagnostics(parsed.errors, expectedDiagnostics);
});
}
describe(testName, () => {
testSuccess("can resolve an extension with a base extension", "tsconfig.json", {
allowJs: true,
noImplicitAny: true,
strictNullChecks: true,
}, [
combinePaths(basePath, "main.ts"),
combinePaths(basePath, "supplemental.ts"),
]);
testSuccess("can resolve an extension with a base extension that overrides options", "tsconfig.nostrictnull.json", {
allowJs: true,
noImplicitAny: true,
strictNullChecks: false,
}, [
combinePaths(basePath, "main.ts"),
combinePaths(basePath, "supplemental.ts"),
]);
testFailure("can report errors on circular imports", "circular.json", [
{
code: 18000,
category: DiagnosticCategory.Error,
messageText: `Circularity detected while resolving configuration: ${[combinePaths(basePath, "circular.json"), combinePaths(basePath, "circular2.json"), combinePaths(basePath, "circular.json")].join(" -> ")}`
}
]);
testFailure("can report missing configurations", "missing.json", [{
code: 6096,
category: DiagnosticCategory.Message,
messageText: `File './missing2' does not exist.`
}]);
testFailure("can report errors in extended configs", "failure.json", [{
code: 6114,
category: DiagnosticCategory.Error,
messageText: `Unknown option 'excludes'. Did you mean 'exclude'?`
}]);
testFailure("can error when 'extends' is not a string", "extends.json", [{
code: 5024,
category: DiagnosticCategory.Error,
messageText: `Compiler option 'extends' requires a value of type string.`
}]);
testFailure("can error when 'extends' is neither relative nor rooted.", "extends2.json", [{
code: 18001,
category: DiagnosticCategory.Error,
messageText: `A path in an 'extends' option must be relative or rooted, but 'configs/base' is not.`
}]);
});
});
});
}
@@ -0,0 +1,491 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\..\compiler\commandLineParser.ts" />
namespace ts {
describe("convertCompilerOptionsFromJson", () => {
function assertCompilerOptions(json: any, configFileName: string, expectedResult: { compilerOptions: CompilerOptions, errors: Diagnostic[] }) {
const { options: actualCompilerOptions, errors: actualErrors} = convertCompilerOptionsFromJson(json["compilerOptions"], "/apath/", configFileName);
const parsedCompilerOptions = JSON.stringify(actualCompilerOptions);
const expectedCompilerOptions = JSON.stringify(expectedResult.compilerOptions);
assert.equal(parsedCompilerOptions, expectedCompilerOptions);
const expectedErrors = expectedResult.errors;
assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`);
for (let i = 0; i < actualErrors.length; i++) {
const actualError = actualErrors[i];
const expectedError = expectedErrors[i];
assert.equal(actualError.code, expectedError.code);
assert.equal(actualError.category, expectedError.category);
assert.equal(actualError.messageText, expectedError.messageText);
}
}
// tsconfig.json tests
it("Convert correctly format tsconfig.json to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"lib": ["es5", "es2015.core", "es2015.symbol"]
}
}, "tsconfig.json",
{
compilerOptions: <CompilerOptions>{
module: ModuleKind.CommonJS,
target: ScriptTarget.ES5,
noImplicitAny: false,
sourceMap: false,
lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"]
},
errors: <Diagnostic[]>[]
}
);
});
it("Convert correctly format tsconfig.json with allowJs is false to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"allowJs": false,
"lib": ["es5", "es2015.core", "es2015.symbol"]
}
}, "tsconfig.json",
{
compilerOptions: <CompilerOptions>{
module: ModuleKind.CommonJS,
target: ScriptTarget.ES5,
noImplicitAny: false,
sourceMap: false,
allowJs: false,
lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"]
},
errors: <Diagnostic[]>[]
}
);
});
it("Convert incorrect option of jsx to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"jsx": ""
}
}, "tsconfig.json",
{
compilerOptions: <CompilerOptions>{
module: ModuleKind.CommonJS,
target: ScriptTarget.ES5,
noImplicitAny: false,
sourceMap: false,
},
errors: [{
file: undefined,
start: 0,
length: 0,
messageText: "Argument for '--jsx' option must be: 'preserve', 'react-native', 'react'.",
code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category
}]
}
);
});
it("Convert incorrect option of module to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"module": "",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
}
}, "tsconfig.json",
{
compilerOptions: <CompilerOptions>{
target: ScriptTarget.ES5,
noImplicitAny: false,
sourceMap: false,
},
errors: [{
file: undefined,
start: 0,
length: 0,
messageText: "Argument for '--module' option must be: 'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015'.",
code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category
}]
}
);
});
it("Convert incorrect option of newLine to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"newLine": "",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
}
}, "tsconfig.json",
{
compilerOptions: <CompilerOptions>{
target: ScriptTarget.ES5,
noImplicitAny: false,
sourceMap: false,
},
errors: [{
file: undefined,
start: 0,
length: 0,
messageText: "Argument for '--newLine' option must be: 'crlf', 'lf'.",
code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category
}]
}
);
});
it("Convert incorrect option of target to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"target": "",
"noImplicitAny": false,
"sourceMap": false,
}
}, "tsconfig.json",
{
compilerOptions: <CompilerOptions>{
noImplicitAny: false,
sourceMap: false,
},
errors: [{
file: undefined,
start: 0,
length: 0,
messageText: "Argument for '--target' option must be: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'esnext'.",
code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category
}]
}
);
});
it("Convert incorrect option of module-resolution to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"moduleResolution": "",
"noImplicitAny": false,
"sourceMap": false,
}
}, "tsconfig.json",
{
compilerOptions: <CompilerOptions>{
noImplicitAny: false,
sourceMap: false,
},
errors: [{
file: undefined,
start: 0,
length: 0,
messageText: "Argument for '--moduleResolution' option must be: 'node', 'classic'.",
code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category
}]
}
);
});
it("Convert incorrect option of libs to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"lib": ["es5", "es2015.core", "incorrectLib"]
}
}, "tsconfig.json",
{
compilerOptions: <CompilerOptions>{
module: ModuleKind.CommonJS,
target: ScriptTarget.ES5,
noImplicitAny: false,
sourceMap: false,
lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts"]
},
errors: [{
file: undefined,
start: 0,
length: 0,
messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'esnext.asynciterable'.",
code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category
}]
}
);
});
it("Convert empty string option of libs to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"lib": ["es5", ""]
}
}, "tsconfig.json",
{
compilerOptions: <CompilerOptions>{
module: ModuleKind.CommonJS,
target: ScriptTarget.ES5,
noImplicitAny: false,
sourceMap: false,
lib: ["lib.es5.d.ts"]
},
errors: [{
file: undefined,
start: 0,
length: 0,
messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'esnext.asynciterable'.",
code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category
}]
}
);
});
it("Convert empty string option of libs to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"lib": [""]
}
}, "tsconfig.json",
{
compilerOptions: <CompilerOptions>{
module: ModuleKind.CommonJS,
target: ScriptTarget.ES5,
noImplicitAny: false,
sourceMap: false,
lib: []
},
errors: [{
file: undefined,
start: 0,
length: 0,
messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'esnext.asynciterable'.",
code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category
}]
}
);
});
it("Convert trailing-whitespace string option of libs to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"lib": [" "]
}
}, "tsconfig.json",
{
compilerOptions: <CompilerOptions>{
module: ModuleKind.CommonJS,
target: ScriptTarget.ES5,
noImplicitAny: false,
sourceMap: false,
lib: []
},
errors: [{
file: undefined,
start: 0,
length: 0,
messageText: "Argument for '--lib' option must be: 'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'esnext', 'dom', 'dom.iterable', 'webworker', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'esnext.asynciterable'.",
code: Diagnostics.Argument_for_0_option_must_be_Colon_1.code,
category: Diagnostics.Argument_for_0_option_must_be_Colon_1.category
}]
}
);
});
it("Convert empty option of libs to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"lib": []
}
}, "tsconfig.json",
{
compilerOptions: <CompilerOptions>{
module: ModuleKind.CommonJS,
target: ScriptTarget.ES5,
noImplicitAny: false,
sourceMap: false,
lib: []
},
errors: []
}
);
});
it("Convert incorrectly format tsconfig.json to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"modu": "commonjs",
}
}, "tsconfig.json",
{
compilerOptions: {},
errors: [{
file: undefined,
start: 0,
length: 0,
messageText: "Unknown compiler option 'modu'.",
code: Diagnostics.Unknown_compiler_option_0.code,
category: Diagnostics.Unknown_compiler_option_0.category
}]
}
);
});
it("Convert default tsconfig.json to compiler-options ", () => {
assertCompilerOptions({}, "tsconfig.json",
{
compilerOptions: {} as CompilerOptions,
errors: <Diagnostic[]>[]
}
);
});
// jsconfig.json
it("Convert correctly format jsconfig.json to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"lib": ["es5", "es2015.core", "es2015.symbol"]
}
}, "jsconfig.json",
{
compilerOptions: <CompilerOptions>{
allowJs: true,
maxNodeModuleJsDepth: 2,
allowSyntheticDefaultImports: true,
skipLibCheck: true,
module: ModuleKind.CommonJS,
target: ScriptTarget.ES5,
noImplicitAny: false,
sourceMap: false,
lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"]
},
errors: <Diagnostic[]>[]
}
);
});
it("Convert correctly format jsconfig.json with allowJs is false to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"noImplicitAny": false,
"sourceMap": false,
"allowJs": false,
"lib": ["es5", "es2015.core", "es2015.symbol"]
}
}, "jsconfig.json",
{
compilerOptions: <CompilerOptions>{
allowJs: false,
maxNodeModuleJsDepth: 2,
allowSyntheticDefaultImports: true,
skipLibCheck: true,
module: ModuleKind.CommonJS,
target: ScriptTarget.ES5,
noImplicitAny: false,
sourceMap: false,
lib: ["lib.es5.d.ts", "lib.es2015.core.d.ts", "lib.es2015.symbol.d.ts"]
},
errors: <Diagnostic[]>[]
}
);
});
it("Convert incorrectly format jsconfig.json to compiler-options ", () => {
assertCompilerOptions(
{
"compilerOptions": {
"modu": "commonjs",
}
}, "jsconfig.json",
{
compilerOptions:
{
allowJs: true,
maxNodeModuleJsDepth: 2,
allowSyntheticDefaultImports: true,
skipLibCheck: true
},
errors: [{
file: undefined,
start: 0,
length: 0,
messageText: "Unknown compiler option 'modu'.",
code: Diagnostics.Unknown_compiler_option_0.code,
category: Diagnostics.Unknown_compiler_option_0.category
}]
}
);
});
it("Convert default jsconfig.json to compiler-options ", () => {
assertCompilerOptions({}, "jsconfig.json",
{
compilerOptions:
{
allowJs: true,
maxNodeModuleJsDepth: 2,
allowSyntheticDefaultImports: true,
skipLibCheck: true
},
errors: <Diagnostic[]>[]
}
);
});
});
}
+35
View File
@@ -0,0 +1,35 @@
/// <reference path="..\harness.ts" />
namespace ts {
describe("convertToBase64", () => {
function runTest(input: string): void {
const actual = ts.convertToBase64(input);
const expected = new Buffer(input).toString("base64");
assert.equal(actual, expected, "Encoded string using convertToBase64 does not match buffer.toString('base64')");
}
if (Buffer) {
it("Converts ASCII charaters correctly", () => {
runTest(" !\"#$ %&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~");
});
it("Converts escape sequences correctly", () => {
runTest("\t\n\r\\\"\'\u0062");
});
it("Converts simple unicode characters correctly", () => {
runTest("ΠΣ ٵپ औठ ⺐⺠");
});
it("Converts simple code snippet correctly", () => {
runTest(`/// <reference path="file.ts" />
var x: string = "string";
console.log(x);`);
});
it("Converts simple code snippet with unicode characters correctly", () => {
runTest(`var Π = 3.1415; console.log(Π);`);
});
}
});
}
@@ -0,0 +1,210 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\..\compiler\commandLineParser.ts" />
namespace ts {
describe("convertTypeAcquisitionFromJson", () => {
function assertTypeAcquisition(json: any, configFileName: string, expectedResult: { typeAcquisition: TypeAcquisition, errors: Diagnostic[] }) {
const jsonOptions = json["typeAcquisition"] || json["typingOptions"];
const { options: actualTypeAcquisition, errors: actualErrors } = convertTypeAcquisitionFromJson(jsonOptions, "/apath/", configFileName);
const parsedTypeAcquisition = JSON.stringify(actualTypeAcquisition);
const expectedTypeAcquisition = JSON.stringify(expectedResult.typeAcquisition);
assert.equal(parsedTypeAcquisition, expectedTypeAcquisition);
const expectedErrors = expectedResult.errors;
assert.isTrue(expectedResult.errors.length === actualErrors.length, `Expected error: ${JSON.stringify(expectedResult.errors)}. Actual error: ${JSON.stringify(actualErrors)}.`);
for (let i = 0; i < actualErrors.length; i++) {
const actualError = actualErrors[i];
const expectedError = expectedErrors[i];
assert.equal(actualError.code, expectedError.code, `Expected error-code: ${JSON.stringify(expectedError.code)}. Actual error-code: ${JSON.stringify(actualError.code)}.`);
assert.equal(actualError.category, expectedError.category, `Expected error-category: ${JSON.stringify(expectedError.category)}. Actual error-category: ${JSON.stringify(actualError.category)}.`);
}
}
// tsconfig.json
it("Convert deprecated typingOptions.enableAutoDiscovery format tsconfig.json to typeAcquisition ", () => {
assertTypeAcquisition(
{
"typingOptions":
{
"enableAutoDiscovery": true,
"include": ["0.d.ts", "1.d.ts"],
"exclude": ["0.js", "1.js"]
}
},
"tsconfig.json",
{
typeAcquisition:
{
enable: true,
include: ["0.d.ts", "1.d.ts"],
exclude: ["0.js", "1.js"]
},
errors: <Diagnostic[]>[]
});
});
it("Convert correctly format tsconfig.json to typeAcquisition ", () => {
assertTypeAcquisition(
{
"typeAcquisition":
{
"enable": true,
"include": ["0.d.ts", "1.d.ts"],
"exclude": ["0.js", "1.js"]
}
},
"tsconfig.json",
{
typeAcquisition:
{
enable: true,
include: ["0.d.ts", "1.d.ts"],
exclude: ["0.js", "1.js"]
},
errors: <Diagnostic[]>[]
});
});
it("Convert incorrect format tsconfig.json to typeAcquisition ", () => {
assertTypeAcquisition(
{
"typeAcquisition":
{
"enableAutoDiscovy": true,
}
}, "tsconfig.json",
{
typeAcquisition:
{
enable: false,
include: [],
exclude: []
},
errors: [
{
category: Diagnostics.Unknown_type_acquisition_option_0.category,
code: Diagnostics.Unknown_type_acquisition_option_0.code,
file: undefined,
start: 0,
length: 0,
messageText: undefined
}
]
});
});
it("Convert default tsconfig.json to typeAcquisition ", () => {
assertTypeAcquisition({}, "tsconfig.json",
{
typeAcquisition:
{
enable: false,
include: [],
exclude: []
},
errors: <Diagnostic[]>[]
});
});
it("Convert tsconfig.json with only enable property to typeAcquisition ", () => {
assertTypeAcquisition(
{
"typeAcquisition":
{
"enable": true
}
}, "tsconfig.json",
{
typeAcquisition:
{
enable: true,
include: [],
exclude: []
},
errors: <Diagnostic[]>[]
});
});
// jsconfig.json
it("Convert jsconfig.json to typeAcquisition ", () => {
assertTypeAcquisition(
{
"typeAcquisition":
{
"enable": false,
"include": ["0.d.ts"],
"exclude": ["0.js"]
}
}, "jsconfig.json",
{
typeAcquisition:
{
enable: false,
include: ["0.d.ts"],
exclude: ["0.js"]
},
errors: <Diagnostic[]>[]
});
});
it("Convert default jsconfig.json to typeAcquisition ", () => {
assertTypeAcquisition({ }, "jsconfig.json",
{
typeAcquisition:
{
enable: true,
include: [],
exclude: []
},
errors: <Diagnostic[]>[]
});
});
it("Convert incorrect format jsconfig.json to typeAcquisition ", () => {
assertTypeAcquisition(
{
"typeAcquisition":
{
"enableAutoDiscovy": true,
}
}, "jsconfig.json",
{
typeAcquisition:
{
enable: true,
include: [],
exclude: []
},
errors: [
{
category: Diagnostics.Unknown_compiler_option_0.category,
code: Diagnostics.Unknown_type_acquisition_option_0.code,
file: undefined,
start: 0,
length: 0,
messageText: undefined
}
]
});
});
it("Convert jsconfig.json with only enable property to typeAcquisition ", () => {
assertTypeAcquisition(
{
"typeAcquisition":
{
"enable": false
}
}, "jsconfig.json",
{
typeAcquisition:
{
enable: false,
include: [],
exclude: []
},
errors: <Diagnostic[]>[]
});
});
});
}
+86
View File
@@ -0,0 +1,86 @@
/// <reference path="..\..\compiler\emitter.ts" />
/// <reference path="..\harness.ts" />
namespace ts {
describe("customTransforms", () => {
function emitsCorrectly(name: string, sources: { file: string, text: string }[], customTransformers: CustomTransformers) {
it(name, () => {
const roots = sources.map(source => createSourceFile(source.file, source.text, ScriptTarget.ES2015));
const fileMap = arrayToMap(roots, file => file.fileName);
const outputs = createMap<string>();
const options: CompilerOptions = {};
const host: CompilerHost = {
getSourceFile: (fileName) => fileMap.get(fileName),
getDefaultLibFileName: () => "lib.d.ts",
getCurrentDirectory: () => "",
getDirectories: () => [],
getCanonicalFileName: (fileName) => fileName,
useCaseSensitiveFileNames: () => true,
getNewLine: () => "\n",
fileExists: (fileName) => fileMap.has(fileName),
readFile: (fileName) => fileMap.has(fileName) ? fileMap.get(fileName).text : undefined,
writeFile: (fileName, text) => outputs.set(fileName, text),
};
const program = createProgram(arrayFrom(fileMap.keys()), options, host);
program.emit(/*targetSourceFile*/ undefined, host.writeFile, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ false, customTransformers);
Harness.Baseline.runBaseline(`customTransforms/${name}.js`, () => {
let content = "";
for (const [file, text] of arrayFrom(outputs.entries())) {
if (content) content += "\n\n";
content += `// [${file}]\n`;
content += text;
}
return content;
});
});
}
const sources = [{
file: "source.ts",
text: `
function f1() { }
class c() { }
enum e { }
// leading
function f2() { } // trailing
`
}];
const before: TransformerFactory<SourceFile> = context => {
return file => visitEachChild(file, visit, context);
function visit(node: Node): VisitResult<Node> {
switch (node.kind) {
case SyntaxKind.FunctionDeclaration:
return visitFunction(<FunctionDeclaration>node);
default:
return visitEachChild(node, visit, context);
}
}
function visitFunction(node: FunctionDeclaration) {
addSyntheticLeadingComment(node, SyntaxKind.MultiLineCommentTrivia, "@before", /*hasTrailingNewLine*/ true);
return node;
}
};
const after: TransformerFactory<SourceFile> = context => {
return file => visitEachChild(file, visit, context);
function visit(node: Node): VisitResult<Node> {
switch (node.kind) {
case SyntaxKind.VariableStatement:
return visitVariableStatement(<VariableStatement>node);
default:
return visitEachChild(node, visit, context);
}
}
function visitVariableStatement(node: VariableStatement) {
addSyntheticLeadingComment(node, SyntaxKind.SingleLineCommentTrivia, "@after");
return node;
}
};
emitsCorrectly("before", sources, { before: [before] });
emitsCorrectly("after", sources, { after: [after] });
emitsCorrectly("both", sources, { before: [before], after: [after] });
});
}
+840
View File
@@ -0,0 +1,840 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\..\compiler\parser.ts" />
namespace ts {
ts.disableIncrementalParsing = false;
function withChange(text: IScriptSnapshot, start: number, length: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } {
const contents = text.getText(0, text.getLength());
const newContents = contents.substr(0, start) + newText + contents.substring(start + length);
return { text: ScriptSnapshot.fromString(newContents), textChangeRange: createTextChangeRange(createTextSpan(start, length), newText.length) };
}
function withInsert(text: IScriptSnapshot, start: number, newText: string): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } {
return withChange(text, start, 0, newText);
}
function withDelete(text: IScriptSnapshot, start: number, length: number): { text: IScriptSnapshot; textChangeRange: TextChangeRange; } {
return withChange(text, start, length, "");
}
function createTree(text: IScriptSnapshot, version: string) {
return createLanguageServiceSourceFile(/*fileName:*/ "", text, ScriptTarget.Latest, version, /*setNodeParents:*/ true);
}
function assertSameDiagnostics(file1: SourceFile, file2: SourceFile) {
const diagnostics1 = file1.parseDiagnostics;
const diagnostics2 = file2.parseDiagnostics;
assert.equal(diagnostics1.length, diagnostics2.length, "diagnostics1.length !== diagnostics2.length");
for (let i = 0; i < diagnostics1.length; i++) {
const d1 = diagnostics1[i];
const d2 = diagnostics2[i];
assert.equal(d1.file, file1, "d1.file !== file1");
assert.equal(d2.file, file2, "d2.file !== file2");
assert.equal(d1.start, d2.start, "d1.start !== d2.start");
assert.equal(d1.length, d2.length, "d1.length !== d2.length");
assert.equal(d1.messageText, d2.messageText, "d1.messageText !== d2.messageText");
assert.equal(d1.category, d2.category, "d1.category !== d2.category");
assert.equal(d1.code, d2.code, "d1.code !== d2.code");
}
}
// NOTE: 'reusedElements' is the expected count of elements reused from the old tree to the new
// tree. It may change as we tweak the parser. If the count increases then that should always
// be a good thing. If it decreases, that's not great (less reusability), but that may be
// unavoidable. If it does decrease an investigation should be done to make sure that things
// are still ok and we're still appropriately reusing most of the tree.
function compareTrees(oldText: IScriptSnapshot, newText: IScriptSnapshot, textChangeRange: TextChangeRange, expectedReusedElements: number, oldTree?: SourceFile) {
oldTree = oldTree || createTree(oldText, /*version:*/ ".");
Utils.assertInvariants(oldTree, /*parent:*/ undefined);
// Create a tree for the new text, in a non-incremental fashion.
const newTree = createTree(newText, oldTree.version + ".");
Utils.assertInvariants(newTree, /*parent:*/ undefined);
// Create a tree for the new text, in an incremental fashion.
const incrementalNewTree = updateLanguageServiceSourceFile(oldTree, newText, oldTree.version + ".", textChangeRange);
Utils.assertInvariants(incrementalNewTree, /*parent:*/ undefined);
// We should get the same tree when doign a full or incremental parse.
Utils.assertStructuralEquals(newTree, incrementalNewTree);
// We should also get the exact same set of diagnostics.
assertSameDiagnostics(newTree, incrementalNewTree);
// There should be no reused nodes between two trees that are fully parsed.
assert.isTrue(reusedElements(oldTree, newTree) === 0);
assert.equal(newTree.fileName, incrementalNewTree.fileName, "newTree.fileName !== incrementalNewTree.fileName");
assert.equal(newTree.text, incrementalNewTree.text, "newTree.text !== incrementalNewTree.text");
if (expectedReusedElements !== -1) {
const actualReusedCount = reusedElements(oldTree, incrementalNewTree);
assert.equal(actualReusedCount, expectedReusedElements, actualReusedCount + " !== " + expectedReusedElements);
}
return { oldTree, newTree, incrementalNewTree };
}
function reusedElements(oldNode: SourceFile, newNode: SourceFile): number {
const allOldElements = collectElements(oldNode);
const allNewElements = collectElements(newNode);
return filter(allOldElements, v => contains(allNewElements, v)).length;
}
function collectElements(node: Node) {
const result: Node[] = [];
visit(node);
return result;
function visit(node: Node) {
result.push(node);
forEachChild(node, visit);
}
}
function deleteCode(source: string, index: number, toDelete: string) {
const repeat = toDelete.length;
let oldTree = createTree(ScriptSnapshot.fromString(source), /*version:*/ ".");
for (let i = 0; i < repeat; i++) {
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, index, 1);
const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree;
source = newTextAndChange.text.getText(0, newTextAndChange.text.getLength());
oldTree = newTree;
}
}
function insertCode(source: string, index: number, toInsert: string) {
const repeat = toInsert.length;
let oldTree = createTree(ScriptSnapshot.fromString(source), /*version:*/ ".");
for (let i = 0; i < repeat; i++) {
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, index + i, toInsert.charAt(i));
const newTree = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1, oldTree).incrementalNewTree;
source = newTextAndChange.text.getText(0, newTextAndChange.text.getLength());
oldTree = newTree;
}
}
describe("Incremental", () => {
it("Inserting into method", () => {
const source = "class C {\r\n" +
" public foo1() { }\r\n" +
" public foo2() {\r\n" +
" return 1;\r\n" +
" }\r\n" +
" public foo3() { }\r\n" +
"}";
const oldText = ScriptSnapshot.fromString(source);
const semicolonIndex = source.indexOf(";");
const newTextAndChange = withInsert(oldText, semicolonIndex, " + 1");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8);
});
it("Deleting from method", () => {
const source = "class C {\r\n" +
" public foo1() { }\r\n" +
" public foo2() {\r\n" +
" return 1 + 1;\r\n" +
" }\r\n" +
" public foo3() { }\r\n" +
"}";
const index = source.indexOf("+ 1");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, index, "+ 1".length);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8);
});
it("Regular expression 1", () => {
const source = "class C { public foo1() { /; } public foo2() { return 1;} public foo3() { } }";
const semicolonIndex = source.indexOf(";}");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, semicolonIndex, "/");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Regular expression 2", () => {
const source = "class C { public foo1() { ; } public foo2() { return 1/;} public foo3() { } }";
const semicolonIndex = source.indexOf(";");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, semicolonIndex, "/");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4);
});
it("Comment 1", () => {
const source = "class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }";
const semicolonIndex = source.indexOf(";");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, semicolonIndex, "/");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Comment 2", () => {
const source = "class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, 0, "//");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Comment 3", () => {
const source = "//class C { public foo1() { /; } public foo2() { return 1; } public foo3() { } }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, 0, 2);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Comment 4", () => {
const source = "class C { public foo1() { /; } public foo2() { */ return 1; } public foo3() { } }";
const index = source.indexOf(";");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, index, "*");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4);
});
it("Parameter 1", () => {
// Should be able to reuse all the parameters.
const source = "class C {\r\n" +
" public foo2(a, b, c, d) {\r\n" +
" return 1;\r\n" +
" }\r\n" +
"}";
const semicolonIndex = source.indexOf(";");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, semicolonIndex, " + 1");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 8);
});
it("Type member 1", () => {
// Should be able to reuse most of the type members.
const source = "interface I { a: number; b: string; (c): d; new (e): f; g(): h }";
const index = source.indexOf(": string");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, index, "?");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14);
});
it("Enum element 1", () => {
// Should be able to reuse most of the enum elements.
const source = "enum E { a = 1, b = 1 << 1, c = 3, e = 4, f = 5, g = 7, h = 8, i = 9, j = 10 }";
const index = source.indexOf("<<");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, index, 2, "+");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 24);
});
it("Strict mode 1", () => {
const source = "foo1();\r\nfoo1();\r\nfoo1();\r\package();";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, 0, "'strict';\r\n");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9);
});
it("Strict mode 2", () => {
const source = "foo1();\r\nfoo1();\r\nfoo1();\r\package();";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, 0, "'use strict';\r\n");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9);
});
it("Strict mode 3", () => {
const source = "'strict';\r\nfoo1();\r\nfoo1();\r\nfoo1();\r\npackage();";
const index = source.indexOf("f");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, 0, index);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9);
});
it("Strict mode 4", () => {
const source = "'use strict';\r\nfoo1();\r\nfoo1();\r\nfoo1();\r\npackage();";
const index = source.indexOf("f");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, 0, index);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9);
});
it("Strict mode 5", () => {
const source = "'use blahhh';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n";
const index = source.indexOf("b");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, index, 6, "strict");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 27);
});
it("Strict mode 6", () => {
const source = "'use strict';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n";
const index = source.indexOf("s");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, index, 6, "blahhh");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 27);
});
it("Strict mode 7", () => {
const source = "'use blahhh';\r\nfoo1();\r\nfoo2();\r\nfoo3();\r\nfoo4();\r\nfoo4();\r\nfoo6();\r\nfoo7();\r\nfoo8();\r\nfoo9();\r\n";
const index = source.indexOf("f");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, 0, index);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 24);
});
it("Parenthesized expression to arrow function 1", () => {
const source = "var v = (a, b, c, d, e)";
const index = source.indexOf("a");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, index + 1, ":");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Parenthesized expression to arrow function 2", () => {
const source = "var v = (a, b) = c";
const index = source.indexOf("= c") + 1;
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, index, ">");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Arrow function to parenthesized expression 1", () => {
const source = "var v = (a:, b, c, d, e)";
const index = source.indexOf(":");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, index, 1);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Arrow function to parenthesized expression 2", () => {
const source = "var v = (a, b) => c";
const index = source.indexOf(">");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, index, 1);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Speculative generic lookahead 1", () => {
const source = "var v = F<b>e";
const index = source.indexOf("b");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, index + 1, ",x");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1);
});
it("Speculative generic lookahead 2", () => {
const source = "var v = F<a,b>e";
const index = source.indexOf("b");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, index + 1, ",x");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1);
});
it("Speculative generic lookahead 3", () => {
const source = "var v = F<a,b,c>e";
const index = source.indexOf("b");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, index + 1, ",x");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1);
});
it("Speculative generic lookahead 4", () => {
const source = "var v = F<a,b,c,d>e";
const index = source.indexOf("b");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, index + 1, ",x");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1);
});
it("Assertion to arrow function", () => {
const source = "var v = <T>(a);";
const index = source.indexOf(";");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, index, " => 1");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Arrow function to assertion", () => {
const source = "var v = <T>(a) => 1;";
const index = source.indexOf(" =>");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, index, " => 1".length);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Contextual shift to shift-equals", () => {
const source = "var v = 1 >> = 2";
const index = source.indexOf(">> =");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, index + 2, 1);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Contextual shift-equals to shift", () => {
const source = "var v = 1 >>= 2";
const index = source.indexOf(">>=");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, index + 2, " ");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Contextual shift to generic invocation", () => {
const source = "var v = T>>(2)";
const index = source.indexOf("T");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, index, "Foo<Bar<");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Test generic invocation to contextual shift", () => {
const source = "var v = Foo<Bar<T>>(2)";
const index = source.indexOf("Foo<Bar<");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, index, "Foo<Bar<".length);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Contextual shift to generic type and initializer", () => {
const source = "var v = T>>=2;";
const index = source.indexOf("=");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, index, "= ".length, ": Foo<Bar<");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Generic type and initializer to contextual shift", () => {
const source = "var v : Foo<Bar<T>>=2;";
const index = source.indexOf(":");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, index, ": Foo<Bar<".length, "= ");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Arithmetic operator to type argument list", () => {
const source = "var v = new Dictionary<A, B>0";
const index = source.indexOf("0");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, index, 1, "()");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Type argument list to arithmetic operator", () => {
const source = "var v = new Dictionary<A, B>()";
const index = source.indexOf("()");
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, index, 2);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Yield context 1", () => {
// We're changing from a non-generator to a genarator. We can't reuse statement nodes.
const source = "function foo() {\r\nyield(foo1);\r\n}";
const oldText = ScriptSnapshot.fromString(source);
const index = source.indexOf("foo");
const newTextAndChange = withInsert(oldText, index, "*");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Yield context 2", () => {
// We're changing from a generator to a non-genarator. We can't reuse statement nodes.
const source = "function *foo() {\r\nyield(foo1);\r\n}";
const oldText = ScriptSnapshot.fromString(source);
const index = source.indexOf("*");
const newTextAndChange = withDelete(oldText, index, "*".length);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Delete semicolon", () => {
const source = "export class Foo {\r\n}\r\n\r\nexport var foo = new Foo();\r\n\r\n export function test(foo: Foo) {\r\n return true;\r\n }\r\n";
const oldText = ScriptSnapshot.fromString(source);
const index = source.lastIndexOf(";");
const newTextAndChange = withDelete(oldText, index, 1);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14);
});
it("Edit after empty type parameter list", () => {
const source = "class Dictionary<> { }\r\nvar y;\r\n";
const oldText = ScriptSnapshot.fromString(source);
const index = source.length;
const newTextAndChange = withInsert(oldText, index, "var x;");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 2);
});
it("Delete parameter after comment", () => {
const source = "function fn(/* comment! */ a: number, c) { }";
const oldText = ScriptSnapshot.fromString(source);
const index = source.indexOf("a:");
const newTextAndChange = withDelete(oldText, index, "a: number,".length);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Modifier added to accessor", () => {
const source =
"class C {\
set Bar(bar:string) {}\
}\
var o2 = { set Foo(val:number) { } };";
const oldText = ScriptSnapshot.fromString(source);
const index = source.indexOf("set");
const newTextAndChange = withInsert(oldText, index, "public ");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 14);
});
it("Insert parameter ahead of parameter", () => {
const source =
"alert(100);\
\
class OverloadedMonster {\
constructor();\
constructor(name) { }\
}";
const oldText = ScriptSnapshot.fromString(source);
const index = source.indexOf("100");
const newTextAndChange = withInsert(oldText, index, "'1', ");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 7);
});
it("Insert declare modifier before module", () => {
const source =
"module mAmbient {\
module m3 { }\
}";
const oldText = ScriptSnapshot.fromString(source);
const index = 0;
const newTextAndChange = withInsert(oldText, index, "declare ");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 3);
});
it("Insert function above arrow function with comment", () => {
const source =
"\
() =>\
// do something\
0;";
const oldText = ScriptSnapshot.fromString(source);
const index = 0;
const newTextAndChange = withInsert(oldText, index, "function Foo() { }");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Finish incomplete regular expression", () => {
const source = "while (true) /3; return;";
const oldText = ScriptSnapshot.fromString(source);
const index = source.length - 1;
const newTextAndChange = withInsert(oldText, index, "/");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Regular expression to divide operation", () => {
const source = "return;\r\nwhile (true) /3/g;";
const oldText = ScriptSnapshot.fromString(source);
const index = source.indexOf("while");
const newTextAndChange = withDelete(oldText, index, "while ".length);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Divide operation to regular expression", () => {
const source = "return;\r\n(true) /3/g;";
const oldText = ScriptSnapshot.fromString(source);
const index = source.indexOf("(");
const newTextAndChange = withInsert(oldText, index, "while ");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Unterminated comment after keyword converted to identifier", () => {
// 'public' as a keyword should be incrementally unusable (because it has an
// unterminated comment). When we convert it to an identifier, that shouldn't
// change anything, and we should still get the same errors.
const source = "return; a.public /*";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, 0, "");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 7);
});
it("Class to interface", () => {
const source = "class A { public M1() { } public M2() { } public M3() { } p1 = 0; p2 = 0; p3 = 0 }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 0, "class".length, "interface");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Interface to class", () => {
const source = "interface A { M1?(); M2?(); M3?(); p1?; p2?; p3? }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 0, "interface".length, "class");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Surrounding function declarations with block", () => {
const source = "declare function F1() { } export function F2() { } declare export function F3() { }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withInsert(oldText, 0, "{");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9);
});
it("Removing block around function declarations", () => {
const source = "{ declare function F1() { } export function F2() { } declare export function F3() { }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withDelete(oldText, 0, "{".length);
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 9);
});
it("Moving methods from class to object literal", () => {
const source = "class C { public A() { } public B() { } public C() { } }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 0, "class C".length, "var v =");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Moving methods from object literal to class", () => {
const source = "var v = { public A() { } public B() { } public C() { } }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4);
});
it("Moving methods from object literal to class in strict mode", () => {
const source = "\"use strict\"; var v = { public A() { } public B() { } public C() { } }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 14, "var v =".length, "class C");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4);
});
it("Do not move constructors from class to object-literal.", () => {
const source = "class C { public constructor() { } public constructor() { } public constructor() { } }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 0, "class C".length, "var v =");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Do not move methods called \"constructor\" from object literal to class", () => {
const source = "var v = { public constructor() { } public constructor() { } public constructor() { } }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Moving index signatures from class to interface", () => {
const source = "class C { public [a: number]: string; public [a: number]: string; public [a: number]: string }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 0, "class".length, "interface");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18);
});
it("Moving index signatures from class to interface in strict mode", () => {
const source = "\"use strict\"; class C { public [a: number]: string; public [a: number]: string; public [a: number]: string }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 14, "class".length, "interface");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18);
});
it("Moving index signatures from interface to class", () => {
const source = "interface C { public [a: number]: string; public [a: number]: string; public [a: number]: string }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 0, "interface".length, "class");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18);
});
it("Moving index signatures from interface to class in strict mode", () => {
const source = "\"use strict\"; interface C { public [a: number]: string; public [a: number]: string; public [a: number]: string }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 14, "interface".length, "class");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 18);
});
it("Moving accessors from class to object literal", () => {
const source = "class C { public get A() { } public get B() { } public get C() { } }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 0, "class C".length, "var v =");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 0);
});
it("Moving accessors from object literal to class", () => {
const source = "var v = { public get A() { } public get B() { } public get C() { } }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 0, "var v =".length, "class C");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4);
});
it("Moving accessors from object literal to class in strict mode", () => {
const source = "\"use strict\"; var v = { public get A() { } public get B() { } public get C() { } }";
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 14, "var v =".length, "class C");
compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, 4);
});
it("Reuse transformFlags of subtree during bind", () => {
const source = `class Greeter { constructor(element: HTMLElement) { } }`;
const oldText = ScriptSnapshot.fromString(source);
const newTextAndChange = withChange(oldText, 15, 0, "\n");
const { oldTree, incrementalNewTree } = compareTrees(oldText, newTextAndChange.text, newTextAndChange.textChangeRange, -1);
bindSourceFile(oldTree, {});
bindSourceFile(incrementalNewTree, {});
assert.equal(oldTree.transformFlags, incrementalNewTree.transformFlags);
});
// Simulated typing tests.
it("Type extends clause 1", () => {
const source = "interface IFoo<T> { }\r\ninterface Array<T> extends IFoo<T> { }";
const index = source.indexOf("extends");
deleteCode(source, index, "extends IFoo<T>");
});
it("Type after incomplete enum 1", () => {
const source = "function foo() {\r\n" +
" function getOccurrencesAtPosition() {\r\n" +
" switch (node) {\r\n" +
" enum \r\n" +
" }\r\n" +
" \r\n" +
" return undefined;\r\n" +
" \r\n" +
" function keywordToReferenceEntry() {\r\n" +
" }\r\n" +
" }\r\n" +
" \r\n" +
" return {\r\n" +
" getEmitOutput: (fileName): Bar => null,\r\n" +
" };\r\n" +
" }";
const index = source.indexOf("enum ") + "enum ".length;
insertCode(source, index, "Fo");
});
});
}
@@ -0,0 +1,34 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\..\compiler\commandLineParser.ts" />
namespace ts {
describe("initTSConfig", () => {
function initTSConfigCorrectly(name: string, commandLinesArgs: string[]) {
describe(name, () => {
const commandLine = parseCommandLine(commandLinesArgs);
const initResult = generateTSConfig(commandLine.options, commandLine.fileNames, "\n");
const outputFileName = `tsConfig/${name.replace(/[^a-z0-9\-. ]/ig, "")}/tsconfig.json`;
it(`Correct output for ${outputFileName}`, () => {
Harness.Baseline.runBaseline(outputFileName, () => initResult);
});
});
}
initTSConfigCorrectly("Default initialized TSConfig", ["--init"]);
initTSConfigCorrectly("Initialized TSConfig with files options", ["--init", "file0.st", "file1.ts", "file2.ts"]);
initTSConfigCorrectly("Initialized TSConfig with boolean value compiler options", ["--init", "--noUnusedLocals"]);
initTSConfigCorrectly("Initialized TSConfig with enum value compiler options", ["--init", "--target", "es5", "--jsx", "react"]);
initTSConfigCorrectly("Initialized TSConfig with list compiler options", ["--init", "--types", "jquery,mocha"]);
initTSConfigCorrectly("Initialized TSConfig with list compiler options with enum value", ["--init", "--lib", "es5,es2015.core"]);
initTSConfigCorrectly("Initialized TSConfig with incorrect compiler option", ["--init", "--someNonExistOption"]);
initTSConfigCorrectly("Initialized TSConfig with incorrect compiler option value", ["--init", "--lib", "nonExistLib,es5,es2015.promise"]);
});
}
+323
View File
@@ -0,0 +1,323 @@
/// <reference path="..\..\compiler\parser.ts" />
/// <reference path="..\harness.ts" />
namespace ts {
describe("JSDocParsing", () => {
describe("TypeExpressions", () => {
function parsesCorrectly(name: string, content: string) {
it(name, () => {
const typeAndDiagnostics = ts.parseJSDocTypeExpressionForTests(content);
assert.isTrue(typeAndDiagnostics && typeAndDiagnostics.diagnostics.length === 0, "no errors issued");
Harness.Baseline.runBaseline("JSDocParsing/TypeExpressions.parsesCorrectly." + name + ".json",
() => Utils.sourceFileToJSON(typeAndDiagnostics.jsDocTypeExpression.type));
});
}
function parsesIncorrectly(name: string, content: string) {
it(name, () => {
const type = ts.parseJSDocTypeExpressionForTests(content);
assert.isTrue(!type || type.diagnostics.length > 0);
});
}
describe("parseCorrectly", () => {
parsesCorrectly("unknownType", "{?}");
parsesCorrectly("allType", "{*}");
parsesCorrectly("nullableType", "{?number}");
parsesCorrectly("nullableType2", "{number?}");
parsesCorrectly("nonNullableType", "{!number}");
parsesCorrectly("nonNullableType2", "{number!}");
parsesCorrectly("recordType1", "{{}}");
parsesCorrectly("recordType2", "{{foo}}");
parsesCorrectly("recordType3", "{{foo: number}}");
parsesCorrectly("recordType4", "{{foo, bar}}");
parsesCorrectly("recordType5", "{{foo: number, bar}}");
parsesCorrectly("recordType6", "{{foo, bar: number}}");
parsesCorrectly("recordType7", "{{foo: number, bar: number}}");
parsesCorrectly("recordType8", "{{function}}");
parsesCorrectly("trailingCommaInRecordType", "{{a,}}");
parsesCorrectly("callSignatureInRecordType", "{{(): number}}");
parsesCorrectly("methodInRecordType", "{{foo(): number}}");
parsesCorrectly("unionType", "{(number|string)}");
parsesCorrectly("topLevelNoParenUnionType", "{number|string}");
parsesCorrectly("functionType1", "{function()}");
parsesCorrectly("functionType2", "{function(string, boolean)}");
parsesCorrectly("functionReturnType1", "{function(string, boolean)}");
parsesCorrectly("thisType1", "{this:a.b}");
parsesCorrectly("newType1", "{new:a.b}");
parsesCorrectly("variadicType", "{...number}");
parsesCorrectly("optionalType", "{number=}");
parsesCorrectly("optionalNullable", "{?=}");
parsesCorrectly("typeReference1", "{a.<number>}");
parsesCorrectly("typeReference2", "{a.<number,string>}");
parsesCorrectly("typeReference3", "{a.function}");
parsesCorrectly("arrayType1", "{a[]}");
parsesCorrectly("arrayType2", "{a[][]}");
parsesCorrectly("arrayType3", "{a[][]=}");
parsesCorrectly("keyword1", "{var}");
parsesCorrectly("keyword2", "{null}");
parsesCorrectly("keyword3", "{undefined}");
parsesCorrectly("tupleType0", "{[]}");
parsesCorrectly("tupleType1", "{[number]}");
parsesCorrectly("tupleType2", "{[number,string]}");
parsesCorrectly("tupleType3", "{[number,string,boolean]}");
});
describe("parsesIncorrectly", () => {
parsesIncorrectly("emptyType", "{}");
parsesIncorrectly("unionTypeWithTrailingBar", "{(a|)}");
parsesIncorrectly("unionTypeWithoutTypes", "{()}");
parsesIncorrectly("nullableTypeWithoutType", "{!}");
parsesIncorrectly("functionTypeWithTrailingComma", "{function(a,)}");
parsesIncorrectly("thisWithoutType", "{this:}");
parsesIncorrectly("newWithoutType", "{new:}");
parsesIncorrectly("variadicWithoutType", "{...}");
parsesIncorrectly("optionalWithoutType", "{=}");
parsesIncorrectly("allWithType", "{*foo}");
parsesIncorrectly("typeArgumentsNotFollowingDot", "{a<>}");
parsesIncorrectly("emptyTypeArguments", "{a.<>}");
parsesIncorrectly("typeArgumentsWithTrailingComma", "{a.<a,>}");
parsesIncorrectly("tsFunctionType", "{() => string}");
parsesIncorrectly("tsConstructoType", "{new () => string}");
parsesIncorrectly("typeOfType", "{typeof M}");
parsesIncorrectly("namedParameter", "{function(a: number)}");
parsesIncorrectly("tupleTypeWithComma", "{[,]}");
parsesIncorrectly("tupleTypeWithTrailingComma", "{[number,]}");
parsesIncorrectly("tupleTypeWithLeadingComma", "{[,number]}");
});
});
describe("DocComments", () => {
function parsesCorrectly(name: string, content: string) {
it(name, () => {
const comment = parseIsolatedJSDocComment(content);
if (!comment) {
Debug.fail("Comment failed to parse entirely");
}
if (comment.diagnostics.length > 0) {
Debug.fail("Comment has at least one diagnostic: " + comment.diagnostics[0].messageText);
}
Harness.Baseline.runBaseline("JSDocParsing/DocComments.parsesCorrectly." + name + ".json",
() => JSON.stringify(comment.jsDoc,
(_, v) => v && v.pos !== undefined ? JSON.parse(Utils.sourceFileToJSON(v)) : v, 4));
});
}
function parsesIncorrectly(name: string, content: string) {
it(name, () => {
const type = parseIsolatedJSDocComment(content);
assert.isTrue(!type || type.diagnostics.length > 0);
});
}
describe("parsesIncorrectly", () => {
parsesIncorrectly("emptyComment", "/***/");
parsesIncorrectly("threeAsterisks", "/*** */");
parsesIncorrectly("multipleTypes",
`/**
* @type {number}
* @type {string}
*/`);
parsesIncorrectly("multipleReturnTypes",
`/**
* @return {number}
* @return {string}
*/`);
parsesIncorrectly("noTypeParameters",
`/**
* @template
*/`);
parsesIncorrectly("trailingTypeParameterComma",
`/**
* @template T,
*/`);
parsesIncorrectly("paramWithoutName",
`/**
* @param {number}
*/`);
parsesIncorrectly("paramWithoutTypeOrName",
`/**
* @param
*/`);
});
describe("parsesCorrectly", () => {
parsesCorrectly("noLeadingAsterisk",
`/**
@type {number}
*/`);
parsesCorrectly("noType",
`/**
* @type
*/`);
parsesCorrectly("noReturnType",
`/**
* @return
*/`);
parsesCorrectly("leadingAsterisk",
`/**
* @type {number}
*/`);
parsesCorrectly("asteriskAfterPreamble", "/** * @type {number} */");
parsesCorrectly("typeTag",
`/**
* @type {number}
*/`);
parsesCorrectly("returnTag1",
`/**
* @return {number}
*/`);
parsesCorrectly("returnTag2",
`/**
* @return {number} Description text follows
*/`);
parsesCorrectly("returnsTag1",
`/**
* @returns {number}
*/`);
parsesCorrectly("oneParamTag",
`/**
* @param {number} name1
*/`);
parsesCorrectly("twoParamTag2",
`/**
* @param {number} name1
* @param {number} name2
*/`);
parsesCorrectly("paramTag1",
`/**
* @param {number} name1 Description text follows
*/`);
parsesCorrectly("paramTagBracketedName1",
`/**
* @param {number} [name1] Description text follows
*/`);
parsesCorrectly("paramTagBracketedName2",
`/**
* @param {number} [ name1 = 1] Description text follows
*/`);
parsesCorrectly("twoParamTagOnSameLine",
`/**
* @param {number} name1 @param {number} name2
*/`);
parsesCorrectly("paramTagNameThenType1",
`/**
* @param name1 {number}
*/`);
parsesCorrectly("paramTagNameThenType2",
`/**
* @param name1 {number} Description
*/`);
parsesCorrectly("argSynonymForParamTag",
`/**
* @arg {number} name1 Description
*/`);
parsesCorrectly("argumentSynonymForParamTag",
`/**
* @argument {number} name1 Description
*/`);
parsesCorrectly("templateTag",
`/**
* @template T
*/`);
parsesCorrectly("templateTag2",
`/**
* @template K,V
*/`);
parsesCorrectly("templateTag3",
`/**
* @template K ,V
*/`);
parsesCorrectly("templateTag4",
`/**
* @template K, V
*/`);
parsesCorrectly("templateTag5",
`/**
* @template K , V
*/`);
parsesCorrectly("templateTag6",
`/**
* @template K , V Description of type parameters.
*/`);
parsesCorrectly("paramWithoutType",
`/**
* @param foo
*/`);
parsesCorrectly("typedefTagWithChildrenTags",
`/**
* @typedef People
* @type {Object}
* @property {number} age
* @property {string} name
*/`);
});
});
describe("getFirstToken", () => {
it("gets jsdoc", () => {
const root = ts.createSourceFile("foo.ts", "/** comment */var a = true;", ts.ScriptTarget.ES5, /*setParentNodes*/ true);
assert.isDefined(root);
assert.equal(root.kind, ts.SyntaxKind.SourceFile);
const first = root.getFirstToken();
assert.isDefined(first);
assert.equal(first.kind, ts.SyntaxKind.VarKeyword);
});
});
describe("getLastToken", () => {
it("gets jsdoc", () => {
const root = ts.createSourceFile("foo.ts", "var a = true;/** comment */", ts.ScriptTarget.ES5, /*setParentNodes*/ true);
assert.isDefined(root);
const last = root.getLastToken();
assert.isDefined(last);
assert.equal(last.kind, ts.SyntaxKind.EndOfFileToken);
});
});
});
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+105
View File
@@ -0,0 +1,105 @@
/// <reference path="..\..\compiler\emitter.ts" />
/// <reference path="..\harness.ts" />
namespace ts {
describe("PrinterAPI", () => {
function makePrintsCorrectly(prefix: string) {
return function printsCorrectly(name: string, options: PrinterOptions, printCallback: (printer: Printer) => string) {
it(name, () => {
Harness.Baseline.runBaseline(`printerApi/${prefix}.${name}.js`, () =>
printCallback(createPrinter({ newLine: NewLineKind.CarriageReturnLineFeed, ...options })));
});
};
}
describe("printFile", () => {
const printsCorrectly = makePrintsCorrectly("printsFileCorrectly");
const sourceFile = createSourceFile("source.ts", `
interface A<T> {
// comment1
readonly prop?: T;
// comment2
method(): void;
// comment3
new <T>(): A<T>;
// comment4
<T>(): A<T>;
}
// comment5
type B = number | string | object;
type C = A<number> & { x: string; }; // comment6
// comment7
enum E1 {
// comment8
first
}
const enum E2 {
second
}
// comment9
console.log(1 + 2);
// comment10
function functionWithDefaultArgValue(argument: string = "defaultValue"): void { }
`, ScriptTarget.ES2015);
printsCorrectly("default", {}, printer => printer.printFile(sourceFile));
printsCorrectly("removeComments", { removeComments: true }, printer => printer.printFile(sourceFile));
// github #14948
printsCorrectly("templateLiteral", {}, printer => printer.printFile(createSourceFile("source.ts", "let greeting = `Hi ${name}, how are you?`;", ScriptTarget.ES2017)));
});
describe("printBundle", () => {
const printsCorrectly = makePrintsCorrectly("printsBundleCorrectly");
const bundle = createBundle([
createSourceFile("a.ts", `
/*! [a.ts] */
// comment0
const a = 1;
`, ScriptTarget.ES2015),
createSourceFile("b.ts", `
/*! [b.ts] */
// comment1
const b = 2;
`, ScriptTarget.ES2015)
]);
printsCorrectly("default", {}, printer => printer.printBundle(bundle));
printsCorrectly("removeComments", { removeComments: true }, printer => printer.printBundle(bundle));
});
describe("printNode", () => {
const printsCorrectly = makePrintsCorrectly("printsNodeCorrectly");
const sourceFile = createSourceFile("source.ts", "", ScriptTarget.ES2015);
// tslint:disable boolean-trivia
const syntheticNode = createClassDeclaration(
undefined,
undefined,
/*name*/ createIdentifier("C"),
undefined,
undefined,
createNodeArray([
createProperty(
undefined,
createNodeArray([createToken(SyntaxKind.PublicKeyword)]),
createIdentifier("prop"),
undefined,
undefined,
undefined
)
])
);
// tslint:enable boolean-trivia
printsCorrectly("class", {}, printer => printer.printNode(EmitHint.Unspecified, syntheticNode, sourceFile));
});
});
}
+186
View File
@@ -0,0 +1,186 @@
/// <reference path="../harness.ts" />
/// <reference path="./tsserverProjectSystem.ts" />
/// <reference path="../../server/typingsInstaller/typingsInstaller.ts" />
namespace ts.projectSystem {
describe("Project errors", () => {
function checkProjectErrors(projectFiles: server.ProjectFilesWithTSDiagnostics, expectedErrors: string[]) {
assert.isTrue(projectFiles !== undefined, "missing project files");
const errors = projectFiles.projectErrors;
assert.equal(errors ? errors.length : 0, expectedErrors.length, `expected ${expectedErrors.length} error in the list`);
if (expectedErrors.length) {
for (let i = 0; i < errors.length; i++) {
const actualMessage = flattenDiagnosticMessageText(errors[i].messageText, "\n");
const expectedMessage = expectedErrors[i];
assert.isTrue(actualMessage.indexOf(expectedMessage) === 0, `error message does not match, expected ${actualMessage} to start with ${expectedMessage}`);
}
}
}
it("external project - diagnostics for missing files", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/lib.ts",
content: ""
};
// only file1 exists - expect error
const host = createServerHost([file1]);
const projectService = createProjectService(host);
const projectFileName = "/a/b/test.csproj";
{
projectService.openExternalProject({
projectFileName,
options: {},
rootFiles: toExternalFiles([file1.path, file2.path])
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
const knownProjects = projectService.synchronizeProjectList([]);
checkProjectErrors(knownProjects[0], ["File '/a/b/lib.ts' not found."]);
}
// only file2 exists - expect error
host.reloadFS([file2]);
{
projectService.openExternalProject({
projectFileName,
options: {},
rootFiles: toExternalFiles([file1.path, file2.path])
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
const knownProjects = projectService.synchronizeProjectList([]);
checkProjectErrors(knownProjects[0], ["File '/a/b/app.ts' not found."]);
}
// both files exist - expect no errors
host.reloadFS([file1, file2]);
{
projectService.openExternalProject({
projectFileName,
options: {},
rootFiles: toExternalFiles([file1.path, file2.path])
});
projectService.checkNumberOfProjects({ externalProjects: 1 });
const knownProjects = projectService.synchronizeProjectList([]);
checkProjectErrors(knownProjects[0], []);
}
});
it("configured projects - diagnostics for missing files", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/lib.ts",
content: ""
};
const config = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const host = createServerHost([file1, config]);
const projectService = createProjectService(host);
projectService.openClientFile(file1.path);
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectErrors(projectService.synchronizeProjectList([])[0], ["File '/a/b/lib.ts' not found."]);
host.reloadFS([file1, file2, config]);
projectService.openClientFile(file1.path);
projectService.checkNumberOfProjects({ configuredProjects: 1 });
checkProjectErrors(projectService.synchronizeProjectList([])[0], []);
});
it("configured projects - diagnostics for corrupted config 1", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/lib.ts",
content: ""
};
const correctConfig = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const corruptedConfig = {
path: correctConfig.path,
content: correctConfig.content.substr(1)
};
const host = createServerHost([file1, file2, corruptedConfig]);
const projectService = createProjectService(host);
projectService.openClientFile(file1.path);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, [
"')' expected.",
"Declaration or statement expected.",
"Declaration or statement expected.",
"Failed to parse file '/a/b/tsconfig.json'"
]);
}
// fix config and trigger watcher
host.reloadFS([file1, file2, correctConfig]);
host.triggerFileWatcherCallback(correctConfig.path, /*false*/);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
}
});
it("configured projects - diagnostics for corrupted config 2", () => {
const file1 = {
path: "/a/b/app.ts",
content: ""
};
const file2 = {
path: "/a/b/lib.ts",
content: ""
};
const correctConfig = {
path: "/a/b/tsconfig.json",
content: JSON.stringify({ files: [file1, file2].map(f => getBaseFileName(f.path)) })
};
const corruptedConfig = {
path: correctConfig.path,
content: correctConfig.content.substr(1)
};
const host = createServerHost([file1, file2, correctConfig]);
const projectService = createProjectService(host);
projectService.openClientFile(file1.path);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, []);
}
// break config and trigger watcher
host.reloadFS([file1, file2, corruptedConfig]);
host.triggerFileWatcherCallback(corruptedConfig.path, /*false*/);
{
projectService.checkNumberOfProjects({ configuredProjects: 1 });
const configuredProject = forEach(projectService.synchronizeProjectList([]), f => f.info.projectName === corruptedConfig.path && f);
assert.isTrue(configuredProject !== undefined, "should find configured project");
checkProjectErrors(configuredProject, [
"')' expected.",
"Declaration or statement expected.",
"Declaration or statement expected.",
"Failed to parse file '/a/b/tsconfig.json'"
]);
}
});
});
}
@@ -0,0 +1,747 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\..\harness\harnessLanguageService.ts" />
namespace ts {
const enum ChangedPart {
references = 1 << 0,
importsAndExports = 1 << 1,
program = 1 << 2
}
const newLine = "\r\n";
interface SourceFileWithText extends SourceFile {
sourceText?: SourceText;
}
interface NamedSourceText {
name: string;
text: SourceText;
}
interface ProgramWithSourceTexts extends Program {
sourceTexts?: NamedSourceText[];
host: TestCompilerHost;
}
interface TestCompilerHost extends CompilerHost {
getTrace(): string[];
}
class SourceText implements IScriptSnapshot {
private fullText: string;
constructor(private references: string,
private importsAndExports: string,
private program: string,
private changedPart: ChangedPart = 0,
private version = 0) {
}
static New(references: string, importsAndExports: string, program: string): SourceText {
Debug.assert(references !== undefined);
Debug.assert(importsAndExports !== undefined);
Debug.assert(program !== undefined);
return new SourceText(references + newLine, importsAndExports + newLine, program || "");
}
public getVersion(): number {
return this.version;
}
public updateReferences(newReferences: string): SourceText {
Debug.assert(newReferences !== undefined);
return new SourceText(newReferences + newLine, this.importsAndExports, this.program, this.changedPart | ChangedPart.references, this.version + 1);
}
public updateImportsAndExports(newImportsAndExports: string): SourceText {
Debug.assert(newImportsAndExports !== undefined);
return new SourceText(this.references, newImportsAndExports + newLine, this.program, this.changedPart | ChangedPart.importsAndExports, this.version + 1);
}
public updateProgram(newProgram: string): SourceText {
Debug.assert(newProgram !== undefined);
return new SourceText(this.references, this.importsAndExports, newProgram, this.changedPart | ChangedPart.program, this.version + 1);
}
public getFullText() {
return this.fullText || (this.fullText = this.references + this.importsAndExports + this.program);
}
public getText(start: number, end: number): string {
return this.getFullText().substring(start, end);
}
getLength(): number {
return this.getFullText().length;
}
getChangeRange(oldSnapshot: IScriptSnapshot): TextChangeRange {
const oldText = <SourceText>oldSnapshot;
let oldSpan: TextSpan;
let newLength: number;
switch (oldText.changedPart ^ this.changedPart) {
case ChangedPart.references:
oldSpan = createTextSpan(0, oldText.references.length);
newLength = this.references.length;
break;
case ChangedPart.importsAndExports:
oldSpan = createTextSpan(oldText.references.length, oldText.importsAndExports.length);
newLength = this.importsAndExports.length;
break;
case ChangedPart.program:
oldSpan = createTextSpan(oldText.references.length + oldText.importsAndExports.length, oldText.program.length);
newLength = this.program.length;
break;
default:
Debug.assert(false, "Unexpected change");
}
return createTextChangeRange(oldSpan, newLength);
}
}
function createSourceFileWithText(fileName: string, sourceText: SourceText, target: ScriptTarget) {
const file = <SourceFileWithText>createSourceFile(fileName, sourceText.getFullText(), target);
file.sourceText = sourceText;
return file;
}
function createTestCompilerHost(texts: NamedSourceText[], target: ScriptTarget, oldProgram?: ProgramWithSourceTexts): TestCompilerHost {
const files = arrayToMap(texts, t => t.name, t => {
if (oldProgram) {
const oldFile = <SourceFileWithText>oldProgram.getSourceFile(t.name);
if (oldFile && oldFile.sourceText.getVersion() === t.text.getVersion()) {
return oldFile;
}
}
return createSourceFileWithText(t.name, t.text, target);
});
const trace: string[] = [];
return {
trace: s => trace.push(s),
getTrace: () => trace,
getSourceFile(fileName): SourceFile {
return files.get(fileName);
},
getDefaultLibFileName(): string {
return "lib.d.ts";
},
writeFile: notImplemented,
getCurrentDirectory(): string {
return "";
},
getDirectories(): string[] {
return [];
},
getCanonicalFileName(fileName): string {
return sys && sys.useCaseSensitiveFileNames ? fileName : fileName.toLowerCase();
},
useCaseSensitiveFileNames(): boolean {
return sys && sys.useCaseSensitiveFileNames;
},
getNewLine(): string {
return sys ? sys.newLine : newLine;
},
fileExists: fileName => files.has(fileName),
readFile: fileName => {
const file = files.get(fileName);
return file && file.text;
},
};
}
function newProgram(texts: NamedSourceText[], rootNames: string[], options: CompilerOptions): ProgramWithSourceTexts {
const host = createTestCompilerHost(texts, options.target);
const program = <ProgramWithSourceTexts>createProgram(rootNames, options, host);
program.sourceTexts = texts;
program.host = host;
return program;
}
function updateProgram(oldProgram: ProgramWithSourceTexts, rootNames: string[], options: CompilerOptions, updater: (files: NamedSourceText[]) => void, newTexts?: NamedSourceText[]) {
if (!newTexts) {
newTexts = (<ProgramWithSourceTexts>oldProgram).sourceTexts.slice(0);
}
updater(newTexts);
const host = createTestCompilerHost(newTexts, options.target, oldProgram);
const program = <ProgramWithSourceTexts>createProgram(rootNames, options, host, oldProgram);
program.sourceTexts = newTexts;
program.host = host;
return program;
}
function checkResolvedTypeDirective(expected: ResolvedTypeReferenceDirective, actual: ResolvedTypeReferenceDirective): boolean {
if (!expected === !actual) {
if (expected) {
assert.isTrue(expected.resolvedFileName === actual.resolvedFileName, `'resolvedFileName': expected '${expected.resolvedFileName}' to be equal to '${actual.resolvedFileName}'`);
assert.isTrue(expected.primary === actual.primary, `'primary': expected '${expected.primary}' to be equal to '${actual.primary}'`);
}
return true;
}
return false;
}
function checkCache<T>(caption: string, program: Program, fileName: string, expectedContent: Map<T>, getCache: (f: SourceFile) => Map<T>, entryChecker: (expected: T, original: T) => boolean): void {
const file = program.getSourceFile(fileName);
assert.isTrue(file !== undefined, `cannot find file ${fileName}`);
const cache = getCache(file);
if (expectedContent === undefined) {
assert.isTrue(cache === undefined, `expected ${caption} to be undefined`);
}
else {
assert.isTrue(cache !== undefined, `expected ${caption} to be set`);
assert.isTrue(mapsAreEqual(expectedContent, cache, entryChecker), `contents of ${caption} did not match the expected contents.`);
}
}
/** True if the maps have the same keys and values. */
function mapsAreEqual<T>(left: Map<T>, right: Map<T>, valuesAreEqual?: (left: T, right: T) => boolean): boolean {
if (left === right) return true;
if (!left || !right) return false;
const someInLeftHasNoMatch = forEachEntry(left, (leftValue, leftKey) => {
if (!right.has(leftKey)) return true;
const rightValue = right.get(leftKey);
return !(valuesAreEqual ? valuesAreEqual(leftValue, rightValue) : leftValue === rightValue);
});
if (someInLeftHasNoMatch) return false;
const someInRightHasNoMatch = forEachKey(right, rightKey => !left.has(rightKey));
return !someInRightHasNoMatch;
}
function checkResolvedModulesCache(program: Program, fileName: string, expectedContent: Map<ResolvedModule>): void {
checkCache("resolved modules", program, fileName, expectedContent, f => f.resolvedModules, checkResolvedModule);
}
function checkResolvedTypeDirectivesCache(program: Program, fileName: string, expectedContent: Map<ResolvedTypeReferenceDirective>): void {
checkCache("resolved type directives", program, fileName, expectedContent, f => f.resolvedTypeReferenceDirectiveNames, checkResolvedTypeDirective);
}
describe("Reuse program structure", () => {
const target = ScriptTarget.Latest;
const files: NamedSourceText[] = [
{
name: "a.ts", text: SourceText.New(
`
/// <reference path='b.ts'/>
/// <reference path='non-existing-file.ts'/>
/// <reference types="typerefs" />
`, "", `var x = 1`)
},
{ name: "b.ts", text: SourceText.New(`/// <reference path='c.ts'/>`, "", `var y = 2`) },
{ name: "c.ts", text: SourceText.New("", "", `var z = 1;`) },
{ name: "types/typerefs/index.d.ts", text: SourceText.New("", "", `declare let z: number;`) },
];
it("successful if change does not affect imports", () => {
const program_1 = newProgram(files, ["a.ts"], { target });
const program_2 = updateProgram(program_1, ["a.ts"], { target }, files => {
files[0].text = files[0].text.updateProgram("var x = 100");
});
assert.isTrue(program_1.structureIsReused === StructureIsReused.Completely);
const program1Diagnostics = program_1.getSemanticDiagnostics(program_1.getSourceFile("a.ts"));
const program2Diagnostics = program_2.getSemanticDiagnostics(program_1.getSourceFile("a.ts"));
assert.equal(program1Diagnostics.length, program2Diagnostics.length);
});
it("successful if change does not affect type reference directives", () => {
const program_1 = newProgram(files, ["a.ts"], { target });
const program_2 = updateProgram(program_1, ["a.ts"], { target }, files => {
files[0].text = files[0].text.updateProgram("var x = 100");
});
assert.isTrue(program_1.structureIsReused === StructureIsReused.Completely);
const program1Diagnostics = program_1.getSemanticDiagnostics(program_1.getSourceFile("a.ts"));
const program2Diagnostics = program_2.getSemanticDiagnostics(program_1.getSourceFile("a.ts"));
assert.equal(program1Diagnostics.length, program2Diagnostics.length);
});
it("fails if change affects tripleslash references", () => {
const program_1 = newProgram(files, ["a.ts"], { target });
updateProgram(program_1, ["a.ts"], { target }, files => {
const newReferences = `/// <reference path='b.ts'/>
/// <reference path='c.ts'/>
`;
files[0].text = files[0].text.updateReferences(newReferences);
});
assert.isTrue(program_1.structureIsReused === StructureIsReused.SafeModules);
});
it("fails if change affects type references", () => {
const program_1 = newProgram(files, ["a.ts"], { types: ["a"] });
updateProgram(program_1, ["a.ts"], { types: ["b"] }, noop);
assert.isTrue(program_1.structureIsReused === StructureIsReused.Not);
});
it("succeeds if change doesn't affect type references", () => {
const program_1 = newProgram(files, ["a.ts"], { types: ["a"] });
updateProgram(program_1, ["a.ts"], { types: ["a"] }, noop);
assert.isTrue(program_1.structureIsReused === StructureIsReused.Completely);
});
it("fails if change affects imports", () => {
const program_1 = newProgram(files, ["a.ts"], { target });
updateProgram(program_1, ["a.ts"], { target }, files => {
files[2].text = files[2].text.updateImportsAndExports("import x from 'b'");
});
assert.isTrue(program_1.structureIsReused === StructureIsReused.SafeModules);
});
it("fails if change affects type directives", () => {
const program_1 = newProgram(files, ["a.ts"], { target });
updateProgram(program_1, ["a.ts"], { target }, files => {
const newReferences = `
/// <reference path='b.ts'/>
/// <reference path='non-existing-file.ts'/>
/// <reference types="typerefs1" />`;
files[0].text = files[0].text.updateReferences(newReferences);
});
assert.isTrue(program_1.structureIsReused === StructureIsReused.SafeModules);
});
it("fails if module kind changes", () => {
const program_1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS });
updateProgram(program_1, ["a.ts"], { target, module: ModuleKind.AMD }, noop);
assert.isTrue(program_1.structureIsReused === StructureIsReused.Not);
});
it("fails if rootdir changes", () => {
const program_1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/b" });
updateProgram(program_1, ["a.ts"], { target, module: ModuleKind.CommonJS, rootDir: "/a/c" }, noop);
assert.isTrue(program_1.structureIsReused === StructureIsReused.Not);
});
it("fails if config path changes", () => {
const program_1 = newProgram(files, ["a.ts"], { target, module: ModuleKind.CommonJS, configFilePath: "/a/b/tsconfig.json" });
updateProgram(program_1, ["a.ts"], { target, module: ModuleKind.CommonJS, configFilePath: "/a/c/tsconfig.json" }, noop);
assert.isTrue(program_1.structureIsReused === StructureIsReused.Not);
});
it("resolution cache follows imports", () => {
(<any>Error).stackTraceLimit = Infinity;
const files = [
{ name: "a.ts", text: SourceText.New("", "import {_} from 'b'", "var x = 1") },
{ name: "b.ts", text: SourceText.New("", "", "var y = 2") },
];
const options: CompilerOptions = { target };
const program_1 = newProgram(files, ["a.ts"], options);
checkResolvedModulesCache(program_1, "a.ts", createMapFromTemplate({ "b": createResolvedModule("b.ts") }));
checkResolvedModulesCache(program_1, "b.ts", /*expectedContent*/ undefined);
const program_2 = updateProgram(program_1, ["a.ts"], options, files => {
files[0].text = files[0].text.updateProgram("var x = 2");
});
assert.isTrue(program_1.structureIsReused === StructureIsReused.Completely);
// content of resolution cache should not change
checkResolvedModulesCache(program_1, "a.ts", createMapFromTemplate({ "b": createResolvedModule("b.ts") }));
checkResolvedModulesCache(program_1, "b.ts", /*expectedContent*/ undefined);
// imports has changed - program is not reused
const program_3 = updateProgram(program_2, ["a.ts"], options, files => {
files[0].text = files[0].text.updateImportsAndExports("");
});
assert.isTrue(program_2.structureIsReused === StructureIsReused.SafeModules);
checkResolvedModulesCache(program_3, "a.ts", /*expectedContent*/ undefined);
const program_4 = updateProgram(program_3, ["a.ts"], options, files => {
const newImports = `import x from 'b'
import y from 'c'
`;
files[0].text = files[0].text.updateImportsAndExports(newImports);
});
assert.isTrue(program_3.structureIsReused === StructureIsReused.SafeModules);
checkResolvedModulesCache(program_4, "a.ts", createMapFromTemplate({ "b": createResolvedModule("b.ts"), "c": undefined }));
});
it("resolved type directives cache follows type directives", () => {
const files = [
{ name: "/a.ts", text: SourceText.New("/// <reference types='typedefs'/>", "", "var x = $") },
{ name: "/types/typedefs/index.d.ts", text: SourceText.New("", "", "declare var $: number") },
];
const options: CompilerOptions = { target, typeRoots: ["/types"] };
const program_1 = newProgram(files, ["/a.ts"], options);
checkResolvedTypeDirectivesCache(program_1, "/a.ts", createMapFromTemplate({ "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }));
checkResolvedTypeDirectivesCache(program_1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined);
const program_2 = updateProgram(program_1, ["/a.ts"], options, files => {
files[0].text = files[0].text.updateProgram("var x = 2");
});
assert.isTrue(program_1.structureIsReused === StructureIsReused.Completely);
// content of resolution cache should not change
checkResolvedTypeDirectivesCache(program_1, "/a.ts", createMapFromTemplate({ "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }));
checkResolvedTypeDirectivesCache(program_1, "/types/typedefs/index.d.ts", /*expectedContent*/ undefined);
// type reference directives has changed - program is not reused
const program_3 = updateProgram(program_2, ["/a.ts"], options, files => {
files[0].text = files[0].text.updateReferences("");
});
assert.isTrue(program_2.structureIsReused === StructureIsReused.SafeModules);
checkResolvedTypeDirectivesCache(program_3, "/a.ts", /*expectedContent*/ undefined);
updateProgram(program_3, ["/a.ts"], options, files => {
const newReferences = `/// <reference types="typedefs"/>
/// <reference types="typedefs2"/>
`;
files[0].text = files[0].text.updateReferences(newReferences);
});
assert.isTrue(program_3.structureIsReused === StructureIsReused.SafeModules);
checkResolvedTypeDirectivesCache(program_1, "/a.ts", createMapFromTemplate({ "typedefs": { resolvedFileName: "/types/typedefs/index.d.ts", primary: true } }));
});
it("fetches imports after npm install", () => {
const file1Ts = { name: "file1.ts", text: SourceText.New("", `import * as a from "a";`, "const myX: number = a.x;") };
const file2Ts = { name: "file2.ts", text: SourceText.New("", "", "") };
const indexDTS = { name: "node_modules/a/index.d.ts", text: SourceText.New("", "export declare let x: number;", "") };
const options: CompilerOptions = { target: ScriptTarget.ES2015, traceResolution: true, moduleResolution: ModuleResolutionKind.NodeJs };
const rootFiles = [file1Ts, file2Ts];
const filesAfterNpmInstall = [file1Ts, file2Ts, indexDTS];
const initialProgram = newProgram(rootFiles, rootFiles.map(f => f.name), options);
{
assert.deepEqual(initialProgram.host.getTrace(),
[
"======== Resolving module 'a' from 'file1.ts'. ========",
"Explicitly specified module resolution kind: 'NodeJs'.",
"Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.",
"File 'node_modules/a.ts' does not exist.",
"File 'node_modules/a.tsx' does not exist.",
"File 'node_modules/a.d.ts' does not exist.",
"File 'node_modules/a/package.json' does not exist.",
"File 'node_modules/a/index.ts' does not exist.",
"File 'node_modules/a/index.tsx' does not exist.",
"File 'node_modules/a/index.d.ts' does not exist.",
"File 'node_modules/@types/a.d.ts' does not exist.",
"File 'node_modules/@types/a/package.json' does not exist.",
"File 'node_modules/@types/a/index.d.ts' does not exist.",
"Loading module 'a' from 'node_modules' folder, target file type 'JavaScript'.",
"File 'node_modules/a.js' does not exist.",
"File 'node_modules/a.jsx' does not exist.",
"File 'node_modules/a/package.json' does not exist.",
"File 'node_modules/a/index.js' does not exist.",
"File 'node_modules/a/index.jsx' does not exist.",
"======== Module name 'a' was not resolved. ========"
],
"initialProgram: execute module resolution normally.");
const initialProgramDiagnostics = initialProgram.getSemanticDiagnostics(initialProgram.getSourceFile("file1.ts"));
assert(initialProgramDiagnostics.length === 1, `initialProgram: import should fail.`);
}
const afterNpmInstallProgram = updateProgram(initialProgram, rootFiles.map(f => f.name), options, f => {
f[1].text = f[1].text.updateReferences(`/// <reference no-default-lib="true"/>`);
}, filesAfterNpmInstall);
{
assert.deepEqual(afterNpmInstallProgram.host.getTrace(),
[
"======== Resolving module 'a' from 'file1.ts'. ========",
"Explicitly specified module resolution kind: 'NodeJs'.",
"Loading module 'a' from 'node_modules' folder, target file type 'TypeScript'.",
"File 'node_modules/a.ts' does not exist.",
"File 'node_modules/a.tsx' does not exist.",
"File 'node_modules/a.d.ts' does not exist.",
"File 'node_modules/a/package.json' does not exist.",
"File 'node_modules/a/index.ts' does not exist.",
"File 'node_modules/a/index.tsx' does not exist.",
"File 'node_modules/a/index.d.ts' exist - use it as a name resolution result.",
"======== Module name 'a' was successfully resolved to 'node_modules/a/index.d.ts'. ========"
],
"afterNpmInstallProgram: execute module resolution normally.");
const afterNpmInstallProgramDiagnostics = afterNpmInstallProgram.getSemanticDiagnostics(afterNpmInstallProgram.getSourceFile("file1.ts"));
assert(afterNpmInstallProgramDiagnostics.length === 0, `afterNpmInstallProgram: program is well-formed with import.`);
}
});
it("can reuse ambient module declarations from non-modified files", () => {
const files = [
{ name: "/a/b/app.ts", text: SourceText.New("", "import * as fs from 'fs'", "") },
{ name: "/a/b/node.d.ts", text: SourceText.New("", "", "declare module 'fs' {}") }
];
const options = { target: ScriptTarget.ES2015, traceResolution: true };
const program = newProgram(files, files.map(f => f.name), options);
assert.deepEqual(program.host.getTrace(),
[
"======== Resolving module 'fs' from '/a/b/app.ts'. ========",
"Module resolution kind is not specified, using 'Classic'.",
"File '/a/b/fs.ts' does not exist.",
"File '/a/b/fs.tsx' does not exist.",
"File '/a/b/fs.d.ts' does not exist.",
"File '/a/fs.ts' does not exist.",
"File '/a/fs.tsx' does not exist.",
"File '/a/fs.d.ts' does not exist.",
"File '/fs.ts' does not exist.",
"File '/fs.tsx' does not exist.",
"File '/fs.d.ts' does not exist.",
"File '/a/b/node_modules/@types/fs.d.ts' does not exist.",
"File '/a/b/node_modules/@types/fs/package.json' does not exist.",
"File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.",
"File '/a/node_modules/@types/fs.d.ts' does not exist.",
"File '/a/node_modules/@types/fs/package.json' does not exist.",
"File '/a/node_modules/@types/fs/index.d.ts' does not exist.",
"File '/node_modules/@types/fs.d.ts' does not exist.",
"File '/node_modules/@types/fs/package.json' does not exist.",
"File '/node_modules/@types/fs/index.d.ts' does not exist.",
"File '/a/b/fs.js' does not exist.",
"File '/a/b/fs.jsx' does not exist.",
"File '/a/fs.js' does not exist.",
"File '/a/fs.jsx' does not exist.",
"File '/fs.js' does not exist.",
"File '/fs.jsx' does not exist.",
"======== Module name 'fs' was not resolved. ========",
], "should look for 'fs'");
const program_2 = updateProgram(program, program.getRootFileNames(), options, f => {
f[0].text = f[0].text.updateProgram("var x = 1;");
});
assert.deepEqual(program_2.host.getTrace(), [
"Module 'fs' was resolved as ambient module declared in '/a/b/node.d.ts' since this file was not modified."
], "should reuse 'fs' since node.d.ts was not changed");
const program_3 = updateProgram(program_2, program_2.getRootFileNames(), options, f => {
f[0].text = f[0].text.updateProgram("var y = 1;");
f[1].text = f[1].text.updateProgram("declare var process: any");
});
assert.deepEqual(program_3.host.getTrace(),
[
"======== Resolving module 'fs' from '/a/b/app.ts'. ========",
"Module resolution kind is not specified, using 'Classic'.",
"File '/a/b/fs.ts' does not exist.",
"File '/a/b/fs.tsx' does not exist.",
"File '/a/b/fs.d.ts' does not exist.",
"File '/a/fs.ts' does not exist.",
"File '/a/fs.tsx' does not exist.",
"File '/a/fs.d.ts' does not exist.",
"File '/fs.ts' does not exist.",
"File '/fs.tsx' does not exist.",
"File '/fs.d.ts' does not exist.",
"File '/a/b/node_modules/@types/fs.d.ts' does not exist.",
"File '/a/b/node_modules/@types/fs/package.json' does not exist.",
"File '/a/b/node_modules/@types/fs/index.d.ts' does not exist.",
"File '/a/node_modules/@types/fs.d.ts' does not exist.",
"File '/a/node_modules/@types/fs/package.json' does not exist.",
"File '/a/node_modules/@types/fs/index.d.ts' does not exist.",
"File '/node_modules/@types/fs.d.ts' does not exist.",
"File '/node_modules/@types/fs/package.json' does not exist.",
"File '/node_modules/@types/fs/index.d.ts' does not exist.",
"File '/a/b/fs.js' does not exist.",
"File '/a/b/fs.jsx' does not exist.",
"File '/a/fs.js' does not exist.",
"File '/a/fs.jsx' does not exist.",
"File '/fs.js' does not exist.",
"File '/fs.jsx' does not exist.",
"======== Module name 'fs' was not resolved. ========",
], "should look for 'fs' again since node.d.ts was changed");
});
it("can reuse module resolutions from non-modified files", () => {
const files = [
{ name: "a1.ts", text: SourceText.New("", "", "let x = 1;") },
{ name: "a2.ts", text: SourceText.New("", "", "let x = 1;") },
{ name: "b1.ts", text: SourceText.New("", "export class B { x: number; }", "") },
{ name: "b2.ts", text: SourceText.New("", "export class B { x: number; }", "") },
{ name: "node_modules/@types/typerefs1/index.d.ts", text: SourceText.New("", "", "declare let z: string;") },
{ name: "node_modules/@types/typerefs2/index.d.ts", text: SourceText.New("", "", "declare let z: string;") },
{
name: "f1.ts",
text:
SourceText.New(
`/// <reference path="a1.ts"/>${newLine}/// <reference types="typerefs1"/>${newLine}/// <reference no-default-lib="true"/>`,
`import { B } from './b1';${newLine}export let BB = B;`,
"declare module './b1' { interface B { y: string; } }")
},
{
name: "f2.ts",
text: SourceText.New(
`/// <reference path="a2.ts"/>${newLine}/// <reference types="typerefs2"/>`,
`import { B } from './b2';${newLine}import { BB } from './f1';`,
"(new BB).x; (new BB).y;")
},
];
const options: CompilerOptions = { target: ScriptTarget.ES2015, traceResolution: true, moduleResolution: ModuleResolutionKind.Classic };
const program_1 = newProgram(files, files.map(f => f.name), options);
let expectedErrors = 0;
{
assert.deepEqual(program_1.host.getTrace(),
[
"======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========",
"Resolving with primary search path 'node_modules/@types'.",
"File 'node_modules/@types/typerefs1/package.json' does not exist.",
"File 'node_modules/@types/typerefs1/index.d.ts' exist - use it as a name resolution result.",
"======== Type reference directive 'typerefs1' was successfully resolved to 'node_modules/@types/typerefs1/index.d.ts', primary: true. ========",
"======== Resolving module './b1' from 'f1.ts'. ========",
"Explicitly specified module resolution kind: 'Classic'.",
"File 'b1.ts' exist - use it as a name resolution result.",
"======== Module name './b1' was successfully resolved to 'b1.ts'. ========",
"======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========",
"Resolving with primary search path 'node_modules/@types'.",
"File 'node_modules/@types/typerefs2/package.json' does not exist.",
"File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.",
"======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========",
"======== Resolving module './b2' from 'f2.ts'. ========",
"Explicitly specified module resolution kind: 'Classic'.",
"File 'b2.ts' exist - use it as a name resolution result.",
"======== Module name './b2' was successfully resolved to 'b2.ts'. ========",
"======== Resolving module './f1' from 'f2.ts'. ========",
"Explicitly specified module resolution kind: 'Classic'.",
"File 'f1.ts' exist - use it as a name resolution result.",
"======== Module name './f1' was successfully resolved to 'f1.ts'. ========"
],
"program_1: execute module reoslution normally.");
const program_1Diagnostics = program_1.getSemanticDiagnostics(program_1.getSourceFile("f2.ts"));
assert(program_1Diagnostics.length === expectedErrors, `initial program should be well-formed`);
}
const indexOfF1 = 6;
const program_2 = updateProgram(program_1, program_1.getRootFileNames(), options, f => {
const newSourceText = f[indexOfF1].text.updateReferences(`/// <reference path="a1.ts"/>${newLine}/// <reference types="typerefs1"/>`);
f[indexOfF1] = { name: "f1.ts", text: newSourceText };
});
{
const program_2Diagnostics = program_2.getSemanticDiagnostics(program_2.getSourceFile("f2.ts"));
assert(program_2Diagnostics.length === expectedErrors, `removing no-default-lib shouldn't affect any types used.`);
assert.deepEqual(program_2.host.getTrace(), [
"======== Resolving type reference directive 'typerefs1', containing file 'f1.ts', root directory 'node_modules/@types'. ========",
"Resolving with primary search path 'node_modules/@types'.",
"File 'node_modules/@types/typerefs1/package.json' does not exist.",
"File 'node_modules/@types/typerefs1/index.d.ts' exist - use it as a name resolution result.",
"======== Type reference directive 'typerefs1' was successfully resolved to 'node_modules/@types/typerefs1/index.d.ts', primary: true. ========",
"======== Resolving module './b1' from 'f1.ts'. ========",
"Explicitly specified module resolution kind: 'Classic'.",
"File 'b1.ts' exist - use it as a name resolution result.",
"======== Module name './b1' was successfully resolved to 'b1.ts'. ========",
"======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========",
"Resolving with primary search path 'node_modules/@types'.",
"File 'node_modules/@types/typerefs2/package.json' does not exist.",
"File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.",
"======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========",
"Reusing resolution of module './b2' to file 'f2.ts' from old program.",
"Reusing resolution of module './f1' to file 'f2.ts' from old program."
], "program_2: reuse module resolutions in f2 since it is unchanged");
}
const program_3 = updateProgram(program_2, program_2.getRootFileNames(), options, f => {
const newSourceText = f[indexOfF1].text.updateReferences(`/// <reference path="a1.ts"/>`);
f[indexOfF1] = { name: "f1.ts", text: newSourceText };
});
{
const program_3Diagnostics = program_3.getSemanticDiagnostics(program_3.getSourceFile("f2.ts"));
assert(program_3Diagnostics.length === expectedErrors, `typerefs2 was unused, so diagnostics should be unaffected.`);
assert.deepEqual(program_3.host.getTrace(), [
"======== Resolving module './b1' from 'f1.ts'. ========",
"Explicitly specified module resolution kind: 'Classic'.",
"File 'b1.ts' exist - use it as a name resolution result.",
"======== Module name './b1' was successfully resolved to 'b1.ts'. ========",
"======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========",
"Resolving with primary search path 'node_modules/@types'.",
"File 'node_modules/@types/typerefs2/package.json' does not exist.",
"File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.",
"======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========",
"Reusing resolution of module './b2' to file 'f2.ts' from old program.",
"Reusing resolution of module './f1' to file 'f2.ts' from old program."
], "program_3: reuse module resolutions in f2 since it is unchanged");
}
const program_4 = updateProgram(program_3, program_3.getRootFileNames(), options, f => {
const newSourceText = f[indexOfF1].text.updateReferences("");
f[indexOfF1] = { name: "f1.ts", text: newSourceText };
});
{
const program_4Diagnostics = program_4.getSemanticDiagnostics(program_4.getSourceFile("f2.ts"));
assert(program_4Diagnostics.length === expectedErrors, `a1.ts was unused, so diagnostics should be unaffected.`);
assert.deepEqual(program_4.host.getTrace(), [
"======== Resolving module './b1' from 'f1.ts'. ========",
"Explicitly specified module resolution kind: 'Classic'.",
"File 'b1.ts' exist - use it as a name resolution result.",
"======== Module name './b1' was successfully resolved to 'b1.ts'. ========",
"======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========",
"Resolving with primary search path 'node_modules/@types'.",
"File 'node_modules/@types/typerefs2/package.json' does not exist.",
"File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.",
"======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========",
"Reusing resolution of module './b2' to file 'f2.ts' from old program.",
"Reusing resolution of module './f1' to file 'f2.ts' from old program."
], "program_4: reuse module resolutions in f2 since it is unchanged");
}
const program_5 = updateProgram(program_4, program_4.getRootFileNames(), options, f => {
const newSourceText = f[indexOfF1].text.updateImportsAndExports(`import { B } from './b1';`);
f[indexOfF1] = { name: "f1.ts", text: newSourceText };
});
{
const program_5Diagnostics = program_5.getSemanticDiagnostics(program_5.getSourceFile("f2.ts"));
assert(program_5Diagnostics.length === ++expectedErrors, `import of BB in f1 fails. BB is of type any. Add one error`);
assert.deepEqual(program_5.host.getTrace(), [
"======== Resolving module './b1' from 'f1.ts'. ========",
"Explicitly specified module resolution kind: 'Classic'.",
"File 'b1.ts' exist - use it as a name resolution result.",
"======== Module name './b1' was successfully resolved to 'b1.ts'. ========"
], "program_5: exports do not affect program structure, so f2's resolutions are silently reused.");
}
const program_6 = updateProgram(program_5, program_5.getRootFileNames(), options, f => {
const newSourceText = f[indexOfF1].text.updateProgram("");
f[indexOfF1] = { name: "f1.ts", text: newSourceText };
});
{
const program_6Diagnostics = program_6.getSemanticDiagnostics(program_6.getSourceFile("f2.ts"));
assert(program_6Diagnostics.length === expectedErrors, `import of BB in f1 fails.`);
assert.deepEqual(program_6.host.getTrace(), [
"======== Resolving module './b1' from 'f1.ts'. ========",
"Explicitly specified module resolution kind: 'Classic'.",
"File 'b1.ts' exist - use it as a name resolution result.",
"======== Module name './b1' was successfully resolved to 'b1.ts'. ========",
"======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========",
"Resolving with primary search path 'node_modules/@types'.",
"File 'node_modules/@types/typerefs2/package.json' does not exist.",
"File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.",
"======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========",
"Reusing resolution of module './b2' to file 'f2.ts' from old program.",
"Reusing resolution of module './f1' to file 'f2.ts' from old program."
], "program_6: reuse module resolutions in f2 since it is unchanged");
}
const program_7 = updateProgram(program_6, program_6.getRootFileNames(), options, f => {
const newSourceText = f[indexOfF1].text.updateImportsAndExports("");
f[indexOfF1] = { name: "f1.ts", text: newSourceText };
});
{
const program_7Diagnostics = program_7.getSemanticDiagnostics(program_7.getSourceFile("f2.ts"));
assert(program_7Diagnostics.length === expectedErrors, `removing import is noop with respect to program, so no change in diagnostics.`);
assert.deepEqual(program_7.host.getTrace(), [
"======== Resolving type reference directive 'typerefs2', containing file 'f2.ts', root directory 'node_modules/@types'. ========",
"Resolving with primary search path 'node_modules/@types'.",
"File 'node_modules/@types/typerefs2/package.json' does not exist.",
"File 'node_modules/@types/typerefs2/index.d.ts' exist - use it as a name resolution result.",
"======== Type reference directive 'typerefs2' was successfully resolved to 'node_modules/@types/typerefs2/index.d.ts', primary: true. ========",
"Reusing resolution of module './b2' to file 'f2.ts' from old program.",
"Reusing resolution of module './f1' to file 'f2.ts' from old program."
], "program_7 should reuse module resolutions in f2 since it is unchanged");
}
});
});
describe("host is optional", () => {
it("should work if host is not provided", () => {
createProgram([], {});
});
});
}
@@ -0,0 +1,444 @@
/// <reference path="..\..\harnessLanguageService.ts" />
interface ClassificationEntry {
value: any;
classification: ts.TokenClass;
position?: number;
}
describe("Colorization", function () {
// Use the shim adapter to ensure test coverage of the shim layer for the classifier
const languageServiceAdapter = new Harness.LanguageService.ShimLanguageServiceAdapter(/*preprocessToResolve*/ false);
const classifier = languageServiceAdapter.getClassifier();
function getEntryAtPosition(result: ts.ClassificationResult, position: number) {
let entryPosition = 0;
for (const entry of result.entries) {
if (entryPosition === position) {
return entry;
}
entryPosition += entry.length;
}
return undefined;
}
function punctuation(text: string, position?: number) { return createClassification(text, ts.TokenClass.Punctuation, position); }
function keyword(text: string, position?: number) { return createClassification(text, ts.TokenClass.Keyword, position); }
function operator(text: string, position?: number) { return createClassification(text, ts.TokenClass.Operator, position); }
function comment(text: string, position?: number) { return createClassification(text, ts.TokenClass.Comment, position); }
function whitespace(text: string, position?: number) { return createClassification(text, ts.TokenClass.Whitespace, position); }
function identifier(text: string, position?: number) { return createClassification(text, ts.TokenClass.Identifier, position); }
function numberLiteral(text: string, position?: number) { return createClassification(text, ts.TokenClass.NumberLiteral, position); }
function stringLiteral(text: string, position?: number) { return createClassification(text, ts.TokenClass.StringLiteral, position); }
function finalEndOfLineState(value: number): ClassificationEntry { return { value: value, classification: undefined, position: 0 }; }
function createClassification(text: string, tokenClass: ts.TokenClass, position?: number): ClassificationEntry {
return {
value: text,
classification: tokenClass,
position: position,
};
}
function testLexicalClassification(text: string, initialEndOfLineState: ts.EndOfLineState, ...expectedEntries: ClassificationEntry[]): void {
const result = classifier.getClassificationsForLine(text, initialEndOfLineState, /*syntacticClassifierAbsent*/ false);
for (const expectedEntry of expectedEntries) {
if (expectedEntry.classification === undefined) {
assert.equal(result.finalLexState, expectedEntry.value, "final endOfLineState does not match expected.");
}
else {
const actualEntryPosition = expectedEntry.position !== undefined ? expectedEntry.position : text.indexOf(expectedEntry.value);
assert(actualEntryPosition >= 0, "token: '" + expectedEntry.value + "' does not exit in text: '" + text + "'.");
const actualEntry = getEntryAtPosition(result, actualEntryPosition);
assert(actualEntry, "Could not find classification entry for '" + expectedEntry.value + "' at position: " + actualEntryPosition);
assert.equal(actualEntry.classification, expectedEntry.classification, "Classification class does not match expected. Expected: " + ts.TokenClass[expectedEntry.classification] + ", Actual: " + ts.TokenClass[actualEntry.classification]);
assert.equal(actualEntry.length, expectedEntry.value.length, "Classification length does not match expected. Expected: " + ts.TokenClass[expectedEntry.value.length] + ", Actual: " + ts.TokenClass[actualEntry.length]);
}
}
}
describe("test getClassifications", function () {
it("Returns correct token classes", function () {
testLexicalClassification("var x: string = \"foo\"; //Hello",
ts.EndOfLineState.None,
keyword("var"),
whitespace(" "),
identifier("x"),
punctuation(":"),
keyword("string"),
operator("="),
stringLiteral("\"foo\""),
comment("//Hello"),
punctuation(";"));
});
it("correctly classifies a comment after a divide operator", function () {
testLexicalClassification("1 / 2 // comment",
ts.EndOfLineState.None,
numberLiteral("1"),
whitespace(" "),
operator("/"),
numberLiteral("2"),
comment("// comment"));
});
it("correctly classifies a literal after a divide operator", function () {
testLexicalClassification("1 / 2, 3 / 4",
ts.EndOfLineState.None,
numberLiteral("1"),
whitespace(" "),
operator("/"),
numberLiteral("2"),
numberLiteral("3"),
numberLiteral("4"),
operator(","));
});
it("correctly classifies a multi-line string with one backslash", function () {
testLexicalClassification("'line1\\",
ts.EndOfLineState.None,
stringLiteral("'line1\\"),
finalEndOfLineState(ts.EndOfLineState.InSingleQuoteStringLiteral));
});
it("correctly classifies a multi-line string with three backslashes", function () {
testLexicalClassification("'line1\\\\\\",
ts.EndOfLineState.None,
stringLiteral("'line1\\\\\\"),
finalEndOfLineState(ts.EndOfLineState.InSingleQuoteStringLiteral));
});
it("correctly classifies an unterminated single-line string with no backslashes", function () {
testLexicalClassification("'line1",
ts.EndOfLineState.None,
stringLiteral("'line1"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("correctly classifies an unterminated single-line string with two backslashes", function () {
testLexicalClassification("'line1\\\\",
ts.EndOfLineState.None,
stringLiteral("'line1\\\\"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("correctly classifies an unterminated single-line string with four backslashes", function () {
testLexicalClassification("'line1\\\\\\\\",
ts.EndOfLineState.None,
stringLiteral("'line1\\\\\\\\"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("correctly classifies the continuing line of a multi-line string ending in one backslash", function () {
testLexicalClassification("\\",
ts.EndOfLineState.InDoubleQuoteStringLiteral,
stringLiteral("\\"),
finalEndOfLineState(ts.EndOfLineState.InDoubleQuoteStringLiteral));
});
it("correctly classifies the continuing line of a multi-line string ending in three backslashes", function () {
testLexicalClassification("\\",
ts.EndOfLineState.InDoubleQuoteStringLiteral,
stringLiteral("\\"),
finalEndOfLineState(ts.EndOfLineState.InDoubleQuoteStringLiteral));
});
it("correctly classifies the last line of an unterminated multi-line string ending in no backslashes", function () {
testLexicalClassification(" ",
ts.EndOfLineState.InDoubleQuoteStringLiteral,
stringLiteral(" "),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("correctly classifies the last line of an unterminated multi-line string ending in two backslashes", function () {
testLexicalClassification("\\\\",
ts.EndOfLineState.InDoubleQuoteStringLiteral,
stringLiteral("\\\\"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("correctly classifies the last line of an unterminated multi-line string ending in four backslashes", function () {
testLexicalClassification("\\\\\\\\",
ts.EndOfLineState.InDoubleQuoteStringLiteral,
stringLiteral("\\\\\\\\"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("correctly classifies the last line of a multi-line string", function () {
testLexicalClassification("'",
ts.EndOfLineState.InSingleQuoteStringLiteral,
stringLiteral("'"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("correctly classifies an unterminated multiline comment", function () {
testLexicalClassification("/*",
ts.EndOfLineState.None,
comment("/*"),
finalEndOfLineState(ts.EndOfLineState.InMultiLineCommentTrivia));
});
it("correctly classifies the termination of a multiline comment", function () {
testLexicalClassification(" */ ",
ts.EndOfLineState.InMultiLineCommentTrivia,
comment(" */"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("correctly classifies the continuation of a multiline comment", function () {
testLexicalClassification("LOREM IPSUM DOLOR ",
ts.EndOfLineState.InMultiLineCommentTrivia,
comment("LOREM IPSUM DOLOR "),
finalEndOfLineState(ts.EndOfLineState.InMultiLineCommentTrivia));
});
it("correctly classifies an unterminated multiline comment on a line ending in '/*/'", function () {
testLexicalClassification(" /*/",
ts.EndOfLineState.None,
comment("/*/"),
finalEndOfLineState(ts.EndOfLineState.InMultiLineCommentTrivia));
});
it("correctly classifies an unterminated multiline comment with trailing space", function () {
testLexicalClassification("/* ",
ts.EndOfLineState.None,
comment("/* "),
finalEndOfLineState(ts.EndOfLineState.InMultiLineCommentTrivia));
});
it("correctly classifies a keyword after a dot", function () {
testLexicalClassification("a.var",
ts.EndOfLineState.None,
identifier("var"));
});
it("correctly classifies a string literal after a dot", function () {
testLexicalClassification("a.\"var\"",
ts.EndOfLineState.None,
stringLiteral("\"var\""));
});
it("correctly classifies a keyword after a dot separated by comment trivia", function () {
testLexicalClassification("a./*hello world*/ var",
ts.EndOfLineState.None,
identifier("a"),
punctuation("."),
comment("/*hello world*/"),
identifier("var"));
});
it("classifies a property access with whitespace around the dot", function () {
testLexicalClassification(" x .\tfoo ()",
ts.EndOfLineState.None,
identifier("x"),
identifier("foo"));
});
it("classifies a keyword after a dot on previous line", function () {
testLexicalClassification("var",
ts.EndOfLineState.None,
keyword("var"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("classifies multiple keywords properly", function () {
testLexicalClassification("public static",
ts.EndOfLineState.None,
keyword("public"),
keyword("static"),
finalEndOfLineState(ts.EndOfLineState.None));
testLexicalClassification("public var",
ts.EndOfLineState.None,
keyword("public"),
identifier("var"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("classifies a single line no substitution template string correctly", () => {
testLexicalClassification("`number number public string`",
ts.EndOfLineState.None,
stringLiteral("`number number public string`"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("classifies substitution parts of a template string correctly", () => {
testLexicalClassification("`number '${ 1 + 1 }' string '${ 'hello' }'`",
ts.EndOfLineState.None,
stringLiteral("`number '${"),
numberLiteral("1"),
operator("+"),
numberLiteral("1"),
stringLiteral("}' string '${"),
stringLiteral("'hello'"),
stringLiteral("}'`"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("classifies an unterminated no substitution template string correctly", () => {
testLexicalClassification("`hello world",
ts.EndOfLineState.None,
stringLiteral("`hello world"),
finalEndOfLineState(ts.EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate));
});
it("classifies the entire line of an unterminated multiline no-substitution/head template", () => {
testLexicalClassification("...",
ts.EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate,
stringLiteral("..."),
finalEndOfLineState(ts.EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate));
});
it("classifies the entire line of an unterminated multiline template middle/end", () => {
testLexicalClassification("...",
ts.EndOfLineState.InTemplateMiddleOrTail,
stringLiteral("..."),
finalEndOfLineState(ts.EndOfLineState.InTemplateMiddleOrTail));
});
it("classifies a termination of a multiline template head", () => {
testLexicalClassification("...${",
ts.EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate,
stringLiteral("...${"),
finalEndOfLineState(ts.EndOfLineState.InTemplateSubstitutionPosition));
});
it("classifies the termination of a multiline no substitution template", () => {
testLexicalClassification("...`",
ts.EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate,
stringLiteral("...`"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("classifies the substitution parts and middle/tail of a multiline template string", () => {
testLexicalClassification("${ 1 + 1 }...`",
ts.EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate,
stringLiteral("${"),
numberLiteral("1"),
operator("+"),
numberLiteral("1"),
stringLiteral("}...`"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("classifies a template middle and propagates the end of line state", () => {
testLexicalClassification("${ 1 + 1 }...`",
ts.EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate,
stringLiteral("${"),
numberLiteral("1"),
operator("+"),
numberLiteral("1"),
stringLiteral("}...`"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("classifies substitution expressions with curly braces appropriately", () => {
let pos = 0;
let lastLength = 0;
testLexicalClassification("...${ () => { } } ${ { x: `1` } }...`",
ts.EndOfLineState.InTemplateHeadOrNoSubstitutionTemplate,
stringLiteral(track("...${"), pos),
punctuation(track(" ", "("), pos),
punctuation(track(")"), pos),
punctuation(track(" ", "=>"), pos),
punctuation(track(" ", "{"), pos),
punctuation(track(" ", "}"), pos),
stringLiteral(track(" ", "} ${"), pos),
punctuation(track(" ", "{"), pos),
identifier(track(" ", "x"), pos),
punctuation(track(":"), pos),
stringLiteral(track(" ", "`1`"), pos),
punctuation(track(" ", "}"), pos),
stringLiteral(track(" ", "}...`"), pos),
finalEndOfLineState(ts.EndOfLineState.None));
// Adjusts 'pos' by accounting for the length of each portion of the string,
// but only return the last given string
function track(...vals: string[]): string {
for (const val of vals) {
pos += lastLength;
lastLength = val.length;
}
return ts.lastOrUndefined(vals);
}
});
it("classifies partially written generics correctly.", function () {
testLexicalClassification("Foo<number",
ts.EndOfLineState.None,
identifier("Foo"),
operator("<"),
identifier("number"),
finalEndOfLineState(ts.EndOfLineState.None));
// Looks like a cast, should get classified as a keyword.
testLexicalClassification("<number",
ts.EndOfLineState.None,
operator("<"),
keyword("number"),
finalEndOfLineState(ts.EndOfLineState.None));
// handle nesting properly.
testLexicalClassification("Foo<Foo,Foo<number",
ts.EndOfLineState.None,
identifier("Foo"),
operator("<"),
identifier("Foo"),
operator(","),
identifier("Foo"),
operator("<"),
identifier("number"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("LexicallyClassifiesConflictTokens", () => {
// Test conflict markers.
testLexicalClassification(
"class C {\r\n\
<<<<<<< HEAD\r\n\
v = 1;\r\n\
=======\r\n\
v = 2;\r\n\
>>>>>>> Branch - a\r\n\
}",
ts.EndOfLineState.None,
keyword("class"),
identifier("C"),
punctuation("{"),
comment("<<<<<<< HEAD"),
identifier("v"),
operator("="),
numberLiteral("1"),
punctuation(";"),
comment("=======\r\n v = 2;\r\n"),
comment(">>>>>>> Branch - a"),
punctuation("}"),
finalEndOfLineState(ts.EndOfLineState.None));
testLexicalClassification(
"<<<<<<< HEAD\r\n\
class C { }\r\n\
=======\r\n\
class D { }\r\n\
>>>>>>> Branch - a\r\n",
ts.EndOfLineState.None,
comment("<<<<<<< HEAD"),
keyword("class"),
identifier("C"),
punctuation("{"),
punctuation("}"),
comment("=======\r\nclass D { }\r\n"),
comment(">>>>>>> Branch - a"),
finalEndOfLineState(ts.EndOfLineState.None));
});
it("'of' keyword", function () {
testLexicalClassification("for (var of of of) { }",
ts.EndOfLineState.None,
keyword("for"),
punctuation("("),
keyword("var"),
keyword("of"),
keyword("of"),
keyword("of"),
punctuation(")"),
punctuation("{"),
punctuation("}"),
finalEndOfLineState(ts.EndOfLineState.None));
});
});
});
@@ -0,0 +1,79 @@
/// <reference path="..\..\harness.ts" />
describe("DocumentRegistry", () => {
it("documents are shared between projects", () => {
const documentRegistry = ts.createDocumentRegistry();
const defaultCompilerOptions = ts.getDefaultCompilerOptions();
const f1 = documentRegistry.acquireDocument("file1.ts", defaultCompilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), /* version */ "1");
const f2 = documentRegistry.acquireDocument("file1.ts", defaultCompilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), /* version */ "1");
assert(f1 === f2, "DocumentRegistry should return the same document for the same name");
});
it("documents are refreshed when settings in compilation settings affect syntax", () => {
const documentRegistry = ts.createDocumentRegistry();
const compilerOptions: ts.CompilerOptions = { target: ts.ScriptTarget.ES5, module: ts.ModuleKind.AMD };
// change compilation setting that doesn't affect parsing - should have the same document
compilerOptions.declaration = true;
const f1 = documentRegistry.acquireDocument("file1.ts", compilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), /* version */ "1");
compilerOptions.declaration = false;
const f2 = documentRegistry.acquireDocument("file1.ts", compilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), /* version */ "1");
assert(f1 === f2, "Expected to have the same document instance");
// change value of compilation setting that is used during production of AST - new document is required
compilerOptions.target = ts.ScriptTarget.ES3;
const f3 = documentRegistry.acquireDocument("file1.ts", compilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), /* version */ "1");
assert(f1 !== f3, "Changed target: Expected to have different instances of document");
compilerOptions.preserveConstEnums = true;
const f4 = documentRegistry.acquireDocument("file1.ts", compilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), /* version */ "1");
assert(f3 === f4, "Changed preserveConstEnums: Expected to have the same instance of the document");
compilerOptions.module = ts.ModuleKind.System;
const f5 = documentRegistry.acquireDocument("file1.ts", compilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), /* version */ "1");
assert(f4 !== f5, "Changed module: Expected to have different instances of the document");
});
it("Acquiring document gets correct version 1", () => {
const documentRegistry = ts.createDocumentRegistry();
const defaultCompilerOptions = ts.getDefaultCompilerOptions();
// Simulate one LS getting the document.
documentRegistry.acquireDocument("file1.ts", defaultCompilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), /* version */ "1");
// Simulate another LS getting the document at another version.
const f2 = documentRegistry.acquireDocument("file1.ts", defaultCompilerOptions, ts.ScriptSnapshot.fromString("var x = 1;"), /* version */ "2");
assert(f2.version === "2");
});
it("Acquiring document gets correct version 2", () => {
const documentRegistry = ts.createDocumentRegistry();
const defaultCompilerOptions = ts.getDefaultCompilerOptions();
const contents = "var x = 1;";
const snapshot = ts.ScriptSnapshot.fromString(contents);
// Always treat any change as a full change.
snapshot.getChangeRange = () => ts.createTextChangeRange(ts.createTextSpan(0, contents.length), contents.length);
// Simulate one LS getting the document.
documentRegistry.acquireDocument("file1.ts", defaultCompilerOptions, snapshot, /* version */ "1");
// Simulate another LS getting that document.
documentRegistry.acquireDocument("file1.ts", defaultCompilerOptions, snapshot, /* version */ "1");
// Now LS1 updates their document.
documentRegistry.updateDocument("file1.ts", defaultCompilerOptions, snapshot, /* version */ "2");
// Now LS2 tries to update their document.
documentRegistry.updateDocument("file1.ts", defaultCompilerOptions, snapshot, /* version */ "3");
});
});
@@ -0,0 +1,526 @@
/// <reference path="..\..\..\services\patternMatcher.ts" />
describe("PatternMatcher", function () {
describe("BreakIntoCharacterSpans", function () {
it("EmptyIdentifier", () => {
verifyBreakIntoCharacterSpans("");
});
it("SimpleIdentifier", () => {
verifyBreakIntoCharacterSpans("foo", "foo");
});
it("PrefixUnderscoredIdentifier", () => {
verifyBreakIntoCharacterSpans("_foo", "_", "foo");
});
it("UnderscoredIdentifier", () => {
verifyBreakIntoCharacterSpans("f_oo", "f", "_", "oo");
});
it("PostfixUnderscoredIdentifier", () => {
verifyBreakIntoCharacterSpans("foo_", "foo", "_");
});
it("PrefixUnderscoredIdentifierWithCapital", () => {
verifyBreakIntoCharacterSpans("_Foo", "_", "Foo");
});
it("MUnderscorePrefixed", () => {
verifyBreakIntoCharacterSpans("m_foo", "m", "_", "foo");
});
it("CamelCaseIdentifier", () => {
verifyBreakIntoCharacterSpans("FogBar", "Fog", "Bar");
});
it("MixedCaseIdentifier", () => {
verifyBreakIntoCharacterSpans("fogBar", "fog", "Bar");
});
it("TwoCharacterCapitalIdentifier", () => {
verifyBreakIntoCharacterSpans("UIElement", "U", "I", "Element");
});
it("NumberSuffixedIdentifier", () => {
verifyBreakIntoCharacterSpans("Foo42", "Foo", "42");
});
it("NumberContainingIdentifier", () => {
verifyBreakIntoCharacterSpans("Fog42Bar", "Fog", "42", "Bar");
});
it("NumberPrefixedIdentifier", () => {
verifyBreakIntoCharacterSpans("42Bar", "42", "Bar");
});
});
describe("BreakIntoWordSpans", function () {
it("VarbatimIdentifier", () => {
verifyBreakIntoWordSpans("@int:", "int");
});
it("AllCapsConstant", () => {
verifyBreakIntoWordSpans("C_STYLE_CONSTANT", "C", "_", "STYLE", "_", "CONSTANT");
});
it("SingleLetterPrefix1", () => {
verifyBreakIntoWordSpans("UInteger", "U", "Integer");
});
it("SingleLetterPrefix2", () => {
verifyBreakIntoWordSpans("IDisposable", "I", "Disposable");
});
it("TwoCharacterCapitalIdentifier", () => {
verifyBreakIntoWordSpans("UIElement", "UI", "Element");
});
it("XDocument", () => {
verifyBreakIntoWordSpans("XDocument", "X", "Document");
});
it("XMLDocument1", () => {
verifyBreakIntoWordSpans("XMLDocument", "XML", "Document");
});
it("XMLDocument2", () => {
verifyBreakIntoWordSpans("XmlDocument", "Xml", "Document");
});
it("TwoUppercaseCharacters", () => {
verifyBreakIntoWordSpans("SimpleUIElement", "Simple", "UI", "Element");
});
});
describe("SingleWordPattern", () => {
it("PreferCaseSensitiveExact", () => {
const match = getFirstMatch("Foo", "Foo");
assert.equal(ts.PatternMatchKind.exact, match.kind);
assert.equal(true, match.isCaseSensitive);
});
it("PreferCaseSensitiveExactInsensitive", () => {
const match = getFirstMatch("foo", "Foo");
assert.equal(ts.PatternMatchKind.exact, match.kind);
assert.equal(false, match.isCaseSensitive);
});
it("PreferCaseSensitivePrefix", () => {
const match = getFirstMatch("Foo", "Fo");
assert.equal(ts.PatternMatchKind.prefix, match.kind);
assert.equal(true, match.isCaseSensitive);
});
it("PreferCaseSensitivePrefixCaseInsensitive", () => {
const match = getFirstMatch("Foo", "fo");
assert.equal(ts.PatternMatchKind.prefix, match.kind);
assert.equal(false, match.isCaseSensitive);
});
it("PreferCaseSensitiveCamelCaseMatchSimple", () => {
const match = getFirstMatch("FogBar", "FB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
assertInRange(match.camelCaseWeight, 1, 1 << 30);
});
it("PreferCaseSensitiveCamelCaseMatchPartialPattern", () => {
const match = getFirstMatch("FogBar", "FoB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
});
it("PreferCaseSensitiveCamelCaseMatchToLongPattern1", () => {
const match = getFirstMatch("FogBar", "FBB");
assert.isTrue(match === undefined);
});
it("PreferCaseSensitiveCamelCaseMatchToLongPattern2", () => {
const match = getFirstMatch("FogBar", "FoooB");
assert.isTrue(match === undefined);
});
it("CamelCaseMatchPartiallyUnmatched", () => {
const match = getFirstMatch("FogBarBaz", "FZ");
assert.isTrue(match === undefined);
});
it("CamelCaseMatchCompletelyUnmatched", () => {
const match = getFirstMatch("FogBarBaz", "ZZ");
assert.isTrue(match === undefined);
});
it("TwoUppercaseCharacters", () => {
const match = getFirstMatch("SimpleUIElement", "SiUI");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
});
it("PreferCaseSensitiveLowercasePattern", () => {
const match = getFirstMatch("FogBar", "b");
assert.equal(ts.PatternMatchKind.substring, match.kind);
assert.equal(false, match.isCaseSensitive);
});
it("PreferCaseSensitiveLowercasePattern2", () => {
const match = getFirstMatch("FogBar", "fB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(false, match.isCaseSensitive);
});
it("PreferCaseSensitiveTryUnderscoredName", () => {
const match = getFirstMatch("_fogBar", "_fB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
});
it("PreferCaseSensitiveTryUnderscoredName2", () => {
const match = getFirstMatch("_fogBar", "fB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
});
it("PreferCaseSensitiveTryUnderscoredNameInsensitive", () => {
const match = getFirstMatch("_FogBar", "_fB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(false, match.isCaseSensitive);
});
it("PreferCaseSensitiveMiddleUnderscore", () => {
const match = getFirstMatch("Fog_Bar", "FB");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
});
it("PreferCaseSensitiveMiddleUnderscore2", () => {
const match = getFirstMatch("Fog_Bar", "F_B");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(true, match.isCaseSensitive);
});
it("PreferCaseSensitiveMiddleUnderscore3", () => {
const match = getFirstMatch("Fog_Bar", "F__B");
assert.isTrue(undefined === match);
});
it("PreferCaseSensitiveMiddleUnderscore4", () => {
const match = getFirstMatch("Fog_Bar", "f_B");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(false, match.isCaseSensitive);
});
it("PreferCaseSensitiveMiddleUnderscore5", () => {
const match = getFirstMatch("Fog_Bar", "F_b");
assert.equal(ts.PatternMatchKind.camelCase, match.kind);
assert.equal(false, match.isCaseSensitive);
});
it("PreferCaseSensitiveRelativeWeights1", () => {
const match1 = getFirstMatch("FogBarBaz", "FB");
const match2 = getFirstMatch("FooFlobBaz", "FB");
// We should prefer something that starts at the beginning if possible
assertInRange(match1.camelCaseWeight, match2.camelCaseWeight + 1, 1 << 30);
});
it("PreferCaseSensitiveRelativeWeights2", () => {
const match1 = getFirstMatch("BazBarFooFooFoo", "FFF");
const match2 = getFirstMatch("BazFogBarFooFoo", "FFF");
// Contiguous things should also be preferred
assertInRange(match1.camelCaseWeight, match2.camelCaseWeight + 1, 1 << 30);
});
it("PreferCaseSensitiveRelativeWeights3", () => {
const match1 = getFirstMatch("FogBarFooFoo", "FFF");
const match2 = getFirstMatch("BarFooFooFoo", "FFF");
// The weight of being first should be greater than the weight of being contiguous
assertInRange(match1.camelCaseWeight, match2.camelCaseWeight + 1, 1 << 30);
});
it("AllLowerPattern1", () => {
const match = getFirstMatch("FogBarChangedEventArgs", "changedeventargs");
assert.isTrue(undefined !== match);
});
it("AllLowerPattern2", () => {
const match = getFirstMatch("FogBarChangedEventArgs", "changedeventarrrgh");
assert.isTrue(undefined === match);
});
it("AllLowerPattern3", () => {
const match = getFirstMatch("ABCDEFGH", "bcd");
assert.isTrue(undefined !== match);
});
it("AllLowerPattern4", () => {
const match = getFirstMatch("AbcdefghijEfgHij", "efghij");
assert.isTrue(undefined === match);
});
});
describe("MultiWordPattern", () => {
it("ExactWithLowercase", () => {
const matches = getAllMatches("AddMetadataReference", "addmetadatareference");
assertContainsKind(ts.PatternMatchKind.exact, matches);
});
it("SingleLowercasedSearchWord1", () => {
const matches = getAllMatches("AddMetadataReference", "add");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
});
it("SingleLowercasedSearchWord2", () => {
const matches = getAllMatches("AddMetadataReference", "metadata");
assertContainsKind(ts.PatternMatchKind.substring, matches);
});
it("SingleUppercaseSearchWord1", () => {
const matches = getAllMatches("AddMetadataReference", "Add");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
});
it("SingleUppercaseSearchWord2", () => {
const matches = getAllMatches("AddMetadataReference", "Metadata");
assertContainsKind(ts.PatternMatchKind.substring, matches);
});
it("SingleUppercaseSearchLetter1", () => {
const matches = getAllMatches("AddMetadataReference", "A");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
});
it("SingleUppercaseSearchLetter2", () => {
const matches = getAllMatches("AddMetadataReference", "M");
assertContainsKind(ts.PatternMatchKind.substring, matches);
});
it("TwoLowercaseWords", () => {
const matches = getAllMatches("AddMetadataReference", "add metadata");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertContainsKind(ts.PatternMatchKind.substring, matches);
});
it("TwoLowercaseWords", () => {
const matches = getAllMatches("AddMetadataReference", "A M");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertContainsKind(ts.PatternMatchKind.substring, matches);
});
it("TwoLowercaseWords", () => {
const matches = getAllMatches("AddMetadataReference", "AM");
assertContainsKind(ts.PatternMatchKind.camelCase, matches);
});
it("TwoLowercaseWords", () => {
const matches = getAllMatches("AddMetadataReference", "ref Metadata");
assertArrayEquals(ts.map(matches, m => m.kind), [ts.PatternMatchKind.substring, ts.PatternMatchKind.substring]);
});
it("TwoLowercaseWords", () => {
const matches = getAllMatches("AddMetadataReference", "ref M");
assertArrayEquals(ts.map(matches, m => m.kind), [ts.PatternMatchKind.substring, ts.PatternMatchKind.substring]);
});
it("MixedCamelCase", () => {
const matches = getAllMatches("AddMetadataReference", "AMRe");
assertContainsKind(ts.PatternMatchKind.camelCase, matches);
});
it("BlankPattern", () => {
const matches = getAllMatches("AddMetadataReference", "");
assert.isTrue(matches === undefined);
});
it("WhitespaceOnlyPattern", () => {
const matches = getAllMatches("AddMetadataReference", " ");
assert.isTrue(matches === undefined);
});
it("EachWordSeparately1", () => {
const matches = getAllMatches("AddMetadataReference", "add Meta");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertContainsKind(ts.PatternMatchKind.substring, matches);
});
it("EachWordSeparately2", () => {
const matches = getAllMatches("AddMetadataReference", "Add meta");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertContainsKind(ts.PatternMatchKind.substring, matches);
});
it("EachWordSeparately3", () => {
const matches = getAllMatches("AddMetadataReference", "Add Meta");
assertContainsKind(ts.PatternMatchKind.prefix, matches);
assertContainsKind(ts.PatternMatchKind.substring, matches);
});
it("MixedCasing", () => {
const matches = getAllMatches("AddMetadataReference", "mEta");
assert.isTrue(matches === undefined);
});
it("MixedCasing2", () => {
const matches = getAllMatches("AddMetadataReference", "Data");
assert.isTrue(matches === undefined);
});
it("AsteriskSplit", () => {
const matches = getAllMatches("GetKeyWord", "K*W");
assertArrayEquals(ts.map(matches, m => m.kind), [ts.PatternMatchKind.substring, ts.PatternMatchKind.substring]);
});
it("LowercaseSubstring1", () => {
const matches = getAllMatches("Operator", "a");
assert.isTrue(matches === undefined);
});
it("LowercaseSubstring2", () => {
const matches = getAllMatches("FooAttribute", "a");
assertContainsKind(ts.PatternMatchKind.substring, matches);
assert.isFalse(matches[0].isCaseSensitive);
});
});
describe("DottedPattern", () => {
it("DottedPattern1", () => {
const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "B.Q");
assert.equal(ts.PatternMatchKind.prefix, match.kind);
assert.equal(true, match.isCaseSensitive);
});
it("DottedPattern2", () => {
const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "C.Q");
assert.isTrue(match === undefined);
});
it("DottedPattern3", () => {
const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "B.B.Q");
assert.equal(ts.PatternMatchKind.prefix, match.kind);
assert.equal(true, match.isCaseSensitive);
});
it("DottedPattern4", () => {
const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "Baz.Quux");
assert.equal(ts.PatternMatchKind.exact, match.kind);
assert.equal(true, match.isCaseSensitive);
});
it("DottedPattern5", () => {
const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "F.B.B.Quux");
assert.equal(ts.PatternMatchKind.exact, match.kind);
assert.equal(true, match.isCaseSensitive);
});
it("DottedPattern6", () => {
const match = getFirstMatchForDottedPattern("Foo.Bar.Baz", "Quux", "F.F.B.B.Quux");
assert.isTrue(match === undefined);
});
it("DottedPattern7", () => {
let match = getFirstMatch("UIElement", "UIElement");
match = getFirstMatch("GetKeyword", "UIElement");
assert.isTrue(match === undefined);
});
});
function getFirstMatch(candidate: string, pattern: string): ts.PatternMatch {
const matches = ts.createPatternMatcher(pattern).getMatchesForLastSegmentOfPattern(candidate);
return matches ? matches[0] : undefined;
}
function getAllMatches(candidate: string, pattern: string): ts.PatternMatch[] {
return ts.createPatternMatcher(pattern).getMatchesForLastSegmentOfPattern(candidate);
}
function getFirstMatchForDottedPattern(dottedContainer: string, candidate: string, pattern: string): ts.PatternMatch {
const matches = ts.createPatternMatcher(pattern).getMatches(dottedContainer.split("."), candidate);
return matches ? matches[0] : undefined;
}
function spanListToSubstrings(identifier: string, spans: ts.TextSpan[]) {
return ts.map(spans, s => identifier.substr(s.start, s.length));
}
function breakIntoCharacterSpans(identifier: string) {
return spanListToSubstrings(identifier, ts.breakIntoCharacterSpans(identifier));
}
function breakIntoWordSpans(identifier: string) {
return spanListToSubstrings(identifier, ts.breakIntoWordSpans(identifier));
}
function assertArrayEquals<T>(array1: T[], array2: T[]) {
assert.equal(array1.length, array2.length);
for (let i = 0; i < array1.length; i++) {
assert.equal(array1[i], array2[i]);
}
}
function assertInRange(val: number, low: number, high: number) {
assert.isTrue(val >= low);
assert.isTrue(val <= high);
}
function verifyBreakIntoCharacterSpans(original: string, ...parts: string[]): void {
assertArrayEquals(parts, breakIntoCharacterSpans(original));
}
function verifyBreakIntoWordSpans(original: string, ...parts: string[]): void {
assertArrayEquals(parts, breakIntoWordSpans(original));
}
function assertContainsKind(kind: ts.PatternMatchKind, results: ts.PatternMatch[]) {
assert.isTrue(ts.forEach(results, r => r.kind === kind));
}
});
@@ -0,0 +1,459 @@
/// <reference path="..\..\harnessLanguageService.ts" />
describe("PreProcessFile:", function () {
function test(sourceText: string, readImportFile: boolean, detectJavaScriptImports: boolean, expectedPreProcess: ts.PreProcessedFileInfo): void {
const resultPreProcess = ts.preProcessFile(sourceText, readImportFile, detectJavaScriptImports);
assert.equal(resultPreProcess.isLibFile, expectedPreProcess.isLibFile, "Pre-processed file has different value for isLibFile. Expected: " + expectedPreProcess.isLibFile + ". Actual: " + resultPreProcess.isLibFile);
checkFileReferenceList("Imported files", expectedPreProcess.importedFiles, resultPreProcess.importedFiles);
checkFileReferenceList("Referenced files", expectedPreProcess.referencedFiles, resultPreProcess.referencedFiles);
checkFileReferenceList("Type reference directives", expectedPreProcess.typeReferenceDirectives, resultPreProcess.typeReferenceDirectives);
assert.deepEqual(resultPreProcess.ambientExternalModules, expectedPreProcess.ambientExternalModules);
}
function checkFileReferenceList(kind: string, expected: ts.FileReference[], actual: ts.FileReference[]) {
if (expected === actual) {
return;
}
if (!expected) {
assert.isTrue(false, `Expected ${JSON.stringify(expected)}, got ${JSON.stringify(actual)}`);
}
assert.equal(actual.length, expected.length, `[${kind}] Actual array's length does not match expected length. Expected files: ${JSON.stringify(expected)}, actual files: ${JSON.stringify(actual)}`);
for (let i = 0; i < expected.length; i++) {
const actualReference = actual[i];
const expectedReference = expected[i];
assert.equal(actualReference.fileName, expectedReference.fileName, `[${kind}] actual file path does not match expected. Expected: "${expectedReference.fileName}". Actual: "${actualReference.fileName}".`);
assert.equal(actualReference.pos, expectedReference.pos, `[${kind}] actual file start position does not match expected. Expected: "${expectedReference.pos}". Actual: "${actualReference.pos}".`);
assert.equal(actualReference.end, expectedReference.end, `[${kind}] actual file end pos does not match expected. Expected: "${expectedReference.end}". Actual: "${actualReference.end}".`);
}
}
describe("Test preProcessFiles,", function () {
it("Correctly return referenced files from triple slash", function () {
test("///<reference path = \"refFile1.ts\" />" + "\n" + "///<reference path =\"refFile2.ts\"/>" + "\n" + "///<reference path=\"refFile3.ts\" />" + "\n" + "///<reference path= \"..\\refFile4d.ts\" />",
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [{ fileName: "refFile1.ts", pos: 0, end: 37 }, { fileName: "refFile2.ts", pos: 38, end: 73 },
{ fileName: "refFile3.ts", pos: 74, end: 109 }, { fileName: "..\\refFile4d.ts", pos: 110, end: 150 }],
importedFiles: <ts.FileReference[]>[],
typeReferenceDirectives: [],
ambientExternalModules: undefined,
isLibFile: false
});
}),
it("Do not return reference path because of invalid triple-slash syntax", function () {
test("///<reference path\"refFile1.ts\" />" + "\n" + "///<reference path =\"refFile2.ts\">" + "\n" + "///<referencepath=\"refFile3.ts\" />" + "\n" + "///<reference pat= \"refFile4d.ts\" />",
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: <ts.FileReference[]>[],
importedFiles: <ts.FileReference[]>[],
typeReferenceDirectives: [],
ambientExternalModules: undefined,
isLibFile: false
});
}),
it("Correctly return imported files", function () {
test("import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\"); import i3= require(\"r3.ts\"); import i4=require(\"r4.ts\"); import i5 = require (\"r5.ts\");",
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: <ts.FileReference[]>[],
typeReferenceDirectives: [],
importedFiles: [{ fileName: "r1.ts", pos: 20, end: 25 }, { fileName: "r2.ts", pos: 49, end: 54 }, { fileName: "r3.ts", pos: 78, end: 83 },
{ fileName: "r4.ts", pos: 106, end: 111 }, { fileName: "r5.ts", pos: 138, end: 143 }],
ambientExternalModules: undefined,
isLibFile: false
});
}),
it("Do not return imported files if readImportFiles argument is false", function () {
test("import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\"); import i3= require(\"r3.ts\"); import i4=require(\"r4.ts\"); import i5 = require (\"r5.ts\");",
/*readImportFile*/ false,
/*detectJavaScriptImports*/ false,
{
referencedFiles: <ts.FileReference[]>[],
typeReferenceDirectives: [],
importedFiles: <ts.FileReference[]>[],
ambientExternalModules: undefined,
isLibFile: false
});
}),
it("Do not return import path because of invalid import syntax", function () {
test("import i1 require(\"r1.ts\"); import = require(\"r2.ts\") import i3= require(\"r3.ts\"); import i5",
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: <ts.FileReference[]>[],
typeReferenceDirectives: [],
importedFiles: [{ fileName: "r3.ts", pos: 73, end: 78 }],
ambientExternalModules: undefined,
isLibFile: false
});
}),
it("Correctly return referenced files and import files", function () {
test("///<reference path=\"refFile1.ts\" />" + "\n" + "///<reference path =\"refFile2.ts\"/>" + "\n" + "import i1 = require(\"r1.ts\"); import i2 =require(\"r2.ts\");",
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [{ fileName: "refFile1.ts", pos: 0, end: 35 }, { fileName: "refFile2.ts", pos: 36, end: 71 }],
typeReferenceDirectives: [],
importedFiles: [{ fileName: "r1.ts", pos: 92, end: 97 }, { fileName: "r2.ts", pos: 121, end: 126 }],
ambientExternalModules: undefined,
isLibFile: false
});
}),
it("Correctly return referenced files and import files even with some invalid syntax", function () {
test("///<reference path=\"refFile1.ts\" />" + "\n" + "///<reference path \"refFile2.ts\"/>" + "\n" + "import i1 = require(\"r1.ts\"); import = require(\"r2.ts\"); import i2 = require(\"r3.ts\");",
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [{ fileName: "refFile1.ts", pos: 0, end: 35 }],
typeReferenceDirectives: [],
importedFiles: [{ fileName: "r1.ts", pos: 91, end: 96 }, { fileName: "r3.ts", pos: 148, end: 153 }],
ambientExternalModules: undefined,
isLibFile: false
});
});
it("Correctly return ES6 imports", function () {
test("import * as ns from \"m1\";" + "\n" +
"import def, * as ns from \"m2\";" + "\n" +
"import def from \"m3\";" + "\n" +
"import {a} from \"m4\";" + "\n" +
"import {a as A} from \"m5\";" + "\n" +
"import {a as A, b, c as C} from \"m6\";" + "\n" +
"import def , {a, b, c as C} from \"m7\";" + "\n",
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ fileName: "m1", pos: 20, end: 22 },
{ fileName: "m2", pos: 51, end: 53 },
{ fileName: "m3", pos: 73, end: 75 },
{ fileName: "m4", pos: 95, end: 97 },
{ fileName: "m5", pos: 122, end: 124 },
{ fileName: "m6", pos: 160, end: 162 },
{ fileName: "m7", pos: 199, end: 201 }
],
ambientExternalModules: undefined,
isLibFile: false
});
});
it("Correctly return ES6 exports", function () {
test("export * from \"m1\";" + "\n" +
"export {a} from \"m2\";" + "\n" +
"export {a as A} from \"m3\";" + "\n" +
"export {a as A, b, c as C} from \"m4\";" + "\n",
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ fileName: "m1", pos: 14, end: 16 },
{ fileName: "m2", pos: 36, end: 38 },
{ fileName: "m3", pos: 63, end: 65 },
{ fileName: "m4", pos: 101, end: 103 },
],
ambientExternalModules: undefined,
isLibFile: false
});
});
it("Correctly return ambient external modules", () => {
test(`
declare module A {}
declare module "B" {}
function foo() {
}
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [],
ambientExternalModules: ["B"],
isLibFile: false
});
});
it("Correctly handles export import declarations", function () {
test("export import a = require(\"m1\");",
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ fileName: "m1", pos: 26, end: 28 }
],
ambientExternalModules: undefined,
isLibFile: false
});
});
it("Correctly handles export require calls in JavaScript files", function () {
test(`
export import a = require("m1");
var x = require('m2');
foo(require('m3'));
var z = { f: require('m4') }
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ true,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ fileName: "m1", pos: 39, end: 41 },
{ fileName: "m2", pos: 74, end: 76 },
{ fileName: "m3", pos: 105, end: 107 },
{ fileName: "m4", pos: 146, end: 148 },
],
ambientExternalModules: undefined,
isLibFile: false
});
});
it("Correctly handles dependency lists in define([deplist]) calls in JavaScript files", function () {
test(`
define(["mod1", "mod2"], (m1, m2) => {
});
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ true,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ fileName: "mod1", pos: 21, end: 25 },
{ fileName: "mod2", pos: 29, end: 33 },
],
ambientExternalModules: undefined,
isLibFile: false
});
});
it("Correctly handles dependency lists in define(modName, [deplist]) calls in JavaScript files", function () {
test(`
define("mod", ["mod1", "mod2"], (m1, m2) => {
});
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ true,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ fileName: "mod1", pos: 28, end: 32 },
{ fileName: "mod2", pos: 36, end: 40 },
],
ambientExternalModules: undefined,
isLibFile: false
});
});
it("correctly handles augmentations in external modules - 1", () => {
test(`
declare module "../Observable" {
interface I {}
}
export {}
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ "fileName": "../Observable", "pos": 28, "end": 41 }
],
ambientExternalModules: undefined,
isLibFile: false
});
});
it("correctly handles augmentations in external modules - 2", () => {
test(`
declare module "../Observable" {
interface I {}
}
import * as x from "m";
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ "fileName": "m", "pos": 123, "end": 124 },
{ "fileName": "../Observable", "pos": 28, "end": 41 }
],
ambientExternalModules: undefined,
isLibFile: false
});
});
it("correctly handles augmentations in external modules - 3", () => {
test(`
declare module "../Observable" {
interface I {}
}
import m = require("m");
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ "fileName": "m", "pos": 123, "end": 124 },
{ "fileName": "../Observable", "pos": 28, "end": 41 }
],
ambientExternalModules: undefined,
isLibFile: false
});
});
it("correctly handles augmentations in external modules - 4", () => {
test(`
declare module "../Observable" {
interface I {}
}
namespace N {}
export = N;
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ "fileName": "../Observable", "pos": 28, "end": 41 }
],
ambientExternalModules: undefined,
isLibFile: false
});
});
it("correctly handles augmentations in external modules - 5", () => {
test(`
declare module "../Observable" {
interface I {}
}
namespace N {}
export import IN = N;
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ "fileName": "../Observable", "pos": 28, "end": 41 }
],
ambientExternalModules: undefined,
isLibFile: false
});
});
it("correctly handles augmentations in external modules - 6", () => {
test(`
declare module "../Observable" {
interface I {}
}
export let x = 1;
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ "fileName": "../Observable", "pos": 28, "end": 41 }
],
ambientExternalModules: undefined,
isLibFile: false
});
});
it ("correctly handles augmentations in ambient external modules - 1", () => {
test(`
declare module "m1" {
export * from "m2";
declare module "augmentation" {
interface I {}
}
}
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ "fileName": "m2", "pos": 65, "end": 67 },
{ "fileName": "augmentation", "pos": 102, "end": 114 }
],
ambientExternalModules: ["m1"],
isLibFile: false
});
});
it ("correctly handles augmentations in ambient external modules - 2", () => {
test(`
namespace M { var x; }
import IM = M;
declare module "m1" {
export * from "m2";
declare module "augmentation" {
interface I {}
}
}
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [],
typeReferenceDirectives: [],
importedFiles: [
{ "fileName": "m2", "pos": 127, "end": 129 },
{ "fileName": "augmentation", "pos": 164, "end": 176 }
],
ambientExternalModules: ["m1"],
isLibFile: false
});
});
it ("correctly recognizes type reference directives", () => {
test(`
/// <reference path="a"/>
/// <reference types="a1"/>
/// <reference path="a2"/>
/// <reference types="a3"/>
`,
/*readImportFile*/ true,
/*detectJavaScriptImports*/ false,
{
referencedFiles: [
{ "pos": 13, "end": 38, "fileName": "a" },
{ "pos": 91, "end": 117, "fileName": "a2" }
],
typeReferenceDirectives: [
{ "pos": 51, "end": 78, "fileName": "a1" },
{ "pos": 130, "end": 157, "fileName": "a3" }
],
importedFiles: [],
ambientExternalModules: undefined,
isLibFile: false
});
});
});
});
+548
View File
@@ -0,0 +1,548 @@
/// <reference path="..\harness.ts" />
const expect: typeof _chai.expect = _chai.expect;
namespace ts.server {
let lastWrittenToHost: string;
const mockHost: ServerHost = {
args: [],
newLine: "\n",
useCaseSensitiveFileNames: true,
write(s): void { lastWrittenToHost = s; },
readFile(): string { return void 0; },
writeFile: noop,
resolvePath(): string { return void 0; },
fileExists: () => false,
directoryExists: () => false,
getDirectories: () => [],
createDirectory: noop,
getExecutingFilePath(): string { return void 0; },
getCurrentDirectory(): string { return void 0; },
getEnvironmentVariable(): string { return ""; },
readDirectory(): string[] { return []; },
exit: noop,
setTimeout() { return 0; },
clearTimeout: noop,
setImmediate: () => 0,
clearImmediate: noop,
createHash: s => s
};
const mockLogger: Logger = {
close: noop,
hasLevel(): boolean { return false; },
loggingEnabled(): boolean { return false; },
perftrc: noop,
info: noop,
startGroup: noop,
endGroup: noop,
msg: noop,
getLogFileName: (): string => undefined
};
class TestSession extends Session {
getProjectService() {
return this.projectService;
}
}
describe("the Session class", () => {
let session: TestSession;
let lastSent: protocol.Message;
function createSession(): TestSession {
const opts: server.SessionOptions = {
host: mockHost,
cancellationToken: nullCancellationToken,
useSingleInferredProject: false,
typingsInstaller: undefined,
byteLength: Utils.byteLength,
hrtime: process.hrtime,
logger: mockLogger,
canUseEvents: true
};
return new TestSession(opts);
}
beforeEach(() => {
session = createSession();
session.send = (msg: protocol.Message) => {
lastSent = msg;
};
});
describe("executeCommand", () => {
it("should throw when commands are executed with invalid arguments", () => {
const req: protocol.FileRequest = {
command: CommandNames.Open,
seq: 0,
type: "request",
arguments: {
file: undefined
}
};
expect(() => session.executeCommand(req)).to.throw();
});
it("should output an error response when a command does not exist", () => {
const req: protocol.Request = {
command: "foobar",
seq: 0,
type: "request"
};
session.executeCommand(req);
expect(lastSent).to.deep.equal(<protocol.Response>{
command: CommandNames.Unknown,
type: "response",
seq: 0,
message: "Unrecognized JSON command: foobar",
request_seq: 0,
success: false
});
});
it("should return a tuple containing the response and if a response is required on success", () => {
const req: protocol.ConfigureRequest = {
command: CommandNames.Configure,
seq: 0,
type: "request",
arguments: {
hostInfo: "unit test",
formatOptions: {
newLineCharacter: "`n"
}
}
};
expect(session.executeCommand(req)).to.deep.equal({
responseRequired: false
});
expect(lastSent).to.deep.equal({
command: CommandNames.Configure,
type: "response",
success: true,
request_seq: 0,
seq: 0,
body: undefined
});
});
it ("should handle literal types in request", () => {
const configureRequest: protocol.ConfigureRequest = {
command: CommandNames.Configure,
seq: 0,
type: "request",
arguments: {
formatOptions: {
indentStyle: "Block"
}
}
};
session.onMessage(JSON.stringify(configureRequest));
assert.equal(session.getProjectService().getFormatCodeOptions().indentStyle, IndentStyle.Block);
const setOptionsRequest: protocol.SetCompilerOptionsForInferredProjectsRequest = {
command: CommandNames.CompilerOptionsForInferredProjects,
seq: 1,
type: "request",
arguments: {
options: {
module: "System",
target: "ES5",
jsx: "React",
newLine: "Lf",
moduleResolution: "Node"
}
}
};
session.onMessage(JSON.stringify(setOptionsRequest));
assert.deepEqual(
session.getProjectService().getCompilerOptionsForInferredProjects(),
<CompilerOptions>{
module: ModuleKind.System,
target: ScriptTarget.ES5,
jsx: JsxEmit.React,
newLine: NewLineKind.LineFeed,
moduleResolution: ModuleResolutionKind.NodeJs,
allowNonTsExtensions: true // injected by tsserver
});
});
});
describe("onMessage", () => {
it("should not throw when commands are executed with invalid arguments", () => {
let i = 0;
for (const name in CommandNames) {
if (!Object.prototype.hasOwnProperty.call(CommandNames, name)) {
continue;
}
const req: protocol.Request = {
command: name,
seq: i,
type: "request"
};
i++;
session.onMessage(JSON.stringify(req));
req.seq = i;
i++;
req.arguments = {};
session.onMessage(JSON.stringify(req));
req.seq = i;
i++;
/* tslint:disable no-null-keyword */
req.arguments = null;
/* tslint:enable no-null-keyword */
session.onMessage(JSON.stringify(req));
req.seq = i;
i++;
req.arguments = "";
session.onMessage(JSON.stringify(req));
req.seq = i;
i++;
req.arguments = 0;
session.onMessage(JSON.stringify(req));
req.seq = i;
i++;
req.arguments = [];
session.onMessage(JSON.stringify(req));
}
session.onMessage("GARBAGE NON_JSON DATA");
});
it("should output the response for a correctly handled message", () => {
const req: protocol.ConfigureRequest = {
command: CommandNames.Configure,
seq: 0,
type: "request",
arguments: {
hostInfo: "unit test",
formatOptions: {
newLineCharacter: "`n"
}
}
};
session.onMessage(JSON.stringify(req));
expect(lastSent).to.deep.equal(<protocol.ConfigureResponse>{
command: CommandNames.Configure,
type: "response",
success: true,
request_seq: 0,
seq: 0,
body: undefined
});
});
});
describe("send", () => {
it("is an overrideable handle which sends protocol messages over the wire", () => {
const msg: server.protocol.Request = { seq: 0, type: "request", command: "" };
const strmsg = JSON.stringify(msg);
const len = 1 + Utils.byteLength(strmsg, "utf8");
const resultMsg = `Content-Length: ${len}\r\n\r\n${strmsg}\n`;
session.send = Session.prototype.send;
assert(session.send);
expect(session.send(msg)).to.not.exist;
expect(lastWrittenToHost).to.equal(resultMsg);
});
});
describe("addProtocolHandler", () => {
it("can add protocol handlers", () => {
const respBody = {
item: false
};
const command = "newhandle";
const result = {
response: respBody,
responseRequired: true
};
session.addProtocolHandler(command, () => result);
expect(session.executeCommand({
command,
seq: 0,
type: "request"
})).to.deep.equal(result);
});
it("throws when a duplicate handler is passed", () => {
const respBody = {
item: false
};
const resp = {
response: respBody,
responseRequired: true
};
const command = "newhandle";
session.addProtocolHandler(command, () => resp);
expect(() => session.addProtocolHandler(command, () => resp))
.to.throw(`Protocol handler already exists for command "${command}"`);
});
});
describe("event", () => {
it("can format event responses and send them", () => {
const evt = "notify-test";
const info = {
test: true
};
session.event(info, evt);
expect(lastSent).to.deep.equal({
type: "event",
seq: 0,
event: evt,
body: info
});
});
});
describe("output", () => {
it("can format command responses and send them", () => {
const body = {
block: {
key: "value"
}
};
const command = "test";
session.output(body, command);
expect(lastSent).to.deep.equal({
seq: 0,
request_seq: 0,
type: "response",
command,
body: body,
success: true
});
});
});
});
describe("how Session is extendable via subclassing", () => {
class TestSession extends Session {
lastSent: protocol.Message;
customHandler = "testhandler";
constructor() {
super({
host: mockHost,
cancellationToken: nullCancellationToken,
useSingleInferredProject: false,
typingsInstaller: undefined,
byteLength: Utils.byteLength,
hrtime: process.hrtime,
logger: mockLogger,
canUseEvents: true
});
this.addProtocolHandler(this.customHandler, () => {
return { response: undefined, responseRequired: true };
});
}
send(msg: protocol.Message) {
this.lastSent = msg;
}
}
it("can override methods such as send", () => {
const session = new TestSession();
const body = {
block: {
key: "value"
}
};
const command = "test";
session.output(body, command);
expect(session.lastSent).to.deep.equal({
seq: 0,
request_seq: 0,
type: "response",
command,
body: body,
success: true
});
});
it("can add and respond to new protocol handlers", () => {
const session = new TestSession();
expect(session.executeCommand({
seq: 0,
type: "request",
command: session.customHandler
})).to.deep.equal({
response: undefined,
responseRequired: true
});
});
it("has access to the project service", () => {
class ServiceSession extends TestSession {
constructor() {
super();
assert(this.projectService);
expect(this.projectService).to.be.instanceOf(ProjectService);
}
}
new ServiceSession();
});
});
describe("an example of using the Session API to create an in-process server", () => {
class InProcSession extends Session {
private queue: protocol.Request[] = [];
constructor(private client: InProcClient) {
super({
host: mockHost,
cancellationToken: nullCancellationToken,
useSingleInferredProject: false,
typingsInstaller: undefined,
byteLength: Utils.byteLength,
hrtime: process.hrtime,
logger: mockLogger,
canUseEvents: true
});
this.addProtocolHandler("echo", (req: protocol.Request) => ({
response: req.arguments,
responseRequired: true
}));
}
send(msg: protocol.Message) {
this.client.handle(msg);
}
enqueue(msg: protocol.Request) {
this.queue.unshift(msg);
}
handleRequest(msg: protocol.Request) {
let response: protocol.Response;
try {
({ response } = this.executeCommand(msg));
}
catch (e) {
this.output(undefined, msg.command, msg.seq, e.toString());
return;
}
if (response) {
this.output(response, msg.command, msg.seq);
}
}
consumeQueue() {
while (this.queue.length > 0) {
const elem = this.queue.pop();
this.handleRequest(elem);
}
}
}
class InProcClient {
private server: InProcSession;
private seq = 0;
private callbacks: Array<(resp: protocol.Response) => void> = [];
private eventHandlers = createMap<(args: any) => void>();
handle(msg: protocol.Message): void {
if (msg.type === "response") {
const response = <protocol.Response>msg;
const handler = this.callbacks[response.request_seq];
if (handler) {
handler(response);
delete this.callbacks[response.request_seq];
}
}
else if (msg.type === "event") {
const event = <protocol.Event>msg;
this.emit(event.event, event.body);
}
}
emit(name: string, args: any): void {
const handler = this.eventHandlers.get(name);
if (handler) {
handler(args);
}
}
on(name: string, handler: (args: any) => void): void {
this.eventHandlers.set(name, handler);
}
connect(session: InProcSession): void {
this.server = session;
}
execute(command: string, args: any, callback: (resp: protocol.Response) => void): void {
if (!this.server) {
return;
}
this.seq++;
this.server.enqueue({
seq: this.seq,
type: "request",
command,
arguments: args
});
this.callbacks[this.seq] = callback;
}
}
it("can be constructed and respond to commands", (done) => {
const cli = new InProcClient();
const session = new InProcSession(cli);
const toEcho = {
data: true
};
const toEvent = {
data: false
};
let responses = 0;
// Connect the client
cli.connect(session);
// Add an event handler
cli.on("testevent", (eventinfo) => {
expect(eventinfo).to.equal(toEvent);
responses++;
expect(responses).to.equal(1);
});
// Trigger said event from the server
session.event(toEvent, "testevent");
// Queue an echo command
cli.execute("echo", toEcho, (resp) => {
assert(resp.success, resp.message);
responses++;
expect(responses).to.equal(2);
expect(resp.body).to.deep.equal(toEcho);
});
// Queue a configure command
cli.execute("configure", {
hostInfo: "unit test",
formatOptions: {
newLineCharacter: "`n"
}
}, (resp) => {
assert(resp.success, resp.message);
responses++;
expect(responses).to.equal(3);
done();
});
// Consume the queue and trigger the callbacks
session.consumeQueue();
});
});
}
+794
View File
@@ -0,0 +1,794 @@
/// <reference path="..\..\compiler\emitter.ts" />
/// <reference path="..\..\services\textChanges.ts" />
/// <reference path="..\harness.ts" />
// Some tests have trailing whitespace
// tslint:disable trim-trailing-whitespace
namespace ts {
describe("textChanges", () => {
function findChild(name: string, n: Node) {
return find(n);
function find(node: Node): Node {
if (isDeclaration(node) && node.name && isIdentifier(node.name) && node.name.text === name) {
return node;
}
else {
return forEachChild(node, find);
}
}
}
const printerOptions = { newLine: NewLineKind.LineFeed };
const newLineCharacter = getNewLineCharacter(printerOptions);
function getRuleProvider(action?: (opts: FormatCodeSettings) => void) {
const options = {
indentSize: 4,
tabSize: 4,
newLineCharacter,
convertTabsToSpaces: true,
indentStyle: ts.IndentStyle.Smart,
insertSpaceAfterConstructor: false,
insertSpaceAfterCommaDelimiter: true,
insertSpaceAfterSemicolonInForStatements: true,
insertSpaceBeforeAndAfterBinaryOperators: true,
insertSpaceAfterKeywordsInControlFlowStatements: true,
insertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
insertSpaceBeforeFunctionParenthesis: false,
placeOpenBraceOnNewLineForFunctions: false,
placeOpenBraceOnNewLineForControlBlocks: false,
};
if (action) {
action(options);
}
const rulesProvider = new formatting.RulesProvider();
rulesProvider.ensureUpToDate(options);
return rulesProvider;
}
// validate that positions that were recovered from the printed text actually match positions that will be created if the same text is parsed.
function verifyPositions({ text, node }: textChanges.NonFormattedText): void {
const nodeList = flattenNodes(node);
const sourceFile = createSourceFile("f.ts", text, ScriptTarget.ES2015);
const parsedNodeList = flattenNodes(sourceFile.statements[0]);
Debug.assert(nodeList.length === parsedNodeList.length);
for (let i = 0; i < nodeList.length; i++) {
const left = nodeList[i];
const right = parsedNodeList[i];
Debug.assert(left.pos === right.pos);
Debug.assert(left.end === right.end);
}
function flattenNodes(n: Node) {
const data: (Node | NodeArray<any>)[] = [];
walk(n);
return data;
function walk(n: Node | Node[]): void {
data.push(<any>n);
return isArray(n) ? forEach(n, walk) : forEachChild(n, walk, walk);
}
}
}
function runSingleFileTest(caption: string, setupFormatOptions: (opts: FormatCodeSettings) => void, text: string, validateNodes: boolean, testBlock: (sourceFile: SourceFile, changeTracker: textChanges.ChangeTracker) => void) {
it(caption, () => {
Harness.Baseline.runBaseline(`textChanges/${caption}.js`, () => {
const sourceFile = createSourceFile("source.ts", text, ScriptTarget.ES2015, /*setParentNodes*/ true);
const rulesProvider = getRuleProvider(setupFormatOptions);
const changeTracker = new textChanges.ChangeTracker(printerOptions.newLine, rulesProvider, validateNodes ? verifyPositions : undefined);
testBlock(sourceFile, changeTracker);
const changes = changeTracker.getChanges();
assert.equal(changes.length, 1);
assert.equal(changes[0].fileName, sourceFile.fileName);
const modified = textChanges.applyChanges(sourceFile.text, changes[0].textChanges);
return `===ORIGINAL===${newLineCharacter}${text}${newLineCharacter}===MODIFIED===${newLineCharacter}${modified}`;
});
});
}
function setNewLineForOpenBraceInFunctions(opts: FormatCodeSettings) {
opts.placeOpenBraceOnNewLineForFunctions = true;
}
{
const text = `
namespace M
{
namespace M2
{
function foo() {
// comment 1
const x = 1;
/**
* comment 2 line 1
* comment 2 line 2
*/
function f() {
return 100;
}
const y = 2; // comment 3
return 1;
}
}
}`;
runSingleFileTest("extractMethodLike", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
const statements = (<Block>(<FunctionDeclaration>findChild("foo", sourceFile)).body).statements.slice(1);
const newFunction = createFunctionDeclaration(
/*decorators*/ undefined,
/*modifiers*/ undefined,
/*asteriskToken*/ undefined,
/*name*/ "bar",
/*typeParameters*/ undefined,
/*parameters*/ emptyArray,
/*type*/ createKeywordTypeNode(SyntaxKind.AnyKeyword),
/*body */ createBlock(statements)
);
changeTracker.insertNodeBefore(sourceFile, /*before*/findChild("M2", sourceFile), newFunction, { suffix: newLineCharacter });
// replace statements with return statement
const newStatement = createReturn(
createCall(
/*expression*/ newFunction.name,
/*typeArguments*/ undefined,
/*argumentsArray*/ emptyArray
));
changeTracker.replaceNodeRange(sourceFile, statements[0], lastOrUndefined(statements), newStatement, { suffix: newLineCharacter });
});
}
{
const text = `
function foo() {
return 1;
}
function bar() {
return 2;
}
`;
runSingleFileTest("deleteRange1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteRange(sourceFile, { pos: text.indexOf("function foo"), end: text.indexOf("function bar") });
});
}
function findVariableStatementContaining(name: string, sourceFile: SourceFile) {
const varDecl = findChild(name, sourceFile);
assert.equal(varDecl.kind, SyntaxKind.VariableDeclaration);
const varStatement = varDecl.parent.parent;
assert.equal(varStatement.kind, SyntaxKind.VariableStatement);
return varStatement;
}
{
const text = `
var x = 1; // some comment - 1
/**
* comment 2
*/
var y = 2; // comment 3
var z = 3; // comment 4
`;
runSingleFileTest("deleteNode1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile));
});
runSingleFileTest("deleteNode2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true });
});
runSingleFileTest("deleteNode3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedEndPosition: true });
});
runSingleFileTest("deleteNode4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("y", sourceFile), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
runSingleFileTest("deleteNode5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findVariableStatementContaining("x", sourceFile));
});
}
{
const text = `
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
`;
runSingleFileTest("deleteNodeRange1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile));
});
runSingleFileTest("deleteNodeRange2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile),
{ useNonAdjustedStartPosition: true });
});
runSingleFileTest("deleteNodeRange3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile),
{ useNonAdjustedEndPosition: true });
});
runSingleFileTest("deleteNodeRange4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile),
{ useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
}
function createTestVariableDeclaration(name: string) {
return createVariableDeclaration(name, /*type*/ undefined, createObjectLiteral([createPropertyAssignment("p1", createLiteral(1))], /*multiline*/ true));
}
function createTestClass() {
return createClassDeclaration(
/*decorators*/ undefined,
[
createToken(SyntaxKind.PublicKeyword)
],
"class1",
/*typeParameters*/ undefined,
[
createHeritageClause(
SyntaxKind.ImplementsKeyword,
[
createExpressionWithTypeArguments(/*typeArguments*/ undefined, createIdentifier("interface1"))
]
)
],
[
createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
"property1",
/*questionToken*/ undefined,
createKeywordTypeNode(SyntaxKind.BooleanKeyword),
/*initializer*/ undefined
)
]
);
}
{
const text = `
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7`;
runSingleFileTest("replaceRange", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("replaceRangeWithForcedIndentation", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceRange(sourceFile, { pos: text.indexOf("var y"), end: text.indexOf("var a") }, createTestClass(), { suffix: newLineCharacter, indentation: 8, delta: 0 });
});
runSingleFileTest("replaceRangeNoLineBreakBefore", setNewLineForOpenBraceInFunctions, `const x = 1, y = "2";`, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createTestVariableDeclaration("z1");
changeTracker.replaceRange(sourceFile, { pos: sourceFile.text.indexOf("y"), end: sourceFile.text.indexOf(";") }, newNode);
});
}
{
const text = `
namespace A {
const x = 1, y = "2";
}
`;
runSingleFileTest("replaceNode1NoLineBreakBefore", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createTestVariableDeclaration("z1");
changeTracker.replaceNode(sourceFile, findChild("y", sourceFile), newNode);
});
}
{
const text = `
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7`;
runSingleFileTest("replaceNode1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("replaceNode2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter });
});
runSingleFileTest("replaceNode3", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter });
});
runSingleFileTest("replaceNode4", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
runSingleFileTest("replaceNode5", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNode(sourceFile, findVariableStatementContaining("x", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
}
{
const text = `
// comment 1
var x = 1; // comment 2
// comment 3
var y = 2; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7`;
runSingleFileTest("replaceNodeRange1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("replaceNodeRange2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, suffix: newLineCharacter, prefix: newLineCharacter });
});
runSingleFileTest("replaceNodeRange3", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedEndPosition: true, suffix: newLineCharacter });
});
runSingleFileTest("replaceNodeRange4", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.replaceNodeRange(sourceFile, findVariableStatementContaining("y", sourceFile), findVariableStatementContaining("z", sourceFile), createTestClass(), { useNonAdjustedStartPosition: true, useNonAdjustedEndPosition: true });
});
}
{
const text = `
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7`;
runSingleFileTest("insertNodeAt1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeAt(sourceFile, text.indexOf("var y"), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("insertNodeAt2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAt(sourceFile, text.indexOf("; // comment 4"), createTestVariableDeclaration("z1"));
});
}
{
const text = `
namespace M {
// comment 1
var x = 1; // comment 2
// comment 3
var y; // comment 4
var z = 3; // comment 5
// comment 6
var a = 4; // comment 7
}`;
runSingleFileTest("insertNodeBefore1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeBefore(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("insertNodeBefore2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeBefore(sourceFile, findChild("M", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("insertNodeAfter1", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("y", sourceFile), createTestClass(), { suffix: newLineCharacter });
});
runSingleFileTest("insertNodeAfter2", setNewLineForOpenBraceInFunctions, text, /*validateNodes*/ true, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findChild("M", sourceFile), createTestClass(), { prefix: newLineCharacter });
});
}
{
function findOpenBraceForConstructor(sourceFile: SourceFile) {
const classDecl = <ClassDeclaration>sourceFile.statements[0];
const constructorDecl = forEach(classDecl.members, m => m.kind === SyntaxKind.Constructor && (<ConstructorDeclaration>m).body && <ConstructorDeclaration>m);
return constructorDecl.body.getFirstToken();
}
function createTestSuperCall() {
const superCall = createCall(
createSuper(),
/*typeArguments*/ undefined,
/*argumentsArray*/ emptyArray
);
return createStatement(superCall);
}
const text1 = `
class A {
constructor() {
}
}
`;
runSingleFileTest("insertNodeAfter3", noop, text1, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { suffix: newLineCharacter });
});
const text2 = `
class A {
constructor() {
var x = 1;
}
}
`;
runSingleFileTest("insertNodeAfter4", noop, text2, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), createTestSuperCall(), { suffix: newLineCharacter });
});
const text3 = `
class A {
constructor() {
}
}
`;
runSingleFileTest("insertNodeAfter3-block with newline", noop, text3, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findOpenBraceForConstructor(sourceFile), createTestSuperCall(), { suffix: newLineCharacter });
});
}
{
const text = `var a = 1, b = 2, c = 3;`;
runSingleFileTest("deleteNodeInList1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `var a = 1,b = 2,c = 3;`;
runSingleFileTest("deleteNodeInList1_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList2_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList3_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
namespace M {
var a = 1,
b = 2,
c = 3;
}`;
runSingleFileTest("deleteNodeInList4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList6", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
namespace M {
var a = 1, // comment 1
// comment 2
b = 2, // comment 3
// comment 4
c = 3; // comment 5
}`;
runSingleFileTest("deleteNodeInList4_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList5_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList6_1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
function foo(a: number, b: string, c = true) {
return 1;
}`;
runSingleFileTest("deleteNodeInList7", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList8", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList9", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
function foo(a: number,b: string,c = true) {
return 1;
}`;
runSingleFileTest("deleteNodeInList10", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList11", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList12", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
function foo(
a: number,
b: string,
c = true) {
return 1;
}`;
runSingleFileTest("deleteNodeInList13", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("a", sourceFile));
});
runSingleFileTest("deleteNodeInList14", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("b", sourceFile));
});
runSingleFileTest("deleteNodeInList15", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNodeInList(sourceFile, findChild("c", sourceFile));
});
}
{
const text = `
const x = 1, y = 2;`;
runSingleFileTest("insertNodeInListAfter1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
runSingleFileTest("insertNodeInListAfter2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
{
const text = `
const /*x*/ x = 1, /*y*/ y = 2;`;
runSingleFileTest("insertNodeInListAfter3", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
runSingleFileTest("insertNodeInListAfter4", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
{
const text = `
const x = 1;`;
runSingleFileTest("insertNodeInListAfter5", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
{
const text = `
const x = 1,
y = 2;`;
runSingleFileTest("insertNodeInListAfter6", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
runSingleFileTest("insertNodeInListAfter7", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
{
const text = `
const /*x*/ x = 1,
/*y*/ y = 2;`;
runSingleFileTest("insertNodeInListAfter8", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
runSingleFileTest("insertNodeInListAfter9", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("y", sourceFile), createVariableDeclaration("z", /*type*/ undefined, createLiteral(1)));
});
}
{
const text = `
import {
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter10", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a")));
});
}
{
const text = `
import {
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter11", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a")));
});
}
{
const text = `
import {
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter12", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
});
}
{
const text = `
import {
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter13", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
});
}
{
const text = `
import {
x0,
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter14", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a")));
});
}
{
const text = `
import {
x0,
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter15", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(createIdentifier("b"), createIdentifier("a")));
});
}
{
const text = `
import {
x0,
x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter16", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
});
}
{
const text = `
import {
x0,
x // this is x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter17", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
});
}
{
const text = `
import {
x0, x
} from "bar"`;
runSingleFileTest("insertNodeInListAfter18", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeInListAfter(sourceFile, findChild("x", sourceFile), createImportSpecifier(undefined, createIdentifier("a")));
});
}
{
const text = `
class A {
x;
}`;
runSingleFileTest("insertNodeAfterMultipleNodes", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNodes = [];
for (let i = 0; i < 11 /*error doesn't occur with fewer nodes*/; ++i) {
newNodes.push(
createProperty(undefined, undefined, i + "", undefined, undefined, undefined));
}
const insertAfter = findChild("x", sourceFile);
for (const newNode of newNodes) {
changeTracker.insertNodeAfter(sourceFile, insertAfter, newNode, { suffix: newLineCharacter });
}
});
}
{
const text = `
class A {
x
}
`;
runSingleFileTest("insertNodeAfterInClass1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), createProperty(undefined, undefined, "a", undefined, createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined), { suffix: newLineCharacter });
});
}
{
const text = `
class A {
x;
}
`;
runSingleFileTest("insertNodeAfterInClass2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), createProperty(undefined, undefined, "a", undefined, createKeywordTypeNode(SyntaxKind.BooleanKeyword), undefined), { suffix: newLineCharacter });
});
}
{
const text = `
class A {
x;
y = 1;
}
`;
runSingleFileTest("deleteNodeAfterInClass1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findChild("x", sourceFile));
});
}
{
const text = `
class A {
x
y = 1;
}
`;
runSingleFileTest("deleteNodeAfterInClass2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
changeTracker.deleteNode(sourceFile, findChild("x", sourceFile));
});
}
{
const text = `
class A {
x = foo
}
`;
runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createComputedPropertyName(createLiteral(1)),
/*questionToken*/ undefined,
createKeywordTypeNode(SyntaxKind.AnyKeyword),
/*initializer*/ undefined);
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode, { suffix: newLineCharacter });
});
}
{
const text = `
class A {
x() {
}
}
`;
runSingleFileTest("insertNodeInClassAfterNodeWithoutSeparator2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createComputedPropertyName(createLiteral(1)),
/*questionToken*/ undefined,
createKeywordTypeNode(SyntaxKind.AnyKeyword),
/*initializer*/ undefined);
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode, { suffix: newLineCharacter });
});
}
{
const text = `
interface A {
x
}
`;
runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createComputedPropertyName(createLiteral(1)),
/*questionToken*/ undefined,
createKeywordTypeNode(SyntaxKind.AnyKeyword),
/*initializer*/ undefined);
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode, { suffix: newLineCharacter });
});
}
{
const text = `
interface A {
x()
}
`;
runSingleFileTest("insertNodeInInterfaceAfterNodeWithoutSeparator2", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createProperty(
/*decorators*/ undefined,
/*modifiers*/ undefined,
createComputedPropertyName(createLiteral(1)),
/*questionToken*/ undefined,
createKeywordTypeNode(SyntaxKind.AnyKeyword),
/*initializer*/ undefined);
changeTracker.insertNodeAfter(sourceFile, findChild("x", sourceFile), newNode, { suffix: newLineCharacter });
});
}
{
const text = `
let x = foo
`;
runSingleFileTest("insertNodeInStatementListAfterNodeWithoutSeparator1", noop, text, /*validateNodes*/ false, (sourceFile, changeTracker) => {
const newNode = createStatement(createParen(createLiteral(1)));
changeTracker.insertNodeAfter(sourceFile, findVariableStatementContaining("x", sourceFile), newNode, { suffix: newLineCharacter });
});
}
});
}
+70
View File
@@ -0,0 +1,70 @@
/// <reference path="../harness.ts" />
/// <reference path="../../server/scriptVersionCache.ts"/>
/// <reference path="./tsserverProjectSystem.ts" />
namespace ts.textStorage {
describe("Text storage", () => {
const f = {
path: "/a/app.ts",
content: `
let x = 1;
let y = 2;
function bar(a: number) {
return a + 1;
}`
};
it("text based storage should be have exactly the same as script version cache", () => {
const host = ts.projectSystem.createServerHost([f]);
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path));
const ts2 = new server.TextStorage(host, server.asNormalizedPath(f.path));
ts1.useScriptVersionCache();
ts2.useText();
const lineMap = computeLineStarts(f.content);
for (let line = 0; line < lineMap.length; line++) {
const start = lineMap[line];
const end = line === lineMap.length - 1 ? f.path.length : lineMap[line + 1];
for (let offset = 0; offset < end - start; offset++) {
const pos1 = ts1.lineOffsetToPosition(line + 1, offset + 1);
const pos2 = ts2.lineOffsetToPosition(line + 1, offset + 1);
assert.isTrue(pos1 === pos2, `lineOffsetToPosition ${line + 1}-${offset + 1}: expected ${pos1} to equal ${pos2}`);
}
const {start: start1, length: length1 } = ts1.lineToTextSpan(line);
const {start: start2, length: length2 } = ts2.lineToTextSpan(line);
assert.isTrue(start1 === start2, `lineToTextSpan ${line}::start:: expected ${start1} to equal ${start2}`);
assert.isTrue(length1 === length2, `lineToTextSpan ${line}::length:: expected ${length1} to equal ${length2}`);
}
for (let pos = 0; pos < f.content.length; pos++) {
const { line: line1, offset: offset1 } = ts1.positionToLineOffset(pos);
const { line: line2, offset: offset2 } = ts2.positionToLineOffset(pos);
assert.isTrue(line1 === line2, `positionToLineOffset ${pos}::line:: expected ${line1} to equal ${line2}`);
assert.isTrue(offset1 === offset2, `positionToLineOffset ${pos}::offset:: expected ${offset1} to equal ${offset2}`);
}
});
it("should switch to script version cache if necessary", () => {
const host = ts.projectSystem.createServerHost([f]);
const ts1 = new server.TextStorage(host, server.asNormalizedPath(f.path));
ts1.getSnapshot();
assert.isTrue(!ts1.hasScriptVersionCache(), "should not have script version cache - 1");
ts1.edit(0, 5, " ");
assert.isTrue(ts1.hasScriptVersionCache(), "have script version cache - 1");
ts1.useText();
assert.isTrue(!ts1.hasScriptVersionCache(), "should not have script version cache - 2");
ts1.getLineInfo(0);
assert.isTrue(ts1.hasScriptVersionCache(), "have script version cache - 2");
});
});
}
+79
View File
@@ -0,0 +1,79 @@
/// <reference path="..\..\services\transform.ts" />
/// <reference path="..\harness.ts" />
namespace ts {
describe("TransformAPI", () => {
function replaceUndefinedWithVoid0(context: ts.TransformationContext) {
const previousOnSubstituteNode = context.onSubstituteNode;
context.enableSubstitution(SyntaxKind.Identifier);
context.onSubstituteNode = (hint, node) => {
node = previousOnSubstituteNode(hint, node);
if (hint === EmitHint.Expression && node.kind === SyntaxKind.Identifier && (<Identifier>node).text === "undefined") {
node = createPartiallyEmittedExpression(
addSyntheticTrailingComment(
setTextRange(
createVoidZero(),
node),
SyntaxKind.MultiLineCommentTrivia, "undefined"));
}
return node;
};
return (file: ts.SourceFile) => file;
}
function replaceIdentifiersNamedOldNameWithNewName(context: ts.TransformationContext) {
const previousOnSubstituteNode = context.onSubstituteNode;
context.enableSubstitution(SyntaxKind.Identifier);
context.onSubstituteNode = (hint, node) => {
node = previousOnSubstituteNode(hint, node);
if (node.kind === SyntaxKind.Identifier && (<Identifier>node).text === "oldName") {
node = setTextRange(createIdentifier("newName"), node);
}
return node;
};
return (file: ts.SourceFile) => file;
}
function transformSourceFile(sourceText: string, transformers: TransformerFactory<SourceFile>[]) {
const transformed = transform(createSourceFile("source.ts", sourceText, ScriptTarget.ES2015), transformers);
const printer = createPrinter({ newLine: NewLineKind.CarriageReturnLineFeed }, {
onEmitNode: transformed.emitNodeWithNotification,
substituteNode: transformed.substituteNode
});
const result = printer.printBundle(createBundle(transformed.transformed));
transformed.dispose();
return result;
}
function testBaseline(testName: string, test: () => string) {
it(testName, () => {
Harness.Baseline.runBaseline(`transformApi/transformsCorrectly.${testName}.js`, test);
});
}
testBaseline("substitution", () => {
return transformSourceFile(`var a = undefined;`, [replaceUndefinedWithVoid0]);
});
testBaseline("types", () => {
return transformSourceFile(`let a: () => void`, [
context => file => visitNode(file, function visitor(node: Node): VisitResult<Node> {
return visitEachChild(node, visitor, context);
})
]);
});
testBaseline("fromTranspileModule", () => {
return ts.transpileModule(`var oldName = undefined;`, {
transformers: {
before: [replaceUndefinedWithVoid0],
after: [replaceIdentifiersNamedOldNameWithNewName]
},
compilerOptions: {
newLine: NewLineKind.CarriageReturnLineFeed
}
}).outputText;
});
});
}
+478
View File
@@ -0,0 +1,478 @@
/// <reference path="..\harness.ts" />
namespace ts {
describe("Transpile", () => {
interface TranspileTestSettings {
options?: TranspileOptions;
}
function transpilesCorrectly(name: string, input: string, testSettings: TranspileTestSettings) {
describe(name, () => {
let justName: string;
let transpileOptions: TranspileOptions;
let canUseOldTranspile: boolean;
let toBeCompiled: Harness.Compiler.TestFile[];
let transpileResult: TranspileOutput;
let oldTranspileResult: string;
let oldTranspileDiagnostics: Diagnostic[];
before(() => {
transpileOptions = testSettings.options || {};
if (!transpileOptions.compilerOptions) {
transpileOptions.compilerOptions = {};
}
if (transpileOptions.compilerOptions.newLine === undefined) {
// use \r\n as default new line
transpileOptions.compilerOptions.newLine = ts.NewLineKind.CarriageReturnLineFeed;
}
transpileOptions.compilerOptions.sourceMap = true;
if (!transpileOptions.fileName) {
transpileOptions.fileName = transpileOptions.compilerOptions.jsx ? "file.tsx" : "file.ts";
}
transpileOptions.reportDiagnostics = true;
justName = "transpile/" + name.replace(/[^a-z0-9\-. ]/ig, "") + (transpileOptions.compilerOptions.jsx ? ".tsx" : ".ts");
toBeCompiled = [{
unitName: transpileOptions.fileName,
content: input
}];
canUseOldTranspile = !transpileOptions.renamedDependencies;
transpileResult = transpileModule(input, transpileOptions);
if (canUseOldTranspile) {
oldTranspileDiagnostics = [];
oldTranspileResult = transpile(input, transpileOptions.compilerOptions, transpileOptions.fileName, oldTranspileDiagnostics, transpileOptions.moduleName);
}
});
after(() => {
justName = undefined;
transpileOptions = undefined;
canUseOldTranspile = undefined;
toBeCompiled = undefined;
transpileResult = undefined;
oldTranspileResult = undefined;
oldTranspileDiagnostics = undefined;
});
it("Correct errors for " + justName, () => {
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".errors.txt"), () => {
if (transpileResult.diagnostics.length === 0) {
/* tslint:disable:no-null-keyword */
return null;
/* tslint:enable:no-null-keyword */
}
return Harness.Compiler.getErrorBaseline(toBeCompiled, transpileResult.diagnostics);
});
});
if (canUseOldTranspile) {
it("Correct errors (old transpile) for " + justName, () => {
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.errors.txt"), () => {
if (oldTranspileDiagnostics.length === 0) {
/* tslint:disable:no-null-keyword */
return null;
/* tslint:enable:no-null-keyword */
}
return Harness.Compiler.getErrorBaseline(toBeCompiled, oldTranspileDiagnostics);
});
});
}
it("Correct output for " + justName, () => {
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".js"), () => {
if (transpileResult.outputText) {
return transpileResult.outputText;
}
else {
// This can happen if compiler recieve invalid compiler-options
/* tslint:disable:no-null-keyword */
return null;
/* tslint:enable:no-null-keyword */
}
});
});
if (canUseOldTranspile) {
it("Correct output (old transpile) for " + justName, () => {
Harness.Baseline.runBaseline(justName.replace(/\.tsx?$/, ".oldTranspile.js"), () => {
return oldTranspileResult;
});
});
}
});
}
transpilesCorrectly("Generates no diagnostics with valid inputs", `var x = 0;`, {
options: { compilerOptions: { module: ModuleKind.CommonJS } }
});
transpilesCorrectly("Generates no diagnostics for missing file references", `/// <reference path="file2.ts" />
var x = 0;`, {
options: { compilerOptions: { module: ModuleKind.CommonJS } }
});
transpilesCorrectly("Generates no diagnostics for missing module imports", `import {a} from "module2";`, {
options: { compilerOptions: { module: ModuleKind.CommonJS } }
});
transpilesCorrectly("Generates expected syntactic diagnostics", `a b`, {
options: { compilerOptions: { module: ModuleKind.CommonJS } }
});
transpilesCorrectly("Does not generate semantic diagnostics", `var x: string = 0;`, {
options: { compilerOptions: { module: ModuleKind.CommonJS } }
});
transpilesCorrectly("Generates module output", `var x = 0;`, {
options: { compilerOptions: { module: ModuleKind.AMD } }
});
transpilesCorrectly("Uses correct newLine character", `var x = 0;`, {
options: { compilerOptions: { module: ModuleKind.CommonJS, newLine: NewLineKind.LineFeed } }
});
transpilesCorrectly("Sets module name", "var x = 1;", {
options: { compilerOptions: { module: ModuleKind.System, newLine: NewLineKind.LineFeed }, moduleName: "NamedModule" }
});
transpilesCorrectly("No extra errors for file without extension", `"use strict";\r\nvar x = 0;`, {
options: { compilerOptions: { module: ModuleKind.CommonJS }, fileName: "file" }
});
transpilesCorrectly("Rename dependencies - System",
`import {foo} from "SomeName";\n` +
`declare function use(a: any);\n` +
`use(foo);`, {
options: { compilerOptions: { module: ModuleKind.System, newLine: NewLineKind.LineFeed }, renamedDependencies: { "SomeName": "SomeOtherName" } }
});
transpilesCorrectly("Rename dependencies - AMD",
`import {foo} from "SomeName";\n` +
`declare function use(a: any);\n` +
`use(foo);`, {
options: { compilerOptions: { module: ModuleKind.AMD, newLine: NewLineKind.LineFeed }, renamedDependencies: { "SomeName": "SomeOtherName" } }
});
transpilesCorrectly("Rename dependencies - UMD",
`import {foo} from "SomeName";\n` +
`declare function use(a: any);\n` +
`use(foo);`, {
options: { compilerOptions: { module: ModuleKind.UMD, newLine: NewLineKind.LineFeed }, renamedDependencies: { "SomeName": "SomeOtherName" } }
});
transpilesCorrectly("Transpile with emit decorators and emit metadata",
`import {db} from './db';\n` +
`function someDecorator(target) {\n` +
` return target;\n` +
`} \n` +
`@someDecorator\n` +
`class MyClass {\n` +
` db: db;\n` +
` constructor(db: db) {\n` +
` this.db = db;\n` +
` this.db.doSomething(); \n` +
` }\n` +
`}\n` +
`export {MyClass}; \n`, {
options: {
compilerOptions: {
module: ModuleKind.CommonJS,
newLine: NewLineKind.LineFeed,
noEmitHelpers: true,
emitDecoratorMetadata: true,
experimentalDecorators: true,
target: ScriptTarget.ES5,
}
}
});
transpilesCorrectly("Supports backslashes in file name", "var x", {
options: { fileName: "a\\b.ts" }
});
transpilesCorrectly("transpile file as 'tsx' if 'jsx' is specified", `var x = <div/>`, {
options: { compilerOptions: { jsx: JsxEmit.React, newLine: NewLineKind.LineFeed } }
});
transpilesCorrectly("transpile .js files", "const a = 10;", {
options: { compilerOptions: { newLine: NewLineKind.LineFeed, module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports urls in file name", "var x", {
options: { fileName: "http://somewhere/directory//directory2/file.ts" }
});
transpilesCorrectly("Accepts string as enum values for compile-options", "export const x = 0", {
options: {
compilerOptions: {
module: <ModuleKind><any>"es6",
// Capitalization and spaces ignored
target: <ScriptTarget><any>" Es6 "
}
}
});
transpilesCorrectly("Report an error when compiler-options module-kind is out-of-range", "", {
options: { compilerOptions: { module: <ModuleKind><any>123 } }
});
transpilesCorrectly("Report an error when compiler-options target-script is out-of-range", "", {
options: { compilerOptions: { module: <ModuleKind><any>123 } }
});
transpilesCorrectly("Support options with lib values", "const a = 10;", {
options: { compilerOptions: { lib: ["es6", "dom"], module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Support options with types values", "const a = 10;", {
options: { compilerOptions: { types: ["jquery", "typescript"], module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'allowJs'", "x;", {
options: { compilerOptions: { allowJs: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'allowSyntheticDefaultImports'", "x;", {
options: { compilerOptions: { allowSyntheticDefaultImports: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'allowUnreachableCode'", "x;", {
options: { compilerOptions: { allowUnreachableCode: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'allowUnusedLabels'", "x;", {
options: { compilerOptions: { allowUnusedLabels: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'alwaysStrict'", "x;", {
options: { compilerOptions: { alwaysStrict: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'baseUrl'", "x;", {
options: { compilerOptions: { baseUrl: "./folder/baseUrl" }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'charset'", "x;", {
options: { compilerOptions: { charset: "en-us" }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'declaration'", "x;", {
options: { compilerOptions: { declaration: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'declarationDir'", "x;", {
options: { compilerOptions: { declarationDir: "out/declarations" }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'emitBOM'", "x;", {
options: { compilerOptions: { emitBOM: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'emitDecoratorMetadata'", "x;", {
options: { compilerOptions: { emitDecoratorMetadata: true, experimentalDecorators: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'experimentalDecorators'", "x;", {
options: { compilerOptions: { experimentalDecorators: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'forceConsistentCasingInFileNames'", "x;", {
options: { compilerOptions: { forceConsistentCasingInFileNames: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'isolatedModules'", "x;", {
options: { compilerOptions: { isolatedModules: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'jsx'", "x;", {
options: { compilerOptions: { jsx: 1 }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'lib'", "x;", {
options: { compilerOptions: { lib: ["es2015", "dom"] }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'locale'", "x;", {
options: { compilerOptions: { locale: "en-us" }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'module'", "x;", {
options: { compilerOptions: { module: ModuleKind.CommonJS }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'moduleResolution'", "x;", {
options: { compilerOptions: { moduleResolution: ModuleResolutionKind.NodeJs }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'newLine'", "x;", {
options: { compilerOptions: { newLine: NewLineKind.CarriageReturnLineFeed }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'noEmit'", "x;", {
options: { compilerOptions: { noEmit: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'noEmitHelpers'", "x;", {
options: { compilerOptions: { noEmitHelpers: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'noEmitOnError'", "x;", {
options: { compilerOptions: { noEmitOnError: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'noErrorTruncation'", "x;", {
options: { compilerOptions: { noErrorTruncation: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'noFallthroughCasesInSwitch'", "x;", {
options: { compilerOptions: { noFallthroughCasesInSwitch: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'noImplicitAny'", "x;", {
options: { compilerOptions: { noImplicitAny: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'noImplicitReturns'", "x;", {
options: { compilerOptions: { noImplicitReturns: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'noImplicitThis'", "x;", {
options: { compilerOptions: { noImplicitThis: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'noImplicitUseStrict'", "x;", {
options: { compilerOptions: { noImplicitUseStrict: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'noLib'", "x;", {
options: { compilerOptions: { noLib: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'noResolve'", "x;", {
options: { compilerOptions: { noResolve: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'out'", "x;", {
options: { compilerOptions: { out: "./out" }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'outDir'", "x;", {
options: { compilerOptions: { outDir: "./outDir" }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'outFile'", "x;", {
options: { compilerOptions: { outFile: "./outFile" }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'paths'", "x;", {
options: { compilerOptions: { paths: { "*": ["./generated*"] } }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'preserveConstEnums'", "x;", {
options: { compilerOptions: { preserveConstEnums: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'reactNamespace'", "x;", {
options: { compilerOptions: { reactNamespace: "react" }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'jsxFactory'", "x;", {
options: { compilerOptions: { jsxFactory: "createElement" }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'removeComments'", "x;", {
options: { compilerOptions: { removeComments: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'rootDir'", "x;", {
options: { compilerOptions: { rootDir: "./rootDir" }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'rootDirs'", "x;", {
options: { compilerOptions: { rootDirs: ["./a", "./b"] }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'skipLibCheck'", "x;", {
options: { compilerOptions: { skipLibCheck: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'skipDefaultLibCheck'", "x;", {
options: { compilerOptions: { skipDefaultLibCheck: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'strictNullChecks'", "x;", {
options: { compilerOptions: { strictNullChecks: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'stripInternal'", "x;", {
options: { compilerOptions: { stripInternal: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'suppressExcessPropertyErrors'", "x;", {
options: { compilerOptions: { suppressExcessPropertyErrors: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'suppressImplicitAnyIndexErrors'", "x;", {
options: { compilerOptions: { suppressImplicitAnyIndexErrors: true }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'target'", "x;", {
options: { compilerOptions: { target: 2 }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'types'", "x;", {
options: { compilerOptions: { types: ["jquery", "jasmine"] }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Supports setting 'typeRoots'", "x;", {
options: { compilerOptions: { typeRoots: ["./folder"] }, fileName: "input.js", reportDiagnostics: true }
});
transpilesCorrectly("Correctly serialize metadata when transpile with CommonJS option",
`import * as ng from "angular2/core";` +
`declare function foo(...args: any[]);` +
`@foo` +
`export class MyClass1 {` +
` constructor(private _elementRef: ng.ElementRef){}` +
`}`, {
options: {
compilerOptions: {
target: ScriptTarget.ES5,
module: ModuleKind.CommonJS,
moduleResolution: ModuleResolutionKind.NodeJs,
emitDecoratorMetadata: true,
experimentalDecorators: true,
isolatedModules: true,
}
}
});
transpilesCorrectly("Correctly serialize metadata when transpile with System option",
`import * as ng from "angular2/core";` +
`declare function foo(...args: any[]);` +
`@foo` +
`export class MyClass1 {` +
` constructor(private _elementRef: ng.ElementRef){}` +
`}`, {
options: {
compilerOptions: {
target: ScriptTarget.ES5,
module: ModuleKind.System,
moduleResolution: ModuleResolutionKind.NodeJs,
emitDecoratorMetadata: true,
experimentalDecorators: true,
isolatedModules: true,
}
}
});
});
}
+273
View File
@@ -0,0 +1,273 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\..\compiler\commandLineParser.ts" />
namespace ts {
describe("parseConfigFileTextToJson", () => {
function assertParseResult(jsonText: string, expectedConfigObject: { config?: any; error?: Diagnostic }) {
const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText);
assert.equal(JSON.stringify(parsed), JSON.stringify(expectedConfigObject));
}
function assertParseError(jsonText: string) {
const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText);
assert.isTrue(undefined === parsed.config);
assert.isTrue(undefined !== parsed.error);
}
function assertParseErrorWithExcludesKeyword(jsonText: string) {
const parsed = ts.parseConfigFileTextToJson("/apath/tsconfig.json", jsonText);
const parsedCommand = ts.parseJsonConfigFileContent(parsed.config, ts.sys, "tests/cases/unittests");
assert.isTrue(parsedCommand.errors && parsedCommand.errors.length === 1 &&
parsedCommand.errors[0].code === ts.Diagnostics.Unknown_option_excludes_Did_you_mean_exclude.code);
}
function assertParseFileList(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedFileList: string[]) {
const json = JSON.parse(jsonText);
const host: ParseConfigHost = new Utils.MockParseConfigHost(basePath, true, allFileList);
const parsed = ts.parseJsonConfigFileContent(json, host, basePath, /*existingOptions*/ undefined, configFileName);
assert.isTrue(arrayIsEqualTo(parsed.fileNames.sort(), expectedFileList.sort()));
}
function assertParseFileDiagnostics(jsonText: string, configFileName: string, basePath: string, allFileList: string[], expectedDiagnosticCode: number) {
const json = JSON.parse(jsonText);
const host: ParseConfigHost = new Utils.MockParseConfigHost(basePath, true, allFileList);
const parsed = ts.parseJsonConfigFileContent(json, host, basePath, /*existingOptions*/ undefined, configFileName);
assert.isTrue(parsed.errors.length >= 0);
assert.isTrue(parsed.errors.filter(e => e.code === expectedDiagnosticCode).length > 0, `Expected error code ${expectedDiagnosticCode} to be in ${JSON.stringify(parsed.errors)}`);
}
it("returns empty config for file with only whitespaces", () => {
assertParseResult("", { config : {} });
assertParseResult(" ", { config : {} });
});
it("returns empty config for file with comments only", () => {
assertParseResult("// Comment", { config: {} });
assertParseResult("/* Comment*/", { config: {} });
});
it("returns empty config when config is empty object", () => {
assertParseResult("{}", { config: {} });
});
it("returns config object without comments", () => {
assertParseResult(
`{ // Excluded files
"exclude": [
// Exclude d.ts
"file.d.ts"
]
}`, { config: { exclude: ["file.d.ts"] } });
assertParseResult(
`{
/* Excluded
Files
*/
"exclude": [
/* multiline comments can be in the middle of a line */"file.d.ts"
]
}`, { config: { exclude: ["file.d.ts"] } });
});
it("keeps string content untouched", () => {
assertParseResult(
`{
"exclude": [
"xx//file.d.ts"
]
}`, { config: { exclude: ["xx//file.d.ts"] } });
assertParseResult(
`{
"exclude": [
"xx/*file.d.ts*/"
]
}`, { config: { exclude: ["xx/*file.d.ts*/"] } });
});
it("handles escaped characters in strings correctly", () => {
assertParseResult(
`{
"exclude": [
"xx\\"//files"
]
}`, { config: { exclude: ["xx\"//files"] } });
assertParseResult(
`{
"exclude": [
"xx\\\\" // end of line comment
]
}`, { config: { exclude: ["xx\\"] } });
});
it("returns object with error when json is invalid", () => {
assertParseError("invalid");
});
it("returns object when users correctly specify library", () => {
assertParseResult(
`{
"compilerOptions": {
"lib": ["es5"]
}
}`, {
config: { compilerOptions: { lib: ["es5"] } }
});
assertParseResult(
`{
"compilerOptions": {
"lib": ["es5", "es6"]
}
}`, {
config: { compilerOptions: { lib: ["es5", "es6"] } }
});
});
it("returns error when tsconfig have excludes", () => {
assertParseErrorWithExcludesKeyword(
`{
"compilerOptions": {
"lib": ["es5"]
},
"excludes": [
"foge.ts"
]
}`);
});
it("ignore dotted files and folders", () => {
assertParseFileList(
`{}`,
"tsconfig.json",
"/apath",
["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"],
["/apath/test.ts"]
);
});
it("allow dotted files and folders when explicitly requested", () => {
assertParseFileList(
`{
"files": ["/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"]
}`,
"tsconfig.json",
"/apath",
["/apath/test.ts", "/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"],
["/apath/.git/a.ts", "/apath/.b.ts", "/apath/..c.ts"]
);
});
it("exclude outDir unless overridden", () => {
const tsconfigWithoutExclude =
`{
"compilerOptions": {
"outDir": "bin"
}
}`;
const tsconfigWithExclude =
`{
"compilerOptions": {
"outDir": "bin"
},
"exclude": [ "obj" ]
}`;
const rootDir = "/";
const allFiles = ["/bin/a.ts", "/b.ts"];
const expectedFiles = ["/b.ts"];
assertParseFileList(tsconfigWithoutExclude, "tsconfig.json", rootDir, allFiles, expectedFiles);
assertParseFileList(tsconfigWithExclude, "tsconfig.json", rootDir, allFiles, allFiles);
});
it("implicitly exclude common package folders", () => {
assertParseFileList(
`{}`,
"tsconfig.json",
"/",
["/node_modules/a.ts", "/bower_components/b.ts", "/jspm_packages/c.ts", "/d.ts", "/folder/e.ts"],
["/d.ts", "/folder/e.ts"]
);
});
it("parse and re-emit tsconfig.json file with diagnostics", () => {
const content = `{
"compilerOptions": {
"allowJs": true
// Some comments
"outDir": "bin"
}
"files": ["file1.ts"]
}`;
const { configJsonObject, diagnostics } = sanitizeConfigFile("config.json", content);
const expectedResult = {
compilerOptions: {
allowJs: true,
outDir: "bin"
},
files: ["file1.ts"]
};
assert.isTrue(diagnostics.length === 2);
assert.equal(JSON.stringify(configJsonObject), JSON.stringify(expectedResult));
});
it("generates errors for empty files list", () => {
const content = `{
"files": []
}`;
assertParseFileDiagnostics(content,
"/apath/tsconfig.json",
"tests/cases/unittests",
["/apath/a.ts"],
Diagnostics.The_files_list_in_config_file_0_is_empty.code);
});
it("generates errors for directory with no .ts files", () => {
const content = `{
}`;
assertParseFileDiagnostics(content,
"/apath/tsconfig.json",
"tests/cases/unittests",
["/apath/a.js"],
Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code);
});
it("generates errors for empty directory", () => {
const content = `{
"compilerOptions": {
"allowJs": true
}
}`;
assertParseFileDiagnostics(content,
"/apath/tsconfig.json",
"tests/cases/unittests",
[],
Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code);
});
it("generates errors for empty include", () => {
const content = `{
"include": []
}`;
assertParseFileDiagnostics(content,
"/apath/tsconfig.json",
"tests/cases/unittests",
["/apath/a.ts"],
Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code);
});
it("generates errors for includes with outDir", () => {
const content = `{
"compilerOptions": {
"outDir": "./"
},
"include": ["**/*"]
}`;
assertParseFileDiagnostics(content,
"/apath/tsconfig.json",
"tests/cases/unittests",
["/apath/a.ts"],
Diagnostics.No_inputs_were_found_in_config_file_0_Specified_include_paths_were_1_and_exclude_paths_were_2.code);
});
});
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+319
View File
@@ -0,0 +1,319 @@
/// <reference path="..\harness.ts" />
/// <reference path="..\..\server\editorServices.ts" />
namespace ts {
function editFlat(position: number, deletedLength: number, newText: string, source: string) {
return source.substring(0, position) + newText + source.substring(position + deletedLength, source.length);
}
function lineColToPosition(lineIndex: server.LineIndex, line: number, col: number) {
const lineInfo = lineIndex.lineNumberToInfo(line);
return (lineInfo.offset + col - 1);
}
function validateEdit(lineIndex: server.LineIndex, sourceText: string, position: number, deleteLength: number, insertString: string): void {
const checkText = editFlat(position, deleteLength, insertString, sourceText);
const snapshot = lineIndex.edit(position, deleteLength, insertString);
const editedText = snapshot.getText(0, snapshot.getLength());
assert.equal(editedText, checkText);
}
describe(`VersionCache TS code`, () => {
let validateEditAtLineCharIndex: (line: number, char: number, deleteLength: number, insertString: string) => void;
before(() => {
const testContent = `/// <reference path="z.ts" />
var x = 10;
var y = { zebra: 12, giraffe: "ell" };
z.a;
class Point {
x: number;
}
k=y;
var p:Point=new Point();
var q:Point=<Point>p;`;
const { lines } = server.LineIndex.linesFromText(testContent);
assert.isTrue(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line");
const lineIndex = new server.LineIndex();
lineIndex.load(lines);
validateEditAtLineCharIndex = (line: number, char: number, deleteLength: number, insertString: string) => {
const position = lineColToPosition(lineIndex, line, char);
validateEdit(lineIndex, testContent, position, deleteLength, insertString);
};
});
after(() => {
validateEditAtLineCharIndex = undefined;
});
it(`change 9 1 0 1 {"y"}`, () => {
validateEditAtLineCharIndex(9, 1, 0, "y");
});
it(`change 9 2 0 1 {"."}`, () => {
validateEditAtLineCharIndex(9, 2, 0, ".");
});
it(`change 9 3 0 1 {"\\n"}`, () => {
validateEditAtLineCharIndex(9, 3, 0, "\n");
});
it(`change 10 1 0 10 {"\\n\\n\\n\\n\\n\\n\\n\\n\\n\\n"}`, () => {
validateEditAtLineCharIndex(10, 1, 0, "\n\n\n\n\n\n\n\n\n\n");
});
it(`change 19 1 1 0`, () => {
validateEditAtLineCharIndex(19, 1, 1, "");
});
it(`change 18 1 1 0`, () => {
validateEditAtLineCharIndex(18, 1, 1, "");
});
});
describe(`VersionCache simple text`, () => {
let validateEditAtPosition: (position: number, deleteLength: number, insertString: string) => void;
let testContent: string;
let lines: string[];
let lineMap: number[];
before(() => {
testContent = `in this story:
the lazy brown fox
jumped over the cow
that ate the grass
that was purple at the tips
and grew 1cm per day`;
({ lines, lineMap } = server.LineIndex.linesFromText(testContent));
assert.isTrue(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line");
const lineIndex = new server.LineIndex();
lineIndex.load(lines);
validateEditAtPosition = (position: number, deleteLength: number, insertString: string) => {
validateEdit(lineIndex, testContent, position, deleteLength, insertString);
};
});
after(() => {
validateEditAtPosition = undefined;
testContent = undefined;
lines = undefined;
lineMap = undefined;
});
it(`Insert at end of file`, () => {
validateEditAtPosition(testContent.length, 0, "hmmmm...\r\n");
});
it(`Unusual line endings merge`, () => {
validateEditAtPosition(lines[0].length - 1, lines[1].length, "");
});
it(`Delete whole line and nothing but line (last line)`, () => {
validateEditAtPosition(lineMap[lineMap.length - 2], lines[lines.length - 1].length, "");
});
it(`Delete whole line and nothing but line (first line)`, () => {
validateEditAtPosition(0, lines[0].length, "");
});
it(`Delete whole line (first line) and insert with no line breaks`, () => {
validateEditAtPosition(0, lines[0].length, "moo, moo, moo! ");
});
it(`Delete whole line (first line) and insert with multiple line breaks`, () => {
validateEditAtPosition(0, lines[0].length, "moo, \r\nmoo, \r\nmoo! ");
});
it(`Delete multiple lines and nothing but lines (first and second lines)`, () => {
validateEditAtPosition(0, lines[0].length + lines[1].length, "");
});
it(`Delete multiple lines and nothing but lines (second and third lines)`, () => {
validateEditAtPosition(lines[0].length, lines[1].length + lines[2].length, "");
});
it(`Insert multiple line breaks`, () => {
validateEditAtPosition(21, 1, "cr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr...\r\ncr");
});
it(`Insert multiple line breaks`, () => {
validateEditAtPosition(21, 1, "cr...\r\ncr...\r\ncr");
});
it(`Insert multiple line breaks with leading \\n`, () => {
validateEditAtPosition(21, 1, "\ncr...\r\ncr...\r\ncr");
});
it(`Single line no line breaks deleted or inserted, delete 1 char`, () => {
validateEditAtPosition(21, 1, "");
});
it(`Single line no line breaks deleted or inserted, insert 1 char`, () => {
validateEditAtPosition(21, 0, "b");
});
it(`Single line no line breaks deleted or inserted, delete 1, insert 2 chars`, () => {
validateEditAtPosition(21, 1, "cr");
});
it(`Delete across line break (just the line break)`, () => {
validateEditAtPosition(21, 22, "");
});
it(`Delete across line break`, () => {
validateEditAtPosition(21, 32, "");
});
it(`Delete across multiple line breaks and insert no line breaks`, () => {
validateEditAtPosition(21, 42, "");
});
it(`Delete across multiple line breaks and insert text`, () => {
validateEditAtPosition(21, 42, "slithery ");
});
});
describe(`VersionCache stress test`, () => {
let rsa: number[] = [];
let la: number[] = [];
let las: number[] = [];
let elas: number[] = [];
let ersa: number[] = [];
let ela: number[] = [];
const iterationCount = 20;
// const iterationCount = 20000; // uncomment for testing
let lines: string[];
let lineMap: number[];
let lineIndex: server.LineIndex;
let testContent: string;
before(() => {
// Use scanner.ts, decent size, does not change frequently
const testFileName = "src/compiler/scanner.ts";
testContent = Harness.IO.readFile(testFileName);
const totalChars = testContent.length;
assert.isTrue(totalChars > 0, "Failed to read test file.");
({ lines, lineMap } = server.LineIndex.linesFromText(testContent));
assert.isTrue(lines.length > 0, "Failed to initialize test text. Expected text to have at least one line");
lineIndex = new server.LineIndex();
lineIndex.load(lines);
let etotalChars = totalChars;
for (let j = 0; j < 100000; j++) {
rsa[j] = Math.floor(Math.random() * totalChars);
la[j] = Math.floor(Math.random() * (totalChars - rsa[j]));
if (la[j] > 4) {
las[j] = 4;
}
else {
las[j] = la[j];
}
if (j < 4000) {
ersa[j] = Math.floor(Math.random() * etotalChars);
ela[j] = Math.floor(Math.random() * (etotalChars - ersa[j]));
if (ela[j] > 4) {
elas[j] = 4;
}
else {
elas[j] = ela[j];
}
etotalChars += (las[j] - elas[j]);
}
}
});
after(() => {
rsa = undefined;
la = undefined;
las = undefined;
elas = undefined;
ersa = undefined;
ela = undefined;
lines = undefined;
lineMap = undefined;
lineIndex = undefined;
testContent = undefined;
});
it("Range (average length 1/4 file size)", () => {
for (let i = 0; i < iterationCount; i++) {
const s2 = lineIndex.getText(rsa[i], la[i]);
const s1 = testContent.substring(rsa[i], rsa[i] + la[i]);
assert.equal(s1, s2);
}
});
it("Range (average length 4 chars)", () => {
for (let j = 0; j < iterationCount; j++) {
const s2 = lineIndex.getText(rsa[j], las[j]);
const s1 = testContent.substring(rsa[j], rsa[j] + las[j]);
assert.equal(s1, s2);
}
});
it("Edit (average length 4)", () => {
for (let i = 0; i < iterationCount; i++) {
const insertString = testContent.substring(rsa[100000 - i], rsa[100000 - i] + las[100000 - i]);
const snapshot = lineIndex.edit(rsa[i], las[i], insertString);
const checkText = editFlat(rsa[i], las[i], insertString, testContent);
const snapText = snapshot.getText(0, checkText.length);
assert.equal(checkText, snapText);
}
});
it("Edit ScriptVersionCache ", () => {
const svc = server.ScriptVersionCache.fromString(<server.ServerHost>ts.sys, testContent);
let checkText = testContent;
for (let i = 0; i < iterationCount; i++) {
const insertString = testContent.substring(rsa[i], rsa[i] + las[i]);
svc.edit(ersa[i], elas[i], insertString);
checkText = editFlat(ersa[i], elas[i], insertString, checkText);
if (0 === (i % 4)) {
const snap = svc.getSnapshot();
const snapText = snap.getText(0, checkText.length);
assert.equal(checkText, snapText);
}
}
});
it("Edit (average length 1/4th file size)", () => {
for (let i = 0; i < iterationCount; i++) {
const insertString = testContent.substring(rsa[100000 - i], rsa[100000 - i] + la[100000 - i]);
const snapshot = lineIndex.edit(rsa[i], la[i], insertString);
const checkText = editFlat(rsa[i], la[i], insertString, testContent);
const snapText = snapshot.getText(0, checkText.length);
assert.equal(checkText, snapText);
}
});
it("Line/offset from pos", () => {
for (let i = 0; i < iterationCount; i++) {
const lp = lineIndex.charOffsetToLineNumberAndPos(rsa[i]);
const lac = ts.computeLineAndCharacterOfPosition(lineMap, rsa[i]);
assert.equal(lac.line + 1, lp.line, "Line number mismatch " + (lac.line + 1) + " " + lp.line + " " + i);
assert.equal(lac.character, (lp.offset), "Charachter offset mismatch " + lac.character + " " + lp.offset + " " + i);
}
});
it("Start pos from line", () => {
for (let i = 0; i < iterationCount; i++) {
for (let j = 0; j < lines.length; j++) {
const lineInfo = lineIndex.lineNumberToInfo(j + 1);
const lineIndexOffset = lineInfo.offset;
const lineMapOffset = lineMap[j];
assert.equal(lineIndexOffset, lineMapOffset);
}
}
});
});
}
+223
View File
@@ -0,0 +1,223 @@
/// <reference path="harness.ts" />
/// <reference path="..\compiler\commandLineParser.ts"/>
namespace Utils {
export class VirtualFileSystemEntry {
fileSystem: VirtualFileSystem;
name: string;
constructor(fileSystem: VirtualFileSystem, name: string) {
this.fileSystem = fileSystem;
this.name = name;
}
isDirectory(): this is VirtualDirectory { return false; }
isFile(): this is VirtualFile { return false; }
isFileSystem(): this is VirtualFileSystem { return false; }
}
export class VirtualFile extends VirtualFileSystemEntry {
content?: Harness.LanguageService.ScriptInfo;
isFile() { return true; }
}
export abstract class VirtualFileSystemContainer extends VirtualFileSystemEntry {
abstract getFileSystemEntries(): VirtualFileSystemEntry[];
getFileSystemEntry(name: string): VirtualFileSystemEntry {
for (const entry of this.getFileSystemEntries()) {
if (this.fileSystem.sameName(entry.name, name)) {
return entry;
}
}
return undefined;
}
getDirectories(): VirtualDirectory[] {
return <VirtualDirectory[]>ts.filter(this.getFileSystemEntries(), entry => entry.isDirectory());
}
getFiles(): VirtualFile[] {
return <VirtualFile[]>ts.filter(this.getFileSystemEntries(), entry => entry.isFile());
}
getDirectory(name: string): VirtualDirectory {
const entry = this.getFileSystemEntry(name);
return entry.isDirectory() ? <VirtualDirectory>entry : undefined;
}
getFile(name: string): VirtualFile {
const entry = this.getFileSystemEntry(name);
return entry.isFile() ? <VirtualFile>entry : undefined;
}
}
export class VirtualDirectory extends VirtualFileSystemContainer {
private entries: VirtualFileSystemEntry[] = [];
isDirectory() { return true; }
getFileSystemEntries() { return this.entries.slice(); }
addDirectory(name: string): VirtualDirectory {
const entry = this.getFileSystemEntry(name);
if (entry === undefined) {
const directory = new VirtualDirectory(this.fileSystem, name);
this.entries.push(directory);
return directory;
}
else if (entry.isDirectory()) {
return <VirtualDirectory>entry;
}
else {
return undefined;
}
}
addFile(name: string, content?: Harness.LanguageService.ScriptInfo): VirtualFile {
const entry = this.getFileSystemEntry(name);
if (entry === undefined) {
const file = new VirtualFile(this.fileSystem, name);
file.content = content;
this.entries.push(file);
return file;
}
else if (entry.isFile()) {
entry.content = content;
return entry;
}
else {
return undefined;
}
}
}
export class VirtualFileSystem extends VirtualFileSystemContainer {
private root: VirtualDirectory;
currentDirectory: string;
useCaseSensitiveFileNames: boolean;
constructor(currentDirectory: string, useCaseSensitiveFileNames: boolean) {
super(/*fileSystem*/ undefined, "");
this.fileSystem = this;
this.root = new VirtualDirectory(this, "");
this.currentDirectory = currentDirectory;
this.useCaseSensitiveFileNames = useCaseSensitiveFileNames;
}
isFileSystem() { return true; }
getFileSystemEntries() { return this.root.getFileSystemEntries(); }
addDirectory(path: string) {
path = ts.normalizePath(path);
const components = ts.getNormalizedPathComponents(path, this.currentDirectory);
let directory: VirtualDirectory = this.root;
for (const component of components) {
directory = directory.addDirectory(component);
if (directory === undefined) {
break;
}
}
return directory;
}
addFile(path: string, content?: Harness.LanguageService.ScriptInfo) {
const absolutePath = ts.normalizePath(ts.getNormalizedAbsolutePath(path, this.currentDirectory));
const fileName = ts.getBaseFileName(path);
const directoryPath = ts.getDirectoryPath(absolutePath);
const directory = this.addDirectory(directoryPath);
return directory ? directory.addFile(fileName, content) : undefined;
}
fileExists(path: string) {
const entry = this.traversePath(path);
return entry !== undefined && entry.isFile();
}
sameName(a: string, b: string) {
return this.useCaseSensitiveFileNames ? a === b : a.toLowerCase() === b.toLowerCase();
}
traversePath(path: string) {
path = ts.normalizePath(path);
let directory: VirtualDirectory = this.root;
for (const component of ts.getNormalizedPathComponents(path, this.currentDirectory)) {
const entry = directory.getFileSystemEntry(component);
if (entry === undefined) {
return undefined;
}
else if (entry.isDirectory()) {
directory = <VirtualDirectory>entry;
}
else {
return entry;
}
}
return directory;
}
/**
* Reads the directory at the given path and retrieves a list of file names and a list
* of directory names within it. Suitable for use with ts.matchFiles()
* @param path The path to the directory to be read
*/
getAccessibleFileSystemEntries(path: string) {
const entry = this.traversePath(path);
if (entry && entry.isDirectory()) {
const directory = <VirtualDirectory>entry;
return {
files: ts.map(directory.getFiles(), f => f.name),
directories: ts.map(directory.getDirectories(), d => d.name)
};
}
return { files: [], directories: [] };
}
getAllFileEntries() {
const fileEntries: VirtualFile[] = [];
getFilesRecursive(this.root, fileEntries);
return fileEntries;
function getFilesRecursive(dir: VirtualDirectory, result: VirtualFile[]) {
const files = dir.getFiles();
const dirs = dir.getDirectories();
for (const file of files) {
result.push(file);
}
for (const subDir of dirs) {
getFilesRecursive(subDir, result);
}
}
}
}
export class MockParseConfigHost extends VirtualFileSystem implements ts.ParseConfigHost {
constructor(currentDirectory: string, ignoreCase: boolean, files: ts.Map<string> | string[]) {
super(currentDirectory, ignoreCase);
if (files instanceof Array) {
for (const file of files) {
this.addFile(file, new Harness.LanguageService.ScriptInfo(file, undefined, /*isRootFile*/false));
}
}
else {
files.forEach((fileContent, fileName) => {
this.addFile(fileName, new Harness.LanguageService.ScriptInfo(fileName, fileContent, /*isRootFile*/false));
});
}
}
readFile(path: string): string {
const value = this.traversePath(path);
if (value && value.isFile()) {
return value.content.content;
}
}
readDirectory(path: string, extensions: string[], excludes: string[], includes: string[]) {
return ts.matchFiles(path, extensions, excludes, includes, this.useCaseSensitiveFileNames, this.currentDirectory, (path: string) => this.getAccessibleFileSystemEntries(path));
}
}
}
+8
View File
@@ -0,0 +1,8 @@
# Read this!
The files within this directory are used to generate `lib.d.ts` and `lib.es6.d.ts`.
## Generated files
Any files ending in `.generated.d.ts` aren't mean to be edited by hand.
If you need to make changes to such files, make a change to the input files for our [library generator](https://github.com/Microsoft/TSJS-lib-generator).
-1184
View File
File diff suppressed because it is too large Load Diff
-11
View File
@@ -1,11 +0,0 @@
interface DOMTokenList {
[Symbol.iterator](): IterableIterator<string>;
}
interface NodeList {
[Symbol.iterator](): IterableIterator<Node>
}
interface NodeListOf<TNode extends Node> {
[Symbol.iterator](): IterableIterator<TNode>
}
+7830 -5747
View File
File diff suppressed because it is too large Load Diff
+107
View File
@@ -0,0 +1,107 @@
/// <reference path="lib.dom.d.ts" />
interface DOMTokenList {
[Symbol.iterator](): IterableIterator<string>;
}
interface FormData {
/**
* Returns an array of key, value pairs for every entry in the list
*/
entries(): IterableIterator<[string, string | File]>;
/**
* Returns a list of keys in the list
*/
keys(): IterableIterator<string>;
/**
* Returns a list of values in the list
*/
values(): IterableIterator<string | File>;
[Symbol.iterator](): IterableIterator<string | File>;
}
interface Headers {
[Symbol.iterator](): IterableIterator<[string, string]>;
/**
* Returns an iterator allowing to go through all key/value pairs contained in this object.
*/
entries(): IterableIterator<[string, string]>;
/**
* Returns an iterator allowing to go through all keys f the key/value pairs contained in this object.
*/
keys(): IterableIterator<string>;
/**
* Returns an iterator allowing to go through all values of the key/value pairs contained in this object.
*/
values(): IterableIterator<string>;
}
interface NodeList {
/**
* Returns an array of key, value pairs for every entry in the list
*/
entries(): IterableIterator<[number, Node]>;
/**
* Performs the specified action for each node in an list.
* @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the list.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
forEach(callbackfn: (value: Node, index: number, listObj: NodeList) => void, thisArg?: any): void;
/**
* Returns an list of keys in the list
*/
keys(): IterableIterator<number>;
/**
* Returns an list of values in the list
*/
values(): IterableIterator<Node>;
[Symbol.iterator](): IterableIterator<Node>;
}
interface NodeListOf<TNode extends Node> {
/**
* Returns an array of key, value pairs for every entry in the list
*/
entries(): IterableIterator<[number, TNode]>;
/**
* Performs the specified action for each node in an list.
* @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the list.
* @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value.
*/
forEach(callbackfn: (value: TNode, index: number, listObj: NodeListOf<TNode>) => void, thisArg?: any): void;
/**
* Returns an list of keys in the list
*/
keys(): IterableIterator<number>;
/**
* Returns an list of values in the list
*/
values(): IterableIterator<TNode>;
[Symbol.iterator](): IterableIterator<TNode>;
}
interface URLSearchParams {
/**
* Returns an array of key, value pairs for every entry in the search params
*/
entries(): IterableIterator<[string, string]>;
/**
* Returns a list of keys in the search params
*/
keys(): IterableIterator<string>;
/**
* Returns a list of values in the search params
*/
values(): IterableIterator<string>;
/**
* iterate over key/value pairs
*/
[Symbol.iterator](): IterableIterator<[string, string]>;
}
+72
View File
@@ -0,0 +1,72 @@
interface Map<K, V> {
clear(): void;
delete(key: K): boolean;
forEach(callbackfn: (value: V, key: K, map: Map<K, V>) => void, thisArg?: any): void;
get(key: K): V | undefined;
has(key: K): boolean;
set(key: K, value: V): this;
readonly size: number;
}
interface MapConstructor {
new (): Map<any, any>;
new <K, V>(entries?: [K, V][]): Map<K, V>;
readonly prototype: Map<any, any>;
}
declare var Map: MapConstructor;
interface ReadonlyMap<K, V> {
forEach(callbackfn: (value: V, key: K, map: ReadonlyMap<K, V>) => void, thisArg?: any): void;
get(key: K): V | undefined;
has(key: K): boolean;
readonly size: number;
}
interface WeakMap<K extends object, V> {
delete(key: K): boolean;
get(key: K): V | undefined;
has(key: K): boolean;
set(key: K, value: V): this;
}
interface WeakMapConstructor {
new (): WeakMap<object, any>;
new <K extends object, V>(entries?: [K, V][]): WeakMap<K, V>;
readonly prototype: WeakMap<object, any>;
}
declare var WeakMap: WeakMapConstructor;
interface Set<T> {
add(value: T): this;
clear(): void;
delete(value: T): boolean;
forEach(callbackfn: (value: T, value2: T, set: Set<T>) => void, thisArg?: any): void;
has(value: T): boolean;
readonly size: number;
}
interface SetConstructor {
new (): Set<any>;
new <T>(values?: T[]): Set<T>;
readonly prototype: Set<any>;
}
declare var Set: SetConstructor;
interface ReadonlySet<T> {
forEach(callbackfn: (value: T, value2: T, set: ReadonlySet<T>) => void, thisArg?: any): void;
has(value: T): boolean;
readonly size: number;
}
interface WeakSet<T> {
add(value: T): this;
delete(value: T): boolean;
has(value: T): boolean;
}
interface WeakSetConstructor {
new (): WeakSet<object>;
new <T extends object>(values?: T[]): WeakSet<T>;
readonly prototype: WeakSet<object>;
}
declare var WeakSet: WeakSetConstructor;
+534
View File
@@ -0,0 +1,534 @@
declare type PropertyKey = string | number | symbol;
interface Array<T> {
/**
* Returns the value of the first element in the array where predicate is true, and undefined
* otherwise.
* @param predicate find calls predicate once for each element of the array, in ascending
* order, until it finds one where predicate returns true. If such an element is found, find
* immediately returns that element value. Otherwise, find returns undefined.
* @param thisArg If provided, it will be used as the this value for each invocation of
* predicate. If it is not provided, undefined is used instead.
*/
find(predicate: (this: void, value: T, index: number, obj: Array<T>) => boolean): T | undefined;
find(predicate: (this: void, value: T, index: number, obj: Array<T>) => boolean, thisArg: undefined): T | undefined;
find<Z>(predicate: (this: Z, value: T, index: number, obj: Array<T>) => boolean, thisArg: Z): T | undefined;
/**
* Returns the index of the first element in the array where predicate is true, and -1
* otherwise.
* @param predicate find calls predicate once for each element of the array, in ascending
* order, until it finds one where predicate returns true. If such an element is found,
* findIndex immediately returns that element index. Otherwise, findIndex returns -1.
* @param thisArg If provided, it will be used as the this value for each invocation of
* predicate. If it is not provided, undefined is used instead.
*/
findIndex(predicate: (this: void, value: T, index: number, obj: Array<T>) => boolean): number;
findIndex(predicate: (this: void, value: T, index: number, obj: Array<T>) => boolean, thisArg: undefined): number;
findIndex<Z>(predicate: (this: Z, value: T, index: number, obj: Array<T>) => boolean, thisArg: Z): number;
/**
* Returns the this object after filling the section identified by start and end with value
* @param value value to fill array section with
* @param start index to start filling the array at. If start is negative, it is treated as
* length+start where length is the length of the array.
* @param end index to stop filling the array at. If end is negative, it is treated as
* length+end.
*/
fill(value: T, start?: number, end?: number): this;
/**
* Returns the this object after copying a section of the array identified by start and end
* to the same array starting at position target
* @param target If target is negative, it is treated as length+target where length is the
* length of the array.
* @param start If start is negative, it is treated as length+start. If end is negative, it
* is treated as length+end.
* @param end If not specified, length of the this object is used as its default value.
*/
copyWithin(target: number, start: number, end?: number): this;
}
interface ArrayConstructor {
/**
* Creates an array from an array-like object.
* @param arrayLike An array-like object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from<T, U>(arrayLike: ArrayLike<T>, mapfn: (this: void, v: T, k: number) => U): Array<U>;
from<T, U>(arrayLike: ArrayLike<T>, mapfn: (this: void, v: T, k: number) => U, thisArg: undefined): Array<U>;
from<Z, T, U>(arrayLike: ArrayLike<T>, mapfn: (this: Z, v: T, k: number) => U, thisArg: Z): Array<U>;
/**
* Creates an array from an array-like object.
* @param arrayLike An array-like object to convert to an array.
*/
from<T>(arrayLike: ArrayLike<T>): Array<T>;
/**
* Returns a new array from a set of elements.
* @param items A set of elements to include in the new array object.
*/
of<T>(...items: T[]): Array<T>;
}
interface DateConstructor {
new (value: Date): Date;
}
interface Function {
/**
* Returns the name of the function. Function names are read-only and can not be changed.
*/
readonly name: string;
}
interface Math {
/**
* Returns the number of leading zero bits in the 32-bit binary representation of a number.
* @param x A numeric expression.
*/
clz32(x: number): number;
/**
* Returns the result of 32-bit multiplication of two numbers.
* @param x First number
* @param y Second number
*/
imul(x: number, y: number): number;
/**
* Returns the sign of the x, indicating whether x is positive, negative or zero.
* @param x The numeric expression to test
*/
sign(x: number): number;
/**
* Returns the base 10 logarithm of a number.
* @param x A numeric expression.
*/
log10(x: number): number;
/**
* Returns the base 2 logarithm of a number.
* @param x A numeric expression.
*/
log2(x: number): number;
/**
* Returns the natural logarithm of 1 + x.
* @param x A numeric expression.
*/
log1p(x: number): number;
/**
* Returns the result of (e^x - 1) of x (e raised to the power of x, where e is the base of
* the natural logarithms).
* @param x A numeric expression.
*/
expm1(x: number): number;
/**
* Returns the hyperbolic cosine of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
cosh(x: number): number;
/**
* Returns the hyperbolic sine of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
sinh(x: number): number;
/**
* Returns the hyperbolic tangent of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
tanh(x: number): number;
/**
* Returns the inverse hyperbolic cosine of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
acosh(x: number): number;
/**
* Returns the inverse hyperbolic sine of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
asinh(x: number): number;
/**
* Returns the inverse hyperbolic tangent of a number.
* @param x A numeric expression that contains an angle measured in radians.
*/
atanh(x: number): number;
/**
* Returns the square root of the sum of squares of its arguments.
* @param values Values to compute the square root for.
* If no arguments are passed, the result is +0.
* If there is only one argument, the result is the absolute value.
* If any argument is +Infinity or -Infinity, the result is +Infinity.
* If any argument is NaN, the result is NaN.
* If all arguments are either +0 or 0, the result is +0.
*/
hypot(...values: number[] ): number;
/**
* Returns the integral part of the a numeric expression, x, removing any fractional digits.
* If x is already an integer, the result is x.
* @param x A numeric expression.
*/
trunc(x: number): number;
/**
* Returns the nearest single precision float representation of a number.
* @param x A numeric expression.
*/
fround(x: number): number;
/**
* Returns an implementation-dependent approximation to the cube root of number.
* @param x A numeric expression.
*/
cbrt(x: number): number;
}
interface NumberConstructor {
/**
* The value of Number.EPSILON is the difference between 1 and the smallest value greater than 1
* that is representable as a Number value, which is approximately:
* 2.2204460492503130808472633361816 x 1016.
*/
readonly EPSILON: number;
/**
* Returns true if passed value is finite.
* Unlike the global isFinite, Number.isFinite doesn't forcibly convert the parameter to a
* number. Only finite values of the type number, result in true.
* @param number A numeric value.
*/
isFinite(number: number): boolean;
/**
* Returns true if the value passed is an integer, false otherwise.
* @param number A numeric value.
*/
isInteger(number: number): boolean;
/**
* Returns a Boolean value that indicates whether a value is the reserved value NaN (not a
* number). Unlike the global isNaN(), Number.isNaN() doesn't forcefully convert the parameter
* to a number. Only values of the type number, that are also NaN, result in true.
* @param number A numeric value.
*/
isNaN(number: number): boolean;
/**
* Returns true if the value passed is a safe integer.
* @param number A numeric value.
*/
isSafeInteger(number: number): boolean;
/**
* The value of the largest integer n such that n and n + 1 are both exactly representable as
* a Number value.
* The value of Number.MAX_SAFE_INTEGER is 9007199254740991 2^53 1.
*/
readonly MAX_SAFE_INTEGER: number;
/**
* The value of the smallest integer n such that n and n 1 are both exactly representable as
* a Number value.
* The value of Number.MIN_SAFE_INTEGER is 9007199254740991 ((2^53 1)).
*/
readonly MIN_SAFE_INTEGER: number;
/**
* Converts a string to a floating-point number.
* @param string A string that contains a floating-point number.
*/
parseFloat(string: string): number;
/**
* Converts A string to an integer.
* @param s A string to convert into a number.
* @param radix A value between 2 and 36 that specifies the base of the number in numString.
* If this argument is not supplied, strings with a prefix of '0x' are considered hexadecimal.
* All other strings are considered decimal.
*/
parseInt(string: string, radix?: number): number;
}
interface Object {
/**
* Determines whether an object has a property with the specified name.
* @param v A property name.
*/
hasOwnProperty(v: PropertyKey): boolean;
/**
* Determines whether a specified property is enumerable.
* @param v A property name.
*/
propertyIsEnumerable(v: PropertyKey): boolean;
}
interface ObjectConstructor {
/**
* Copy the values of all of the enumerable own properties from one or more source objects to a
* target object. Returns the target object.
* @param target The target object to copy to.
* @param source The source object from which to copy properties.
*/
assign<T, U>(target: T, source: U): T & U;
/**
* Copy the values of all of the enumerable own properties from one or more source objects to a
* target object. Returns the target object.
* @param target The target object to copy to.
* @param source1 The first source object from which to copy properties.
* @param source2 The second source object from which to copy properties.
*/
assign<T, U, V>(target: T, source1: U, source2: V): T & U & V;
/**
* Copy the values of all of the enumerable own properties from one or more source objects to a
* target object. Returns the target object.
* @param target The target object to copy to.
* @param source1 The first source object from which to copy properties.
* @param source2 The second source object from which to copy properties.
* @param source3 The third source object from which to copy properties.
*/
assign<T, U, V, W>(target: T, source1: U, source2: V, source3: W): T & U & V & W;
/**
* Copy the values of all of the enumerable own properties from one or more source objects to a
* target object. Returns the target object.
* @param target The target object to copy to.
* @param sources One or more source objects from which to copy properties
*/
assign(target: object, ...sources: any[]): any;
/**
* Returns an array of all symbol properties found directly on object o.
* @param o Object to retrieve the symbols from.
*/
getOwnPropertySymbols(o: any): symbol[];
/**
* Returns true if the values are the same value, false otherwise.
* @param value1 The first value.
* @param value2 The second value.
*/
is(value1: any, value2: any): boolean;
/**
* Sets the prototype of a specified object o to object proto or null. Returns the object o.
* @param o The object to change its prototype.
* @param proto The value of the new prototype or null.
*/
setPrototypeOf(o: any, proto: object | null): any;
/**
* Gets the own property descriptor of the specified object.
* An own property descriptor is one that is defined directly on the object and is not
* inherited from the object's prototype.
* @param o Object that contains the property.
* @param p Name of the property.
*/
getOwnPropertyDescriptor(o: any, propertyKey: PropertyKey): PropertyDescriptor;
/**
* Adds a property to an object, or modifies attributes of an existing property.
* @param o Object on which to add or modify the property. This can be a native JavaScript
* object (that is, a user-defined object or a built in object) or a DOM object.
* @param p The property name.
* @param attributes Descriptor for the property. It can be for a data property or an accessor
* property.
*/
defineProperty(o: any, propertyKey: PropertyKey, attributes: PropertyDescriptor): any;
}
interface ReadonlyArray<T> {
/**
* Returns the value of the first element in the array where predicate is true, and undefined
* otherwise.
* @param predicate find calls predicate once for each element of the array, in ascending
* order, until it finds one where predicate returns true. If such an element is found, find
* immediately returns that element value. Otherwise, find returns undefined.
* @param thisArg If provided, it will be used as the this value for each invocation of
* predicate. If it is not provided, undefined is used instead.
*/
find(predicate: (this: void, value: T, index: number, obj: ReadonlyArray<T>) => boolean): T | undefined;
find(predicate: (this: void, value: T, index: number, obj: ReadonlyArray<T>) => boolean, thisArg: undefined): T | undefined;
find<Z>(predicate: (this: Z, value: T, index: number, obj: ReadonlyArray<T>) => boolean, thisArg: Z): T | undefined;
/**
* Returns the index of the first element in the array where predicate is true, and -1
* otherwise.
* @param predicate find calls predicate once for each element of the array, in ascending
* order, until it finds one where predicate returns true. If such an element is found,
* findIndex immediately returns that element index. Otherwise, findIndex returns -1.
* @param thisArg If provided, it will be used as the this value for each invocation of
* predicate. If it is not provided, undefined is used instead.
*/
findIndex(predicate: (this: void, value: T, index: number, obj: Array<T>) => boolean): number;
findIndex(predicate: (this: void, value: T, index: number, obj: Array<T>) => boolean, thisArg: undefined): number;
findIndex<Z>(predicate: (this: Z, value: T, index: number, obj: Array<T>) => boolean, thisArg: Z): number;
}
interface RegExp {
/**
* Returns a string indicating the flags of the regular expression in question. This field is read-only.
* The characters in this string are sequenced and concatenated in the following order:
*
* - "g" for global
* - "i" for ignoreCase
* - "m" for multiline
* - "u" for unicode
* - "y" for sticky
*
* If no flags are set, the value is the empty string.
*/
readonly flags: string;
/**
* Returns a Boolean value indicating the state of the sticky flag (y) used with a regular
* expression. Default is false. Read-only.
*/
readonly sticky: boolean;
/**
* Returns a Boolean value indicating the state of the Unicode flag (u) used with a regular
* expression. Default is false. Read-only.
*/
readonly unicode: boolean;
}
interface RegExpConstructor {
new (pattern: RegExp, flags?: string): RegExp;
(pattern: RegExp, flags?: string): RegExp;
}
interface String {
/**
* Returns a nonnegative integer Number less than 1114112 (0x110000) that is the code point
* value of the UTF-16 encoded code point starting at the string element at position pos in
* the String resulting from converting this object to a String.
* If there is no element at that position, the result is undefined.
* If a valid UTF-16 surrogate pair does not begin at pos, the result is the code unit at pos.
*/
codePointAt(pos: number): number | undefined;
/**
* Returns true if searchString appears as a substring of the result of converting this
* object to a String, at one or more positions that are
* greater than or equal to position; otherwise, returns false.
* @param searchString search string
* @param position If position is undefined, 0 is assumed, so as to search all of the String.
*/
includes(searchString: string, position?: number): boolean;
/**
* Returns true if the sequence of elements of searchString converted to a String is the
* same as the corresponding elements of this object (converted to a String) starting at
* endPosition length(this). Otherwise returns false.
*/
endsWith(searchString: string, endPosition?: number): boolean;
/**
* Returns the String value result of normalizing the string into the normalization form
* named by form as specified in Unicode Standard Annex #15, Unicode Normalization Forms.
* @param form Applicable values: "NFC", "NFD", "NFKC", or "NFKD", If not specified default
* is "NFC"
*/
normalize(form: "NFC" | "NFD" | "NFKC" | "NFKD"): string;
/**
* Returns the String value result of normalizing the string into the normalization form
* named by form as specified in Unicode Standard Annex #15, Unicode Normalization Forms.
* @param form Applicable values: "NFC", "NFD", "NFKC", or "NFKD", If not specified default
* is "NFC"
*/
normalize(form?: string): string;
/**
* Returns a String value that is made from count copies appended together. If count is 0,
* T is the empty String is returned.
* @param count number of copies to append
*/
repeat(count: number): string;
/**
* Returns true if the sequence of elements of searchString converted to a String is the
* same as the corresponding elements of this object (converted to a String) starting at
* position. Otherwise returns false.
*/
startsWith(searchString: string, position?: number): boolean;
/**
* Returns an <a> HTML anchor element and sets the name attribute to the text value
* @param name
*/
anchor(name: string): string;
/** Returns a <big> HTML element */
big(): string;
/** Returns a <blink> HTML element */
blink(): string;
/** Returns a <b> HTML element */
bold(): string;
/** Returns a <tt> HTML element */
fixed(): string;
/** Returns a <font> HTML element and sets the color attribute value */
fontcolor(color: string): string;
/** Returns a <font> HTML element and sets the size attribute value */
fontsize(size: number): string;
/** Returns a <font> HTML element and sets the size attribute value */
fontsize(size: string): string;
/** Returns an <i> HTML element */
italics(): string;
/** Returns an <a> HTML element and sets the href attribute value */
link(url: string): string;
/** Returns a <small> HTML element */
small(): string;
/** Returns a <strike> HTML element */
strike(): string;
/** Returns a <sub> HTML element */
sub(): string;
/** Returns a <sup> HTML element */
sup(): string;
}
interface StringConstructor {
/**
* Return the String value whose elements are, in order, the elements in the List elements.
* If length is 0, the empty string is returned.
*/
fromCodePoint(...codePoints: number[]): string;
/**
* String.raw is intended for use as a tag function of a Tagged Template String. When called
* as such the first argument will be a well formed template call site object and the rest
* parameter will contain the substitution values.
* @param template A well-formed template string call site representation.
* @param substitutions A set of substitution values.
*/
raw(template: TemplateStringsArray, ...substitutions: any[]): string;
}
+10
View File
@@ -0,0 +1,10 @@
/// <reference path="lib.es2015.core.d.ts" />
/// <reference path="lib.es2015.collection.d.ts" />
/// <reference path="lib.es2015.generator.d.ts" />
/// <reference path="lib.es2015.iterable.d.ts" />
/// <reference path="lib.es2015.promise.d.ts" />
/// <reference path="lib.es2015.proxy.d.ts" />
/// <reference path="lib.es2015.reflect.d.ts" />
/// <reference path="lib.es2015.symbol.d.ts" />
/// <reference path="lib.es2015.symbol.wellknown.d.ts" />
/// <reference path="lib.es5.d.ts" />
+52
View File
@@ -0,0 +1,52 @@
interface Generator extends Iterator<any> { }
interface GeneratorFunction {
/**
* Creates a new Generator object.
* @param args A list of arguments the function accepts.
*/
new (...args: any[]): Generator;
/**
* Creates a new Generator object.
* @param args A list of arguments the function accepts.
*/
(...args: any[]): Generator;
/**
* The length of the arguments.
*/
readonly length: number;
/**
* Returns the name of the function.
*/
readonly name: string;
/**
* A reference to the prototype.
*/
readonly prototype: Generator;
}
interface GeneratorFunctionConstructor {
/**
* Creates a new Generator function.
* @param args A list of arguments the function accepts.
*/
new (...args: string[]): GeneratorFunction;
/**
* Creates a new Generator function.
* @param args A list of arguments the function accepts.
*/
(...args: string[]): GeneratorFunction;
/**
* The length of the arguments.
*/
readonly length: number;
/**
* Returns the name of the function.
*/
readonly name: string;
/**
* A reference to the prototype.
*/
readonly prototype: GeneratorFunction;
}
declare var GeneratorFunction: GeneratorFunctionConstructor;
+547
View File
@@ -0,0 +1,547 @@
/// <reference path="lib.es2015.symbol.d.ts" />
interface SymbolConstructor {
/**
* A method that returns the default iterator for an object. Called by the semantics of the
* for-of statement.
*/
readonly iterator: symbol;
}
interface IteratorResult<T> {
done: boolean;
value: T;
}
interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}
interface Iterable<T> {
[Symbol.iterator](): Iterator<T>;
}
interface IterableIterator<T> extends Iterator<T> {
[Symbol.iterator](): IterableIterator<T>;
}
interface Array<T> {
/** Iterator */
[Symbol.iterator](): IterableIterator<T>;
/**
* Returns an iterable of key, value pairs for every entry in the array
*/
entries(): IterableIterator<[number, T]>;
/**
* Returns an iterable of keys in the array
*/
keys(): IterableIterator<number>;
/**
* Returns an iterable of values in the array
*/
values(): IterableIterator<T>;
}
interface ArrayConstructor {
/**
* Creates an array from an iterable object.
* @param iterable An iterable object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from<T, U>(iterable: Iterable<T>, mapfn: (this: void, v: T, k: number) => U): Array<U>;
from<T, U>(iterable: Iterable<T>, mapfn: (this: void, v: T, k: number) => U, thisArg: undefined): Array<U>;
from<Z, T, U>(iterable: Iterable<T>, mapfn: (this: Z, v: T, k: number) => U, thisArg: Z): Array<U>;
/**
* Creates an array from an iterable object.
* @param iterable An iterable object to convert to an array.
*/
from<T>(iterable: Iterable<T>): Array<T>;
}
interface ReadonlyArray<T> {
/** Iterator of values in the array. */
[Symbol.iterator](): IterableIterator<T>;
/**
* Returns an iterable of key, value pairs for every entry in the array
*/
entries(): IterableIterator<[number, T]>;
/**
* Returns an iterable of keys in the array
*/
keys(): IterableIterator<number>;
/**
* Returns an iterable of values in the array
*/
values(): IterableIterator<T>;
}
interface IArguments {
/** Iterator */
[Symbol.iterator](): IterableIterator<any>;
}
interface Map<K, V> {
/** Returns an iterable of entries in the map. */
[Symbol.iterator](): IterableIterator<[K, V]>;
/**
* Returns an iterable of key, value pairs for every entry in the map.
*/
entries(): IterableIterator<[K, V]>;
/**
* Returns an iterable of keys in the map
*/
keys(): IterableIterator<K>;
/**
* Returns an iterable of values in the map
*/
values(): IterableIterator<V>;
}
interface ReadonlyMap<K, V> {
/** Returns an iterable of entries in the map. */
[Symbol.iterator](): IterableIterator<[K, V]>;
/**
* Returns an iterable of key, value pairs for every entry in the map.
*/
entries(): IterableIterator<[K, V]>;
/**
* Returns an iterable of keys in the map
*/
keys(): IterableIterator<K>;
/**
* Returns an iterable of values in the map
*/
values(): IterableIterator<V>;
}
interface MapConstructor {
new <K, V>(iterable: Iterable<[K, V]>): Map<K, V>;
}
interface WeakMap<K extends object, V> { }
interface WeakMapConstructor {
new <K extends object, V>(iterable: Iterable<[K, V]>): WeakMap<K, V>;
}
interface Set<T> {
/** Iterates over values in the set. */
[Symbol.iterator](): IterableIterator<T>;
/**
* Returns an iterable of [v,v] pairs for every value `v` in the set.
*/
entries(): IterableIterator<[T, T]>;
/**
* Despite its name, returns an iterable of the values in the set,
*/
keys(): IterableIterator<T>;
/**
* Returns an iterable of values in the set.
*/
values(): IterableIterator<T>;
}
interface ReadonlySet<T> {
/** Iterates over values in the set. */
[Symbol.iterator](): IterableIterator<T>;
/**
* Returns an iterable of [v,v] pairs for every value `v` in the set.
*/
entries(): IterableIterator<[T, T]>;
/**
* Despite its name, returns an iterable of the values in the set,
*/
keys(): IterableIterator<T>;
/**
* Returns an iterable of values in the set.
*/
values(): IterableIterator<T>;
}
interface SetConstructor {
new <T>(iterable: Iterable<T>): Set<T>;
}
interface WeakSet<T> { }
interface WeakSetConstructor {
new <T extends object>(iterable: Iterable<T>): WeakSet<T>;
}
interface Promise<T> { }
interface PromiseConstructor {
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<TAll>(values: Iterable<TAll | PromiseLike<TAll>>): Promise<TAll[]>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T>(values: Iterable<T | PromiseLike<T>>): Promise<T>;
}
declare namespace Reflect {
function enumerate(target: object): IterableIterator<any>;
}
interface String {
/** Iterator */
[Symbol.iterator](): IterableIterator<string>;
}
/**
* A typed array of 8-bit integer values. The contents are initialized to 0. If the requested
* number of bytes could not be allocated an exception is raised.
*/
interface Int8Array {
[Symbol.iterator](): IterableIterator<number>;
/**
* Returns an array of key, value pairs for every entry in the array
*/
entries(): IterableIterator<[number, number]>;
/**
* Returns an list of keys in the array
*/
keys(): IterableIterator<number>;
/**
* Returns an list of values in the array
*/
values(): IterableIterator<number>;
}
interface Int8ArrayConstructor {
new (elements: Iterable<number>): Int8Array;
/**
* Creates an array from an array-like or iterable object.
* @param arrayLike An array-like or iterable object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number): Int8Array;
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number, thisArg: undefined): Int8Array;
from<Z>(arrayLike: Iterable<number>, mapfn: (this: Z, v: number, k: number) => number, thisArg: Z): Int8Array;
from(arrayLike: Iterable<number>): Int8Array;
}
/**
* A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the
* requested number of bytes could not be allocated an exception is raised.
*/
interface Uint8Array {
[Symbol.iterator](): IterableIterator<number>;
/**
* Returns an array of key, value pairs for every entry in the array
*/
entries(): IterableIterator<[number, number]>;
/**
* Returns an list of keys in the array
*/
keys(): IterableIterator<number>;
/**
* Returns an list of values in the array
*/
values(): IterableIterator<number>;
}
interface Uint8ArrayConstructor {
new (elements: Iterable<number>): Uint8Array;
/**
* Creates an array from an array-like or iterable object.
* @param arrayLike An array-like or iterable object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number): Uint8Array;
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number, thisArg: undefined): Uint8Array;
from<Z>(arrayLike: Iterable<number>, mapfn: (this: Z, v: number, k: number) => number, thisArg: Z): Uint8Array;
from(arrayLike: Iterable<number>): Uint8Array;
}
/**
* A typed array of 8-bit unsigned integer (clamped) values. The contents are initialized to 0.
* If the requested number of bytes could not be allocated an exception is raised.
*/
interface Uint8ClampedArray {
[Symbol.iterator](): IterableIterator<number>;
/**
* Returns an array of key, value pairs for every entry in the array
*/
entries(): IterableIterator<[number, number]>;
/**
* Returns an list of keys in the array
*/
keys(): IterableIterator<number>;
/**
* Returns an list of values in the array
*/
values(): IterableIterator<number>;
}
interface Uint8ClampedArrayConstructor {
new (elements: Iterable<number>): Uint8ClampedArray;
/**
* Creates an array from an array-like or iterable object.
* @param arrayLike An array-like or iterable object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number): Uint8ClampedArray;
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number, thisArg: undefined): Uint8ClampedArray;
from<Z>(arrayLike: Iterable<number>, mapfn: (this: Z, v: number, k: number) => number, thisArg: Z): Uint8ClampedArray;
from(arrayLike: Iterable<number>): Uint8ClampedArray;
}
/**
* A typed array of 16-bit signed integer values. The contents are initialized to 0. If the
* requested number of bytes could not be allocated an exception is raised.
*/
interface Int16Array {
[Symbol.iterator](): IterableIterator<number>;
/**
* Returns an array of key, value pairs for every entry in the array
*/
entries(): IterableIterator<[number, number]>;
/**
* Returns an list of keys in the array
*/
keys(): IterableIterator<number>;
/**
* Returns an list of values in the array
*/
values(): IterableIterator<number>;
}
interface Int16ArrayConstructor {
new (elements: Iterable<number>): Int16Array;
/**
* Creates an array from an array-like or iterable object.
* @param arrayLike An array-like or iterable object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number): Int16Array;
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number, thisArg: undefined): Int16Array;
from<Z>(arrayLike: Iterable<number>, mapfn: (this: Z, v: number, k: number) => number, thisArg: Z): Int16Array;
from(arrayLike: Iterable<number>): Int16Array;
}
/**
* A typed array of 16-bit unsigned integer values. The contents are initialized to 0. If the
* requested number of bytes could not be allocated an exception is raised.
*/
interface Uint16Array {
[Symbol.iterator](): IterableIterator<number>;
/**
* Returns an array of key, value pairs for every entry in the array
*/
entries(): IterableIterator<[number, number]>;
/**
* Returns an list of keys in the array
*/
keys(): IterableIterator<number>;
/**
* Returns an list of values in the array
*/
values(): IterableIterator<number>;
}
interface Uint16ArrayConstructor {
new (elements: Iterable<number>): Uint16Array;
/**
* Creates an array from an array-like or iterable object.
* @param arrayLike An array-like or iterable object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number): Uint16Array;
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number, thisArg: undefined): Uint16Array;
from<Z>(arrayLike: Iterable<number>, mapfn: (this: Z, v: number, k: number) => number, thisArg: Z): Uint16Array;
from(arrayLike: Iterable<number>): Uint16Array;
}
/**
* A typed array of 32-bit signed integer values. The contents are initialized to 0. If the
* requested number of bytes could not be allocated an exception is raised.
*/
interface Int32Array {
[Symbol.iterator](): IterableIterator<number>;
/**
* Returns an array of key, value pairs for every entry in the array
*/
entries(): IterableIterator<[number, number]>;
/**
* Returns an list of keys in the array
*/
keys(): IterableIterator<number>;
/**
* Returns an list of values in the array
*/
values(): IterableIterator<number>;
}
interface Int32ArrayConstructor {
new (elements: Iterable<number>): Int32Array;
/**
* Creates an array from an array-like or iterable object.
* @param arrayLike An array-like or iterable object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number): Int32Array;
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number, thisArg: undefined): Int32Array;
from<Z>(arrayLike: Iterable<number>, mapfn: (this: Z, v: number, k: number) => number, thisArg: Z): Int32Array;
from(arrayLike: Iterable<number>): Int32Array;
}
/**
* A typed array of 32-bit unsigned integer values. The contents are initialized to 0. If the
* requested number of bytes could not be allocated an exception is raised.
*/
interface Uint32Array {
[Symbol.iterator](): IterableIterator<number>;
/**
* Returns an array of key, value pairs for every entry in the array
*/
entries(): IterableIterator<[number, number]>;
/**
* Returns an list of keys in the array
*/
keys(): IterableIterator<number>;
/**
* Returns an list of values in the array
*/
values(): IterableIterator<number>;
}
interface Uint32ArrayConstructor {
new (elements: Iterable<number>): Uint32Array;
/**
* Creates an array from an array-like or iterable object.
* @param arrayLike An array-like or iterable object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number): Uint32Array;
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number, thisArg: undefined): Uint32Array;
from<Z>(arrayLike: Iterable<number>, mapfn: (this: Z, v: number, k: number) => number, thisArg: Z): Uint32Array;
from(arrayLike: Iterable<number>): Uint32Array;
}
/**
* A typed array of 32-bit float values. The contents are initialized to 0. If the requested number
* of bytes could not be allocated an exception is raised.
*/
interface Float32Array {
[Symbol.iterator](): IterableIterator<number>;
/**
* Returns an array of key, value pairs for every entry in the array
*/
entries(): IterableIterator<[number, number]>;
/**
* Returns an list of keys in the array
*/
keys(): IterableIterator<number>;
/**
* Returns an list of values in the array
*/
values(): IterableIterator<number>;
}
interface Float32ArrayConstructor {
new (elements: Iterable<number>): Float32Array;
/**
* Creates an array from an array-like or iterable object.
* @param arrayLike An array-like or iterable object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number): Float32Array;
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number, thisArg: undefined): Float32Array;
from<Z>(arrayLike: Iterable<number>, mapfn: (this: Z, v: number, k: number) => number, thisArg: Z): Float32Array;
from(arrayLike: Iterable<number>): Float32Array;
}
/**
* A typed array of 64-bit float values. The contents are initialized to 0. If the requested
* number of bytes could not be allocated an exception is raised.
*/
interface Float64Array {
[Symbol.iterator](): IterableIterator<number>;
/**
* Returns an array of key, value pairs for every entry in the array
*/
entries(): IterableIterator<[number, number]>;
/**
* Returns an list of keys in the array
*/
keys(): IterableIterator<number>;
/**
* Returns an list of values in the array
*/
values(): IterableIterator<number>;
}
interface Float64ArrayConstructor {
new (elements: Iterable<number>): Float64Array;
/**
* Creates an array from an array-like or iterable object.
* @param arrayLike An array-like or iterable object to convert to an array.
* @param mapfn A mapping function to call on every element of the array.
* @param thisArg Value of 'this' used to invoke the mapfn.
*/
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number): Float64Array;
from(arrayLike: Iterable<number>, mapfn: (this: void, v: number, k: number) => number, thisArg: undefined): Float64Array;
from<Z>(arrayLike: Iterable<number>, mapfn: (this: Z, v: number, k: number) => number, thisArg: Z): Float64Array;
from(arrayLike: Iterable<number>): Float64Array;
}
+203
View File
@@ -0,0 +1,203 @@
interface PromiseConstructor {
/**
* A reference to the prototype.
*/
readonly prototype: Promise<any>;
/**
* Creates a new Promise.
* @param executor A callback used to initialize the promise. This callback is passed two arguments:
* a resolve callback used resolve the promise with a value or the result of another promise,
* and a reject callback used to reject the promise with a provided reason or error.
*/
new <T>(executor: (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void): Promise<T>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9, T10]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8, T9]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6, T7, T8>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): Promise<[T1, T2, T3, T4, T5, T6, T7, T8]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6, T7>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>]): Promise<[T1, T2, T3, T4, T5, T6, T7]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5, T6>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>]): Promise<[T1, T2, T3, T4, T5, T6]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4, T5>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>, T5 | PromiseLike<T5>]): Promise<[T1, T2, T3, T4, T5]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3, T4>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike <T4>]): Promise<[T1, T2, T3, T4]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2, T3>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<[T1, T2, T3]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T1, T2>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<[T1, T2]>;
/**
* Creates a Promise that is resolved with an array of results when all of the provided Promises
* resolve, or rejected when any Promise is rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
all<T>(values: (T | PromiseLike<T>)[]): Promise<T[]>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>, T10 | PromiseLike<T10>]): Promise<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9 | T10>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T1, T2, T3, T4, T5, T6, T7, T8, T9>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>, T9 | PromiseLike<T9>]): Promise<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8 | T9>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T1, T2, T3, T4, T5, T6, T7, T8>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>, T8 | PromiseLike<T8>]): Promise<T1 | T2 | T3 | T4 | T5 | T6 | T7 | T8>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T1, T2, T3, T4, T5, T6, T7>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>, T7 | PromiseLike<T7>]): Promise<T1 | T2 | T3 | T4 | T5 | T6 | T7>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T1, T2, T3, T4, T5, T6>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>, T6 | PromiseLike<T6>]): Promise<T1 | T2 | T3 | T4 | T5 | T6>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T1, T2, T3, T4, T5>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>, T5 | PromiseLike<T5>]): Promise<T1 | T2 | T3 | T4 | T5>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T1, T2, T3, T4>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>, T4 | PromiseLike<T4>]): Promise<T1 | T2 | T3 | T4>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T1, T2, T3>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>, T3 | PromiseLike<T3>]): Promise<T1 | T2 | T3>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T1, T2>(values: [T1 | PromiseLike<T1>, T2 | PromiseLike<T2>]): Promise<T1 | T2>;
/**
* Creates a Promise that is resolved or rejected when any of the provided Promises are resolved
* or rejected.
* @param values An array of Promises.
* @returns A new Promise.
*/
race<T>(values: (T | PromiseLike<T>)[]): Promise<T>;
/**
* Creates a new rejected promise for the provided reason.
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
reject(reason: any): Promise<never>;
/**
* Creates a new rejected promise for the provided reason.
* @param reason The reason the promise was rejected.
* @returns A new rejected Promise.
*/
reject<T>(reason: any): Promise<T>;
/**
* Creates a new resolved promise for the provided value.
* @param value A promise.
* @returns A promise whose internal state matches the provided promise.
*/
resolve<T>(value: T | PromiseLike<T>): Promise<T>;
/**
* Creates a new resolved promise .
* @returns A resolved promise.
*/
resolve(): Promise<void>;
}
declare var Promise: PromiseConstructor;
+22
View File
@@ -0,0 +1,22 @@
interface ProxyHandler<T extends object> {
getPrototypeOf? (target: T): object | null;
setPrototypeOf? (target: T, v: any): boolean;
isExtensible? (target: T): boolean;
preventExtensions? (target: T): boolean;
getOwnPropertyDescriptor? (target: T, p: PropertyKey): PropertyDescriptor | undefined;
has? (target: T, p: PropertyKey): boolean;
get? (target: T, p: PropertyKey, receiver: any): any;
set? (target: T, p: PropertyKey, value: any, receiver: any): boolean;
deleteProperty? (target: T, p: PropertyKey): boolean;
defineProperty? (target: T, p: PropertyKey, attributes: PropertyDescriptor): boolean;
enumerate? (target: T): PropertyKey[];
ownKeys? (target: T): PropertyKey[];
apply? (target: T, thisArg: any, argArray?: any): any;
construct? (target: T, argArray: any, newTarget?: any): object;
}
interface ProxyConstructor {
revocable<T extends object>(target: T, handler: ProxyHandler<T>): { proxy: T; revoke: () => void; };
new <T extends object>(target: T, handler: ProxyHandler<T>): T;
}
declare var Proxy: ProxyConstructor;
+15
View File
@@ -0,0 +1,15 @@
declare namespace Reflect {
function apply(target: Function, thisArgument: any, argumentsList: ArrayLike<any>): any;
function construct(target: Function, argumentsList: ArrayLike<any>, newTarget?: any): any;
function defineProperty(target: object, propertyKey: PropertyKey, attributes: PropertyDescriptor): boolean;
function deleteProperty(target: object, propertyKey: PropertyKey): boolean;
function get(target: object, propertyKey: PropertyKey, receiver?: any): any;
function getOwnPropertyDescriptor(target: object, propertyKey: PropertyKey): PropertyDescriptor;
function getPrototypeOf(target: object): object;
function has(target: object, propertyKey: PropertyKey): boolean;
function isExtensible(target: object): boolean;
function ownKeys(target: object): Array<PropertyKey>;
function preventExtensions(target: object): boolean;
function set(target: object, propertyKey: PropertyKey, value: any, receiver?: any): boolean;
function setPrototypeOf(target: object, proto: any): boolean;
}
+36
View File
@@ -0,0 +1,36 @@
interface Symbol {
/** Returns a string representation of an object. */
toString(): string;
/** Returns the primitive value of the specified object. */
valueOf(): symbol;
}
interface SymbolConstructor {
/**
* A reference to the prototype.
*/
readonly prototype: Symbol;
/**
* Returns a new unique Symbol value.
* @param description Description of the new Symbol object.
*/
(description?: string | number): symbol;
/**
* Returns a Symbol object from the global symbol registry matching the given key if found.
* Otherwise, returns a new symbol with this key.
* @param key key to search for.
*/
for(key: string): symbol;
/**
* Returns a key from the global symbol registry matching the given Symbol if found.
* Otherwise, returns a undefined.
* @param sym Symbol to find the key for.
*/
keyFor(sym: symbol): string | undefined;
}
declare var Symbol: SymbolConstructor;
+327
View File
@@ -0,0 +1,327 @@
/// <reference path="lib.es2015.symbol.d.ts" />
interface SymbolConstructor {
/**
* A method that determines if a constructor object recognizes an object as one of the
* constructors instances. Called by the semantics of the instanceof operator.
*/
readonly hasInstance: symbol;
/**
* A Boolean value that if true indicates that an object should flatten to its array elements
* by Array.prototype.concat.
*/
readonly isConcatSpreadable: symbol;
/**
* A regular expression method that matches the regular expression against a string. Called
* by the String.prototype.match method.
*/
readonly match: symbol;
/**
* A regular expression method that replaces matched substrings of a string. Called by the
* String.prototype.replace method.
*/
readonly replace: symbol;
/**
* A regular expression method that returns the index within a string that matches the
* regular expression. Called by the String.prototype.search method.
*/
readonly search: symbol;
/**
* A function valued property that is the constructor function that is used to create
* derived objects.
*/
readonly species: symbol;
/**
* A regular expression method that splits a string at the indices that match the regular
* expression. Called by the String.prototype.split method.
*/
readonly split: symbol;
/**
* A method that converts an object to a corresponding primitive value.
* Called by the ToPrimitive abstract operation.
*/
readonly toPrimitive: symbol;
/**
* A String value that is used in the creation of the default string description of an object.
* Called by the built-in method Object.prototype.toString.
*/
readonly toStringTag: symbol;
/**
* An Object whose own property names are property names that are excluded from the 'with'
* environment bindings of the associated objects.
*/
readonly unscopables: symbol;
}
interface Symbol {
readonly [Symbol.toStringTag]: "Symbol";
}
interface Array<T> {
/**
* Returns an object whose properties have the value 'true'
* when they will be absent when used in a 'with' statement.
*/
[Symbol.unscopables](): {
copyWithin: boolean;
entries: boolean;
fill: boolean;
find: boolean;
findIndex: boolean;
keys: boolean;
values: boolean;
};
}
interface Date {
/**
* Converts a Date object to a string.
*/
[Symbol.toPrimitive](hint: "default"): string;
/**
* Converts a Date object to a string.
*/
[Symbol.toPrimitive](hint: "string"): string;
/**
* Converts a Date object to a number.
*/
[Symbol.toPrimitive](hint: "number"): number;
/**
* Converts a Date object to a string or number.
*
* @param hint The strings "number", "string", or "default" to specify what primitive to return.
*
* @throws {TypeError} If 'hint' was given something other than "number", "string", or "default".
* @returns A number if 'hint' was "number", a string if 'hint' was "string" or "default".
*/
[Symbol.toPrimitive](hint: string): string | number;
}
interface Map<K, V> {
readonly [Symbol.toStringTag]: "Map";
}
interface WeakMap<K extends object, V>{
readonly [Symbol.toStringTag]: "WeakMap";
}
interface Set<T> {
readonly [Symbol.toStringTag]: "Set";
}
interface WeakSet<T> {
readonly [Symbol.toStringTag]: "WeakSet";
}
interface JSON {
readonly [Symbol.toStringTag]: "JSON";
}
interface Function {
/**
* Determines whether the given value inherits from this function if this function was used
* as a constructor function.
*
* A constructor function can control which objects are recognized as its instances by
* 'instanceof' by overriding this method.
*/
[Symbol.hasInstance](value: any): boolean;
}
interface GeneratorFunction {
readonly [Symbol.toStringTag]: "GeneratorFunction";
}
interface Math {
readonly [Symbol.toStringTag]: "Math";
}
interface Promise<T> {
readonly [Symbol.toStringTag]: "Promise";
}
interface PromiseConstructor {
readonly [Symbol.species]: Function;
}
interface RegExp {
/**
* Matches a string with this regular expression, and returns an array containing the results of
* that search.
* @param string A string to search within.
*/
[Symbol.match](string: string): RegExpMatchArray | null;
/**
* Replaces text in a string, using this regular expression.
* @param string A String object or string literal whose contents matching against
* this regular expression will be replaced
* @param replaceValue A String object or string literal containing the text to replace for every
* successful match of this regular expression.
*/
[Symbol.replace](string: string, replaceValue: string): string;
/**
* Replaces text in a string, using this regular expression.
* @param string A String object or string literal whose contents matching against
* this regular expression will be replaced
* @param replacer A function that returns the replacement text.
*/
[Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string;
/**
* Finds the position beginning first substring match in a regular expression search
* using this regular expression.
*
* @param string The string to search within.
*/
[Symbol.search](string: string): number;
/**
* Returns an array of substrings that were delimited by strings in the original input that
* match against this regular expression.
*
* If the regular expression contains capturing parentheses, then each time this
* regular expression matches, the results (including any undefined results) of the
* capturing parentheses are spliced.
*
* @param string string value to split
* @param limit if not undefined, the output array is truncated so that it contains no more
* than 'limit' elements.
*/
[Symbol.split](string: string, limit?: number): string[];
}
interface RegExpConstructor {
[Symbol.species](): RegExpConstructor;
}
interface String {
/**
* Matches a string an object that supports being matched against, and returns an array containing the results of that search.
* @param matcher An object that supports being matched against.
*/
match(matcher: { [Symbol.match](string: string): RegExpMatchArray | null; }): RegExpMatchArray | null;
/**
* Replaces text in a string, using an object that supports replacement within a string.
* @param searchValue A object can search for and replace matches within a string.
* @param replaceValue A string containing the text to replace for every successful match of searchValue in this string.
*/
replace(searchValue: { [Symbol.replace](string: string, replaceValue: string): string; }, replaceValue: string): string;
/**
* Replaces text in a string, using an object that supports replacement within a string.
* @param searchValue A object can search for and replace matches within a string.
* @param replacer A function that returns the replacement text.
*/
replace(searchValue: { [Symbol.replace](string: string, replacer: (substring: string, ...args: any[]) => string): string; }, replacer: (substring: string, ...args: any[]) => string): string;
/**
* Finds the first substring match in a regular expression search.
* @param searcher An object which supports searching within a string.
*/
search(searcher: { [Symbol.search](string: string): number; }): number;
/**
* Split a string into substrings using the specified separator and return them as an array.
* @param splitter An object that can split a string.
* @param limit A value used to limit the number of elements returned in the array.
*/
split(splitter: { [Symbol.split](string: string, limit?: number): string[]; }, limit?: number): string[];
}
/**
* Represents a raw buffer of binary data, which is used to store data for the
* different typed arrays. ArrayBuffers cannot be read from or written to directly,
* but can be passed to a typed array or DataView Object to interpret the raw
* buffer as needed.
*/
interface ArrayBuffer {
readonly [Symbol.toStringTag]: "ArrayBuffer";
}
interface DataView {
readonly [Symbol.toStringTag]: "DataView";
}
/**
* A typed array of 8-bit integer values. The contents are initialized to 0. If the requested
* number of bytes could not be allocated an exception is raised.
*/
interface Int8Array {
readonly [Symbol.toStringTag]: "Int8Array";
}
/**
* A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the
* requested number of bytes could not be allocated an exception is raised.
*/
interface Uint8Array {
readonly [Symbol.toStringTag]: "UInt8Array";
}
/**
* A typed array of 8-bit unsigned integer (clamped) values. The contents are initialized to 0.
* If the requested number of bytes could not be allocated an exception is raised.
*/
interface Uint8ClampedArray {
readonly [Symbol.toStringTag]: "Uint8ClampedArray";
}
/**
* A typed array of 16-bit signed integer values. The contents are initialized to 0. If the
* requested number of bytes could not be allocated an exception is raised.
*/
interface Int16Array {
readonly [Symbol.toStringTag]: "Int16Array";
}
/**
* A typed array of 16-bit unsigned integer values. The contents are initialized to 0. If the
* requested number of bytes could not be allocated an exception is raised.
*/
interface Uint16Array {
readonly [Symbol.toStringTag]: "Uint16Array";
}
/**
* A typed array of 32-bit signed integer values. The contents are initialized to 0. If the
* requested number of bytes could not be allocated an exception is raised.
*/
interface Int32Array {
readonly [Symbol.toStringTag]: "Int32Array";
}
/**
* A typed array of 32-bit unsigned integer values. The contents are initialized to 0. If the
* requested number of bytes could not be allocated an exception is raised.
*/
interface Uint32Array {
readonly [Symbol.toStringTag]: "Uint32Array";
}
/**
* A typed array of 32-bit float values. The contents are initialized to 0. If the requested number
* of bytes could not be allocated an exception is raised.
*/
interface Float32Array {
readonly [Symbol.toStringTag]: "Float32Array";
}
/**
* A typed array of 64-bit float values. The contents are initialized to 0. If the requested
* number of bytes could not be allocated an exception is raised.
*/
interface Float64Array {
readonly [Symbol.toStringTag]: "Float64Array";
}
+98
View File
@@ -0,0 +1,98 @@
interface Array<T> {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: T, fromIndex?: number): boolean;
}
interface ReadonlyArray<T> {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: T, fromIndex?: number): boolean;
}
interface Int8Array {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: number, fromIndex?: number): boolean;
}
interface Uint8Array {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: number, fromIndex?: number): boolean;
}
interface Uint8ClampedArray {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: number, fromIndex?: number): boolean;
}
interface Int16Array {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: number, fromIndex?: number): boolean;
}
interface Uint16Array {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: number, fromIndex?: number): boolean;
}
interface Int32Array {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: number, fromIndex?: number): boolean;
}
interface Uint32Array {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: number, fromIndex?: number): boolean;
}
interface Float32Array {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: number, fromIndex?: number): boolean;
}
interface Float64Array {
/**
* Determines whether an array includes a certain element, returning true or false as appropriate.
* @param searchElement The element to search for.
* @param fromIndex The position in this array at which to begin searching for searchElement.
*/
includes(searchElement: number, fromIndex?: number): boolean;
}
+2
View File
@@ -0,0 +1,2 @@
/// <reference path="lib.es2015.d.ts" />
/// <reference path="lib.es2016.array.include.d.ts" />

Some files were not shown because too many files have changed in this diff Show More