Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
Kanchalai Tanglertsampan committed Sep 7, 2016
2 parents 9b7d8c7 + bef6a66 commit 4685646
Show file tree
Hide file tree
Showing 71 changed files with 1,829 additions and 131 deletions.
13 changes: 13 additions & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

/* @internal */
namespace ts {
const ambientModuleSymbolRegex = /^".+"$/;

let nextSymbolId = 1;
let nextNodeId = 1;
let nextMergeId = 1;
Expand Down Expand Up @@ -100,6 +102,7 @@ namespace ts {
getAliasedSymbol: resolveAlias,
getEmitResolver,
getExportsOfModule: getExportsOfModuleAsArray,
getAmbientModules,

getJsxElementAttributesType,
getJsxIntrinsicTagNames,
Expand Down Expand Up @@ -20195,5 +20198,15 @@ namespace ts {
return true;
}
}

function getAmbientModules(): Symbol[] {
const result: Symbol[] = [];
for (const sym in globals) {
if (ambientModuleSymbolRegex.test(sym)) {
result.push(globals[sym]);
}
}
return result;
}
}
}
8 changes: 4 additions & 4 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ namespace ts {

const emptyArray: any[] = [];

export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean): string {
export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string {
while (true) {
const fileName = combinePaths(searchPath, "tsconfig.json");
const fileName = combinePaths(searchPath, configName);
if (fileExists(fileName)) {
return fileName;
}
Expand Down Expand Up @@ -166,7 +166,7 @@ namespace ts {

const typeReferenceExtensions = [".d.ts"];

function getEffectiveTypeRoots(options: CompilerOptions, host: ModuleResolutionHost): string[] | undefined {
export function getEffectiveTypeRoots(options: CompilerOptions, host: { directoryExists?: (directoryName: string) => boolean, getCurrentDirectory?: () => string }): string[] | undefined {
if (options.typeRoots) {
return options.typeRoots;
}
Expand All @@ -186,7 +186,7 @@ namespace ts {
* Returns the path to every node_modules/@types directory from some ancestor directory.
* Returns undefined if there are none.
*/
function getDefaultTypeRoots(currentDirectory: string, host: ModuleResolutionHost): string[] | undefined {
function getDefaultTypeRoots(currentDirectory: string, host: { directoryExists?: (directoryName: string) => boolean }): string[] | undefined {
if (!host.directoryExists) {
return [combinePaths(currentDirectory, nodeModulesAtTypes)];
// And if it doesn't exist, tough.
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1965,6 +1965,7 @@ namespace ts {
getJsxElementAttributesType(elementNode: JsxOpeningLikeElement): Type;
getJsxIntrinsicTagNames(): Symbol[];
isOptionalParameter(node: ParameterDeclaration): boolean;
getAmbientModules(): Symbol[];

// Should not be called directly. Should only be accessed through the Program instance.
/* @internal */ getDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): Diagnostic[];
Expand Down
108 changes: 81 additions & 27 deletions src/harness/fourslash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,22 +263,31 @@ namespace FourSlash {
constructor(private basePath: string, private testType: FourSlashTestType, public testData: FourSlashData) {
// Create a new Services Adapter
this.cancellationToken = new TestCancellationToken();
const compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions);
if (compilationOptions.typeRoots) {
compilationOptions.typeRoots = compilationOptions.typeRoots.map(p => ts.getNormalizedAbsolutePath(p, this.basePath));
}
let compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions);
compilationOptions.skipDefaultLibCheck = true;

const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions);
this.languageServiceAdapterHost = languageServiceAdapter.getHost();
this.languageService = languageServiceAdapter.getLanguageService();

// Initialize the language service with all the scripts
let startResolveFileRef: FourSlashFile;

ts.forEach(testData.files, file => {
// Create map between fileName and its content for easily looking up when resolveReference flag is specified
this.inputFiles[file.fileName] = file.content;

if (ts.getBaseFileName(file.fileName).toLowerCase() === "tsconfig.json") {
const configJson = ts.parseConfigFileTextToJson(file.fileName, file.content);
assert.isTrue(configJson.config !== undefined);

// Extend our existing compiler options so that we can also support tsconfig only options
if (configJson.config.compilerOptions) {
const baseDirectory = ts.normalizePath(ts.getDirectoryPath(file.fileName));
const tsConfig = ts.convertCompilerOptionsFromJson(configJson.config.compilerOptions, baseDirectory, file.fileName);

if (!tsConfig.errors || !tsConfig.errors.length) {
compilationOptions = ts.extend(compilationOptions, tsConfig.options);
}
}
}

if (!startResolveFileRef && file.fileOptions[metadataOptionNames.resolveReference] === "true") {
startResolveFileRef = file;
}
Expand All @@ -288,6 +297,15 @@ namespace FourSlash {
}
});


if (compilationOptions.typeRoots) {
compilationOptions.typeRoots = compilationOptions.typeRoots.map(p => ts.getNormalizedAbsolutePath(p, this.basePath));
}

const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions);
this.languageServiceAdapterHost = languageServiceAdapter.getHost();
this.languageService = languageServiceAdapter.getLanguageService();

if (startResolveFileRef) {
// Add the entry-point file itself into the languageServiceShimHost
this.languageServiceAdapterHost.addScript(startResolveFileRef.fileName, startResolveFileRef.content, /*isRootFile*/ true);
Expand Down Expand Up @@ -342,6 +360,7 @@ namespace FourSlash {
InsertSpaceAfterFunctionKeywordForAnonymousFunctions: false,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis: false,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets: false,
InsertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
InsertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false,
InsertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false,
PlaceOpenBraceOnNewLineForFunctions: false,
Expand Down Expand Up @@ -730,10 +749,10 @@ namespace FourSlash {
}
}

public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string) {
public verifyCompletionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
const completions = this.getCompletionListAtCaret();
if (completions) {
this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind);
this.assertItemInCompletionList(completions.entries, symbol, text, documentation, kind, spanIndex);
}
else {
this.raiseError(`No completions at position '${this.currentCaretPosition}' when looking for '${symbol}'.`);
Expand All @@ -749,25 +768,32 @@ namespace FourSlash {
* @param expectedText the text associated with the symbol
* @param expectedDocumentation the documentation text associated with the symbol
* @param expectedKind the kind of symbol (see ScriptElementKind)
* @param spanIndex the index of the range that the completion item's replacement text span should match
*/
public verifyCompletionListDoesNotContain(symbol: string, expectedText?: string, expectedDocumentation?: string, expectedKind?: string) {
public verifyCompletionListDoesNotContain(symbol: string, expectedText?: string, expectedDocumentation?: string, expectedKind?: string, spanIndex?: number) {
const that = this;
let replacementSpan: ts.TextSpan;
if (spanIndex !== undefined) {
replacementSpan = this.getTextSpanForRangeAtIndex(spanIndex);
}

function filterByTextOrDocumentation(entry: ts.CompletionEntry) {
const details = that.getCompletionEntryDetails(entry.name);
const documentation = ts.displayPartsToString(details.documentation);
const text = ts.displayPartsToString(details.displayParts);
if (expectedText && expectedDocumentation) {
return (documentation === expectedDocumentation && text === expectedText) ? true : false;

// If any of the expected values are undefined, assume that users don't
// care about them.
if (replacementSpan && !TestState.textSpansEqual(replacementSpan, entry.replacementSpan)) {
return false;
}
else if (expectedText && !expectedDocumentation) {
return text === expectedText ? true : false;
else if (expectedText && text !== expectedText) {
return false;
}
else if (expectedDocumentation && !expectedText) {
return documentation === expectedDocumentation ? true : false;
else if (expectedDocumentation && documentation !== expectedDocumentation) {
return false;
}
// Because expectedText and expectedDocumentation are undefined, we assume that
// users don"t care to compare them so we will treat that entry as if the entry has matching text and documentation
// and keep it in the list of filtered entry.

return true;
}

Expand All @@ -791,6 +817,10 @@ namespace FourSlash {
if (expectedKind) {
error += "Expected kind: " + expectedKind + " to equal: " + filterCompletions[0].kind + ".";
}
if (replacementSpan) {
const spanText = filterCompletions[0].replacementSpan ? stringify(filterCompletions[0].replacementSpan) : undefined;
error += "Expected replacement span: " + stringify(replacementSpan) + " to equal: " + spanText + ".";
}
this.raiseError(error);
}
}
Expand Down Expand Up @@ -2188,7 +2218,7 @@ namespace FourSlash {
return text.substring(startPos, endPos);
}

private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string) {
private assertItemInCompletionList(items: ts.CompletionEntry[], name: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
for (let i = 0; i < items.length; i++) {
const item = items[i];
if (item.name === name) {
Expand All @@ -2207,6 +2237,11 @@ namespace FourSlash {
assert.equal(item.kind, kind, this.assertionMessageAtLastKnownMarker("completion item kind for " + name));
}

if (spanIndex !== undefined) {
const span = this.getTextSpanForRangeAtIndex(spanIndex);
assert.isTrue(TestState.textSpansEqual(span, item.replacementSpan), this.assertionMessageAtLastKnownMarker(stringify(span) + " does not equal " + stringify(item.replacementSpan) + " replacement span for " + name));
}

return;
}
}
Expand Down Expand Up @@ -2263,6 +2298,17 @@ namespace FourSlash {
return `line ${(pos.line + 1)}, col ${pos.character}`;
}

private getTextSpanForRangeAtIndex(index: number): ts.TextSpan {
const ranges = this.getRanges();
if (ranges && ranges.length > index) {
const range = ranges[index];
return { start: range.start, length: range.end - range.start };
}
else {
this.raiseError("Supplied span index: " + index + " does not exist in range list of size: " + (ranges ? 0 : ranges.length));
}
}

public getMarkerByName(markerName: string) {
const markerPos = this.testData.markerPositions[markerName];
if (markerPos === undefined) {
Expand All @@ -2286,6 +2332,10 @@ namespace FourSlash {
public resetCancelled(): void {
this.cancellationToken.resetCancelled();
}

private static textSpansEqual(a: ts.TextSpan, b: ts.TextSpan) {
return a && b && a.start === b.start && a.length === b.length;
}
}

export function runFourSlashTest(basePath: string, testType: FourSlashTestType, fileName: string) {
Expand All @@ -2294,12 +2344,16 @@ namespace FourSlash {
}

export function runFourSlashTestContent(basePath: string, testType: FourSlashTestType, content: string, fileName: string): void {
// Give file paths an absolute path for the virtual file system
const absoluteBasePath = ts.combinePaths(Harness.virtualFileSystemRoot, basePath);
const absoluteFileName = ts.combinePaths(Harness.virtualFileSystemRoot, fileName);

// Parse out the files and their metadata
const testData = parseTestData(basePath, content, fileName);
const state = new TestState(basePath, testType, testData);
const testData = parseTestData(absoluteBasePath, content, absoluteFileName);
const state = new TestState(absoluteBasePath, testType, testData);
const output = ts.transpileModule(content, { reportDiagnostics: true });
if (output.diagnostics.length > 0) {
throw new Error(`Syntax error in ${basePath}: ${output.diagnostics[0].messageText}`);
throw new Error(`Syntax error in ${absoluteBasePath}: ${output.diagnostics[0].messageText}`);
}
runCode(output.outputText, state);
}
Expand Down Expand Up @@ -2852,12 +2906,12 @@ namespace FourSlashInterface {

// Verifies the completion list contains the specified symbol. The
// completion list is brought up if necessary
public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string) {
public completionListContains(symbol: string, text?: string, documentation?: string, kind?: string, spanIndex?: number) {
if (this.negative) {
this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind);
this.state.verifyCompletionListDoesNotContain(symbol, text, documentation, kind, spanIndex);
}
else {
this.state.verifyCompletionListContains(symbol, text, documentation, kind);
this.state.verifyCompletionListContains(symbol, text, documentation, kind, spanIndex);
}
}

Expand Down
3 changes: 3 additions & 0 deletions src/harness/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,9 @@ namespace Harness {
// harness always uses one kind of new line
const harnessNewLine = "\r\n";

// Root for file paths that are stored in a virtual file system
export const virtualFileSystemRoot = "/";

namespace IOImpl {
declare class Enumerator {
public atEnd(): boolean;
Expand Down
Loading

1 comment on commit 4685646

@basarat
Copy link
Contributor

@basarat basarat commented on 4685646 Sep 7, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 💯 x🌹

Please sign in to comment.