mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 20:25:23 -06:00
Handling more compiler options and minor refactor
This commit is contained in:
parent
84a10e439e
commit
ed2da32776
@ -245,14 +245,7 @@ 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));
|
||||
}
|
||||
|
||||
const languageServiceAdapter = this.getLanguageServiceAdapter(testType, this.cancellationToken, compilationOptions);
|
||||
this.languageServiceAdapterHost = languageServiceAdapter.getHost();
|
||||
this.languageService = languageServiceAdapter.getLanguageService();
|
||||
let compilationOptions = convertGlobalOptionsToCompilerOptions(this.testData.globalOptions);
|
||||
|
||||
// Initialize the language service with all the scripts
|
||||
let startResolveFileRef: FourSlashFile;
|
||||
@ -260,6 +253,22 @@ namespace FourSlash {
|
||||
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) {
|
||||
let baseDir = ts.normalizePath(ts.getDirectoryPath(file.fileName));
|
||||
let tsConfig = ts.convertCompilerOptionsFromJson(configJson.config.compilerOptions, baseDir, file.fileName);
|
||||
|
||||
if (!tsConfig.errors || !tsConfig.errors.length) {
|
||||
compilationOptions = ts.extend(compilationOptions, tsConfig.options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!startResolveFileRef && file.fileOptions[metadataOptionNames.resolveReference] === "true") {
|
||||
startResolveFileRef = file;
|
||||
}
|
||||
@ -269,6 +278,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);
|
||||
|
||||
@ -1757,6 +1757,12 @@ namespace ts {
|
||||
owners: string[];
|
||||
}
|
||||
|
||||
interface VisibleModuleInfo {
|
||||
moduleName: string;
|
||||
moduleDir: string;
|
||||
canBeImported: boolean;
|
||||
}
|
||||
|
||||
export interface DisplayPartsSymbolWriter extends SymbolWriter {
|
||||
displayParts(): SymbolDisplayPart[];
|
||||
}
|
||||
@ -4438,18 +4444,100 @@ namespace ts {
|
||||
/**
|
||||
* Check all of the declared modules and those in node modules. Possible sources of modules:
|
||||
* Modules that are found by the type checker
|
||||
* Modules found relative to "baseUrl" compliler options (including patterns from "paths" compiler option)
|
||||
* Modules from node_modules (i.e. those listed in package.json)
|
||||
* This includes all files that are found in node_modules/moduleName/ with acceptable file extensions
|
||||
*/
|
||||
function getCompletionEntriesForNonRelativeModules(fragment: string, scriptPath: string): CompletionEntry[] {
|
||||
return ts.map(enumeratePotentialNonRelativeModules(fragment, scriptPath), (moduleName) => {
|
||||
return {
|
||||
name: moduleName,
|
||||
kind: ScriptElementKind.externalModuleName,
|
||||
kindModifiers: ScriptElementKindModifier.none,
|
||||
sortText: moduleName
|
||||
};
|
||||
const options = program.getCompilerOptions();
|
||||
const { baseUrl, paths } = options;
|
||||
|
||||
let result: CompletionEntry[];
|
||||
|
||||
if (baseUrl) {
|
||||
const fileExtensions = getSupportedExtensions(options);
|
||||
const absolute = isRootedDiskPath(baseUrl) ? baseUrl : combinePaths(host.getCurrentDirectory(), baseUrl)
|
||||
result = getCompletionEntriesForDirectoryFragment(fragment, normalizePath(absolute), fileExtensions, /*includeExtensions*/false);
|
||||
|
||||
if (paths) {
|
||||
for (var path in paths) {
|
||||
if (paths.hasOwnProperty(path)) {
|
||||
if (path === "*") {
|
||||
if (paths[path]) {
|
||||
forEach(paths[path], pattern => {
|
||||
forEach(getModulesForPathsPattern(fragment, baseUrl, pattern, fileExtensions), match => {
|
||||
result.push(createCompletionEntryForModule(match, ScriptElementKind.externalModuleName));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (startsWith(path, fragment)) {
|
||||
const entry = paths[path] && paths[path].length === 1 && paths[path][0];
|
||||
if (entry) {
|
||||
result.push(createCompletionEntryForModule(path, ScriptElementKind.externalModuleName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
result = [];
|
||||
}
|
||||
|
||||
|
||||
|
||||
forEach(enumeratePotentialNonRelativeModules(fragment, scriptPath), moduleName => {
|
||||
result.push(createCompletionEntryForModule(moduleName, ScriptElementKind.externalModuleName));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function getModulesForPathsPattern(fragment: string, baseUrl: string, pattern: string, fileExtensions: string[]): string[] {
|
||||
const parsed = hasZeroOrOneAsteriskCharacter(pattern) ? tryParsePattern(pattern) : undefined;
|
||||
if (parsed) {
|
||||
const hasTrailingSlash = parsed.prefix.charAt(parsed.prefix.length - 1) === "/" || parsed.prefix.charAt(parsed.prefix.length - 1) === "\\";
|
||||
|
||||
// The prefix has two effective parts: the directory path and the base component after the filepath that is not a
|
||||
// full directory component. For example: directory/path/of/prefix/base*
|
||||
const normalizedPrefix = hasTrailingSlash ? ensureTrailingDirectorySeparator(normalizePath(parsed.prefix)) : normalizePath(parsed.prefix);
|
||||
const normalizedPrefixDirectory = getDirectoryPath(normalizedPrefix);
|
||||
const normalizedPrefixBase = getBaseFileName(normalizedPrefix);
|
||||
|
||||
const fragmentHasPath = fragment.indexOf(directorySeparator) !== -1;
|
||||
|
||||
// Try and expand the prefix to include any path from the fragment so that we can limit the readDirectory call
|
||||
const expandedPrefixDir = fragmentHasPath ? combinePaths(normalizedPrefixDirectory, normalizedPrefixBase + getDirectoryPath(fragment)) : normalizedPrefixDirectory;
|
||||
|
||||
const normalizedSuffix = normalizePath(parsed.suffix);
|
||||
const baseDirectory = combinePaths(baseUrl, expandedPrefixDir);
|
||||
const completePrefix = fragmentHasPath ? baseDirectory : ensureTrailingDirectorySeparator(baseDirectory) + normalizedPrefixBase;
|
||||
|
||||
// If we have a suffix, then we need to read the directory all the way down. We could create a glob
|
||||
// that encodes the suffix, but we would have to escape the character "?" which readDirectory
|
||||
// doesn't support. For now, this is safer but slower
|
||||
const includeGlob = normalizedSuffix ? "**/*" : "./*"
|
||||
|
||||
const matches = host.readDirectory(baseDirectory, fileExtensions, undefined, [includeGlob]);
|
||||
const result: string[] = [];
|
||||
|
||||
// Trim away prefix and suffix
|
||||
forEach(matches, match => {
|
||||
const normalizedMatch = normalizePath(match);
|
||||
if (!endsWith(normalizedMatch, normalizedSuffix) || !startsWith(normalizedMatch, completePrefix)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const start = completePrefix.length;
|
||||
const length = normalizedMatch.length - start - normalizedSuffix.length;
|
||||
|
||||
result.push(removeFileExtension(normalizedMatch.substr(start, length)));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function enumeratePotentialNonRelativeModules(fragment: string, scriptPath: string): string[] {
|
||||
@ -4513,6 +4601,112 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string, modulePrefix?: string) {
|
||||
const result: VisibleModuleInfo[] = [];
|
||||
findPackageJsons(scriptPath).forEach((packageJson) => {
|
||||
const package = tryReadingPackageJson(packageJson);
|
||||
if (!package) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules");
|
||||
const foundModuleNames: string[] = [];
|
||||
|
||||
if (package.dependencies) {
|
||||
addPotentialPackageNames(package.dependencies, modulePrefix, foundModuleNames);
|
||||
}
|
||||
if (package.devDependencies) {
|
||||
addPotentialPackageNames(package.devDependencies, modulePrefix, foundModuleNames);
|
||||
}
|
||||
|
||||
forEach(foundModuleNames, (moduleName) => {
|
||||
const moduleDir = combinePaths(nodeModulesDir, moduleName);
|
||||
result.push({
|
||||
moduleName,
|
||||
moduleDir,
|
||||
canBeImported: moduleCanBeImported(moduleDir)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
function findPackageJsons(currentDir: string): string[] {
|
||||
const paths: string[] = [];
|
||||
let currentConfigPath: string;
|
||||
while (true) {
|
||||
currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json");
|
||||
if (currentConfigPath) {
|
||||
paths.push(currentConfigPath);
|
||||
|
||||
currentDir = getDirectoryPath(currentConfigPath);
|
||||
const parent = getDirectoryPath(currentDir);
|
||||
if (currentDir === parent) {
|
||||
break;
|
||||
}
|
||||
currentDir = parent;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
function tryReadingPackageJson(filePath: string) {
|
||||
try {
|
||||
const fileText = host.readFile(filePath);
|
||||
return JSON.parse(fileText);
|
||||
}
|
||||
catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function addPotentialPackageNames(dependencies: any, prefix: string, result: string[]) {
|
||||
for (const dep in dependencies) {
|
||||
if (dependencies.hasOwnProperty(dep) && (!prefix || startsWith(dep, prefix))) {
|
||||
result.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A module can be imported by name alone if one of the following is true:
|
||||
* It defines the "typings" property in its package.json
|
||||
* The module has a "main" export and an index.d.ts file
|
||||
* The module has an index.ts
|
||||
*/
|
||||
function moduleCanBeImported(modulePath: string): boolean {
|
||||
const packagePath = combinePaths(modulePath, "package.json");
|
||||
|
||||
let hasMainExport = false;
|
||||
if (host.fileExists(packagePath)) {
|
||||
const package = tryReadingPackageJson(packagePath);
|
||||
if (package) {
|
||||
if (package.typings) {
|
||||
return true;
|
||||
}
|
||||
hasMainExport = !!package.main;
|
||||
}
|
||||
}
|
||||
|
||||
hasMainExport = hasMainExport || host.fileExists(combinePaths(modulePath, "index.js"));
|
||||
|
||||
return (hasMainExport && host.fileExists(combinePaths(modulePath, "index.d.ts"))) || host.fileExists(combinePaths(modulePath, "index.ts"));
|
||||
}
|
||||
}
|
||||
|
||||
function createCompletionEntryForModule(name: string, kind: string): CompletionEntry {
|
||||
return {
|
||||
name,
|
||||
kind,
|
||||
kindModifiers: ScriptElementKindModifier.none,
|
||||
sortText: name
|
||||
}
|
||||
}
|
||||
|
||||
function getCompletionEntryDetails(fileName: string, position: number, entryName: string): CompletionEntryDetails {
|
||||
synchronizeHostData();
|
||||
|
||||
|
||||
@ -6,12 +6,6 @@ namespace ts {
|
||||
list: Node;
|
||||
}
|
||||
|
||||
export interface VisibleModuleInfo {
|
||||
moduleName: string;
|
||||
moduleDir: string;
|
||||
canBeImported: boolean;
|
||||
}
|
||||
|
||||
export function getLineStartPositionForPosition(position: number, sourceFile: SourceFile): number {
|
||||
const lineStarts = sourceFile.getLineStarts();
|
||||
const line = sourceFile.getLineAndCharacterOfPosition(position).line;
|
||||
@ -933,101 +927,4 @@ namespace ts {
|
||||
}
|
||||
return ensureScriptKind(fileName, scriptKind);
|
||||
}
|
||||
|
||||
export function enumerateNodeModulesVisibleToScript(host: LanguageServiceHost, scriptPath: string, modulePrefix?: string) {
|
||||
const result: VisibleModuleInfo[] = [];
|
||||
findPackageJsons(scriptPath).forEach((packageJson) => {
|
||||
const package = tryReadingPackageJson(packageJson);
|
||||
if (!package) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeModulesDir = combinePaths(getDirectoryPath(packageJson), "node_modules");
|
||||
const foundModuleNames: string[] = [];
|
||||
|
||||
if (package.dependencies) {
|
||||
addPotentialPackageNames(package.dependencies, modulePrefix, foundModuleNames);
|
||||
}
|
||||
if (package.devDependencies) {
|
||||
addPotentialPackageNames(package.devDependencies, modulePrefix, foundModuleNames);
|
||||
}
|
||||
|
||||
forEach(foundModuleNames, (moduleName) => {
|
||||
const moduleDir = combinePaths(nodeModulesDir, moduleName);
|
||||
result.push({
|
||||
moduleName,
|
||||
moduleDir,
|
||||
canBeImported: moduleCanBeImported(moduleDir)
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return result;
|
||||
|
||||
function findPackageJsons(currentDir: string): string[] {
|
||||
const paths: string[] = [];
|
||||
let currentConfigPath: string;
|
||||
while (true) {
|
||||
currentConfigPath = findConfigFile(currentDir, (f) => host.fileExists(f), "package.json");
|
||||
if (currentConfigPath) {
|
||||
paths.push(currentConfigPath);
|
||||
|
||||
currentDir = getDirectoryPath(currentConfigPath);
|
||||
const parent = getDirectoryPath(currentDir);
|
||||
if (currentDir === parent) {
|
||||
break;
|
||||
}
|
||||
currentDir = parent;
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
function tryReadingPackageJson(filePath: string) {
|
||||
try {
|
||||
const fileText = host.readFile(filePath);
|
||||
return JSON.parse(fileText);
|
||||
}
|
||||
catch (e) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function addPotentialPackageNames(dependencies: any, prefix: string, result: string[]) {
|
||||
for (const dep in dependencies) {
|
||||
if (dependencies.hasOwnProperty(dep) && (!prefix || startsWith(dep, prefix))) {
|
||||
result.push(dep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* A module can be imported by name alone if one of the following is true:
|
||||
* It defines the "typings" property in its package.json
|
||||
* The module has a "main" export and an index.d.ts file
|
||||
* The module has an index.ts
|
||||
*/
|
||||
function moduleCanBeImported(modulePath: string): boolean {
|
||||
const packagePath = combinePaths(modulePath, "package.json");
|
||||
|
||||
let hasMainExport = false;
|
||||
if (host.fileExists(packagePath)) {
|
||||
const package = tryReadingPackageJson(packagePath);
|
||||
if (package) {
|
||||
if (package.typings) {
|
||||
return true;
|
||||
}
|
||||
hasMainExport = !!package.main;
|
||||
}
|
||||
}
|
||||
|
||||
hasMainExport = hasMainExport || host.fileExists(combinePaths(modulePath, "index.js"));
|
||||
|
||||
return (hasMainExport && host.fileExists(combinePaths(modulePath, "index.d.ts"))) || host.fileExists(combinePaths(modulePath, "index.ts"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
// @baseUrl: tests/cases/fourslash/modules
|
||||
|
||||
// @Filename: tests/test0.ts
|
||||
//// import * as foo1 from "mod/*import_as0*/
|
||||
//// import foo2 = require("mod/*import_equals0*/
|
||||
//// var foo3 = require("mod/*require0*/
|
||||
|
||||
// @Filename: modules/module.ts
|
||||
//// export var x = 5;
|
||||
|
||||
// @Filename: package.json
|
||||
//// { "dependencies": { "module-from-node": "latest" } }
|
||||
// @Filename: node_modules/module-from-node/index.ts
|
||||
//// /*module1*/
|
||||
|
||||
|
||||
|
||||
const kinds = ["import_as", "import_equals", "require"];
|
||||
|
||||
for (const kind of kinds) {
|
||||
goTo.marker(kind + "0");
|
||||
|
||||
verify.completionListContains("module");
|
||||
verify.completionListContains("module-from-node");
|
||||
verify.not.completionListItemsCountIsGreaterThan(2);
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @Filename: tsconfig.json
|
||||
//// {
|
||||
//// "compilerOptions": {
|
||||
//// "baseUrl": "./modules",
|
||||
//// "paths": {
|
||||
//// "*": [
|
||||
//// "prefix/0*/suffix.ts",
|
||||
//// "prefix-only/*",
|
||||
//// "*/suffix-only.ts"
|
||||
//// ]
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
|
||||
|
||||
// @Filename: tests/test0.ts
|
||||
//// import * as foo1 from "0/*import_as0*/
|
||||
//// import foo2 = require("0/*import_equals0*/
|
||||
//// var foo3 = require("0/*require0*/
|
||||
|
||||
//// import * as foo1 from "1/*import_as1*/
|
||||
//// import foo2 = require("1/*import_equals1*/
|
||||
//// var foo3 = require("1/*require1*/
|
||||
|
||||
//// import * as foo1 from "2/*import_as2*/
|
||||
//// import foo2 = require("2/*import_equals2*/
|
||||
//// var foo3 = require("2/*require2*/
|
||||
|
||||
|
||||
// @Filename: modules/prefix/00test/suffix.ts
|
||||
//// export var x = 5;
|
||||
|
||||
// @Filename: modules/prefix-only/1test.ts
|
||||
//// export var y = 5;
|
||||
|
||||
// @Filename: modules/2test/suffix-only.ts
|
||||
//// export var z = 5;
|
||||
|
||||
|
||||
const kinds = ["import_as", "import_equals", "require"];
|
||||
|
||||
for (const kind of kinds) {
|
||||
goTo.marker(kind + "0");
|
||||
verify.completionListContains("0test");
|
||||
|
||||
goTo.marker(kind + "1");
|
||||
verify.completionListContains("1test");
|
||||
|
||||
goTo.marker(kind + "2");
|
||||
verify.completionListContains("2test");
|
||||
}
|
||||
@ -0,0 +1,34 @@
|
||||
/// <reference path='fourslash.ts' />
|
||||
|
||||
// @Filename: tsconfig.json
|
||||
//// {
|
||||
//// "compilerOptions": {
|
||||
//// "baseUrl": "./modules",
|
||||
//// "paths": {
|
||||
//// "module1": ["some/path/whatever.ts"],
|
||||
//// "module2": ["some/other/path.ts"]
|
||||
//// }
|
||||
//// }
|
||||
//// }
|
||||
|
||||
|
||||
// @Filename: tests/test0.ts
|
||||
//// import * as foo1 from "m/*import_as0*/
|
||||
//// import foo2 = require("m/*import_equals0*/
|
||||
//// var foo3 = require("m/*require0*/
|
||||
|
||||
// @Filename: some/path/whatever.ts
|
||||
//// export var x = 9;
|
||||
|
||||
// @Filename: some/other/path.ts
|
||||
//// export var y = 10;
|
||||
|
||||
|
||||
const kinds = ["import_as", "import_equals", "require"];
|
||||
|
||||
for (const kind of kinds) {
|
||||
goTo.marker(kind + "0");
|
||||
verify.completionListContains("module1");
|
||||
verify.completionListContains("module2");
|
||||
verify.not.completionListItemsCountIsGreaterThan(2);
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user