Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 53 additions & 57 deletions src/services/jsTyping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,17 +61,20 @@ namespace ts.JsTyping {
unresolvedImports: ReadonlyArray<string>):
{ cachedTypingPaths: string[], newTypingNames: string[], filesToWatch: string[] } {

// A typing name to typing file path mapping
const inferredTypings = createMap<string>();

if (!typeAcquisition || !typeAcquisition.enable) {
return { cachedTypingPaths: [], newTypingNames: [], filesToWatch: [] };
}

// A typing name to typing file path mapping
const inferredTypings = createMap<string>();

// Only infer typings for .js and .jsx files
fileNames = filter(map(fileNames, normalizePath), f => {
const kind = ensureScriptKind(f, getScriptKindFromFileName(f));
return kind === ScriptKind.JS || kind === ScriptKind.JSX;
fileNames = mapDefined(fileNames, fileName => {
const path = normalizePath(fileName);
const kind = ensureScriptKind(path, getScriptKindFromFileName(path));
if (kind === ScriptKind.JS || kind === ScriptKind.JSX) {
return path;
}
});

if (!safeList) {
Expand All @@ -80,31 +83,29 @@ namespace ts.JsTyping {
}

const filesToWatch: string[] = [];
// Directories to search for package.json, bower.json and other typing information
let searchDirs: string[] = [];
let exclude: string[] = [];

mergeTypings(typeAcquisition.include);
exclude = typeAcquisition.exclude || [];
forEach(typeAcquisition.include, addInferredTyping);
const exclude = typeAcquisition.exclude || [];

const possibleSearchDirs = map(fileNames, getDirectoryPath);
if (projectRootPath) {
possibleSearchDirs.push(projectRootPath);
// Directories to search for package.json, bower.json and other typing information
const possibleSearchDirs = createMap<true>();
for (const f of fileNames) {
possibleSearchDirs.set(getDirectoryPath(f), true);
}
searchDirs = deduplicate(possibleSearchDirs);
for (const searchDir of searchDirs) {
possibleSearchDirs.set(projectRootPath, true);
Copy link
Copy Markdown
Member

@jramsay jramsay Jun 23, 2017

Choose a reason for hiding this comment

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

do we need a null/undefined check for projectRootPath before it is added to the list of possibleSearchDirs?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It doesn't look like that's ever passed in as undefined?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

At one point I believe there were scenarios where this could be undefined in VS (ex: websites, open folder). Not sure if this is still the case though.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Maybe we should add an assert?

possibleSearchDirs.forEach((_true, searchDir) => {
const packageJsonPath = combinePaths(searchDir, "package.json");
getTypingNamesFromJson(packageJsonPath, filesToWatch);

const bowerJsonPath = combinePaths(searchDir, "bower.json");
getTypingNamesFromJson(bowerJsonPath, filesToWatch);

const bowerComponentsPath = combinePaths(searchDir, "bower_components");
getTypingNamesFromPackagesFolder(bowerComponentsPath);
getTypingNamesFromPackagesFolder(bowerComponentsPath, filesToWatch);

const nodeModulesPath = combinePaths(searchDir, "node_modules");
getTypingNamesFromPackagesFolder(nodeModulesPath);
}
getTypingNamesFromPackagesFolder(nodeModulesPath, filesToWatch);
});
getTypingNamesFromSourceFileNames(fileNames);

// add typings for unresolved imports
Expand Down Expand Up @@ -140,41 +141,33 @@ namespace ts.JsTyping {
});
return { cachedTypingPaths, newTypingNames, filesToWatch };

/**
* Merge a given list of typingNames to the inferredTypings map
*/
function mergeTypings(typingNames: ReadonlyArray<string>) {
if (!typingNames) {
return;
}

for (const typing of typingNames) {
if (!inferredTypings.has(typing)) {
inferredTypings.set(typing, undefined);
}
function addInferredTyping(typingName: string) {
if (!inferredTypings.has(typingName)) {
inferredTypings.set(typingName, undefined);
}
}

/**
* Get the typing info from common package manager json files like package.json or bower.json
*/
function getTypingNamesFromJson(jsonPath: string, filesToWatch: string[]) {
if (host.fileExists(jsonPath)) {
filesToWatch.push(jsonPath);
}
const result = readConfigFile(jsonPath, (path: string) => host.readFile(path));
const jsonConfig: PackageJson = result.config;
if (jsonConfig.dependencies) {
mergeTypings(getOwnKeys(jsonConfig.dependencies));
}
if (jsonConfig.devDependencies) {
mergeTypings(getOwnKeys(jsonConfig.devDependencies));
}
if (jsonConfig.optionalDependencies) {
mergeTypings(getOwnKeys(jsonConfig.optionalDependencies));
function getTypingNamesFromJson(jsonPath: string, filesToWatch: Push<string>) {
if (!host.fileExists(jsonPath)) {
return;
}
if (jsonConfig.peerDependencies) {
mergeTypings(getOwnKeys(jsonConfig.peerDependencies));

filesToWatch.push(jsonPath);
const jsonConfig: PackageJson = readConfigFile(jsonPath, (path: string) => host.readFile(path)).config;
addInferredTypingsFromKeys(jsonConfig.dependencies);
addInferredTypingsFromKeys(jsonConfig.devDependencies);
addInferredTypingsFromKeys(jsonConfig.optionalDependencies);
addInferredTypingsFromKeys(jsonConfig.peerDependencies);

function addInferredTypingsFromKeys(map: MapLike<string> | undefined): void {
for (const key in map) {
if (ts.hasProperty(map, key)) {
addInferredTyping(key);
}
}
}
}

Expand All @@ -185,33 +178,37 @@ namespace ts.JsTyping {
* @param fileNames are the names for source files in the project
*/
function getTypingNamesFromSourceFileNames(fileNames: string[]) {
const jsFileNames = filter(fileNames, hasJavaScriptFileExtension);
const inferredTypingNames = map(jsFileNames, f => removeFileExtension(getBaseFileName(f.toLowerCase())));
const cleanedTypingNames = map(inferredTypingNames, f => f.replace(/((?:\.|-)min(?=\.|$))|((?:-|\.)\d+)/g, ""));

if (safeList !== EmptySafeList) {
mergeTypings(ts.mapDefined(cleanedTypingNames, f => safeList.get(f)));
for (const j of fileNames) {
if (!hasJavaScriptFileExtension(j)) continue;

const inferredTypingName = removeFileExtension(getBaseFileName(j.toLowerCase()));
const cleanedTypingName = inferredTypingName.replace(/((?:\.|-)min(?=\.|$))|((?:-|\.)\d+)/g, "");
const safe = safeList.get(cleanedTypingName);
if (safe !== undefined) {
addInferredTyping(safe);
}
}
}

const hasJsxFile = forEach(fileNames, f => ensureScriptKind(f, getScriptKindFromFileName(f)) === ScriptKind.JSX);
if (hasJsxFile) {
mergeTypings(["react"]);
addInferredTyping("react");
}
}

/**
* Infer typing names from packages folder (ex: node_module, bower_components)
* @param packagesFolderPath is the path to the packages folder
*/
function getTypingNamesFromPackagesFolder(packagesFolderPath: string) {
function getTypingNamesFromPackagesFolder(packagesFolderPath: string, filesToWatch: Push<string>) {
filesToWatch.push(packagesFolderPath);

// Todo: add support for ModuleResolutionHost too
if (!host.directoryExists(packagesFolderPath)) {
return;
}

const typingNames: string[] = [];
const fileNames = host.readDirectory(packagesFolderPath, [".json"], /*excludes*/ undefined, /*includes*/ undefined, /*depth*/ 2);
for (const fileName of fileNames) {
const normalizedFileName = normalizePath(fileName);
Expand Down Expand Up @@ -240,10 +237,9 @@ namespace ts.JsTyping {
inferredTypings.set(packageJson.name, absolutePath);
}
else {
typingNames.push(packageJson.name);
addInferredTyping(packageJson.name);
}
}
mergeTypings(typingNames);
}

}
Expand Down