Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit f5c3aa3

Browse files
committed
JS: Handle types/typings fields in package.json
1 parent 0220ab6 commit f5c3aa3

3 files changed

Lines changed: 123 additions & 12 deletions

File tree

javascript/extractor/lib/typescript/src/common.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { VirtualSourceRoot } from "./virtual_source_root";
99
const packageNameRex = /^(?:@[\w.-]+[/\\]+)?\w[\w.-]*(?=[/\\]|$)/;
1010
const extensions = ['.ts', '.tsx', '.d.ts', '.js', '.jsx'];
1111

12-
function getPackageName(importString: string) {
12+
export function getPackageName(importString: string) {
1313
let packageNameMatch = packageNameRex.exec(importString);
1414
if (packageNameMatch == null) return null;
1515
let packageName = packageNameMatch[0];

javascript/extractor/lib/typescript/src/main.ts

Lines changed: 111 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,11 +95,85 @@ class State {
9595

9696
/** Next response to be delivered. */
9797
public pendingResponse: string = null;
98+
99+
/** Map from `package.json` files to their contents. */
100+
public parsedPackageJson = new Map<string, any>();
101+
102+
/** Map from `package.json` files to the file referenced in its `types` or `typings` field. */
103+
public packageTypings = new Map<string, string | undefined>();
104+
105+
/** Map from file path to the enclosing `package.json` file, if any. Will not traverse outside node_modules. */
106+
public enclosingPackageJson = new Map<string, string>();
98107
}
99108
let state = new State();
100109

101110
const reloadMemoryThresholdMb = getEnvironmentVariable("SEMMLE_TYPESCRIPT_MEMORY_THRESHOLD", Number, 1000);
102111

112+
function getPackageJson(file: string): any {
113+
let cache = state.parsedPackageJson;
114+
if (cache.has(file)) return cache.get(file);
115+
let result = getPackageJsonRaw(file);
116+
cache.set(file, result);
117+
return result;
118+
}
119+
120+
function getPackageJsonRaw(file: string): any {
121+
if (!ts.sys.fileExists(file)) return undefined;
122+
try {
123+
let json = JSON.parse(ts.sys.readFile(file));
124+
if (typeof json !== 'object') return undefined;
125+
return json;
126+
} catch (e) {
127+
return undefined;
128+
}
129+
}
130+
131+
function getPackageTypings(file: string): string | undefined {
132+
let cache = state.packageTypings;
133+
if (cache.has(file)) return cache.get(file);
134+
let result = getPackageTypingsRaw(file);
135+
cache.set(file, result);
136+
return result;
137+
}
138+
139+
function getPackageTypingsRaw(packageJsonFile: string): string | undefined {
140+
let json = getPackageJson(packageJsonFile);
141+
if (json == null) return undefined;
142+
let typings = json.types || json.typings;
143+
if (typeof typings !== 'string') return undefined;
144+
let absolutePath = pathlib.join(pathlib.dirname(packageJsonFile), typings);
145+
if (ts.sys.directoryExists(absolutePath)) {
146+
absolutePath = pathlib.join(absolutePath, 'index.d.ts');
147+
} else if (!absolutePath.endsWith('.ts')) {
148+
absolutePath += '.d.ts';
149+
}
150+
if (!ts.sys.fileExists(absolutePath)) return undefined;
151+
return ts.sys.resolvePath(absolutePath);
152+
}
153+
154+
function getEnclosingPackageJson(file: string): string | undefined {
155+
let cache = state.packageTypings;
156+
if (cache.has(file)) return cache.get(file);
157+
let result = getEnclosingPackageJsonRaw(file);
158+
cache.set(file, result);
159+
return result;
160+
}
161+
162+
function getEnclosingPackageJsonRaw(file: string): string | undefined {
163+
let packageJson = pathlib.join(file, 'package.json');
164+
if (ts.sys.fileExists(packageJson)) {
165+
return packageJson;
166+
}
167+
if (pathlib.basename(file) === 'node_modules') {
168+
return undefined;
169+
}
170+
let dirname = pathlib.dirname(file);
171+
if (dirname.length < file.length) {
172+
return getEnclosingPackageJson(dirname);
173+
}
174+
return undefined;
175+
}
176+
103177
/**
104178
* Debugging method for finding cycles in the TypeScript AST. Should not be used in production.
105179
*
@@ -505,14 +579,18 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
505579
// inverse mapping, nor a way to enumerate all known module names. So we discover all
506580
// modules on the type roots (usually "node_modules/@types" but this is configurable).
507581
let typeRoots = ts.getEffectiveTypeRoots(config.options, {
508-
directoryExists: (path) => fs.existsSync(path),
582+
directoryExists: (path) => ts.sys.directoryExists(path),
509583
getCurrentDirectory: () => basePath,
510584
});
511585

512586
for (let typeRoot of typeRoots || []) {
513-
if (fs.existsSync(typeRoot) && fs.statSync(typeRoot).isDirectory()) {
587+
if (ts.sys.directoryExists(typeRoot)) {
514588
traverseTypeRoot(typeRoot, "");
515589
}
590+
let virtualTypeRoot = virtualSourceRoot.toVirtualPathIfDirectoryExists(typeRoot);
591+
if (virtualTypeRoot != null) {
592+
traverseTypeRoot(virtualTypeRoot, "");
593+
}
516594
}
517595

518596
for (let sourceFile of program.getSourceFiles()) {
@@ -549,22 +627,25 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
549627
if (sourceFile == null) {
550628
continue;
551629
}
552-
addModuleBindingFromRelativePath(sourceFile, importPrefix, child);
630+
let importPath = getImportPathFromFileInFolder(importPrefix, child);
631+
addModuleBindingFromImportPath(sourceFile, importPath);
553632
}
554633
}
555634

635+
function getImportPathFromFileInFolder(folder: string, baseName: string) {
636+
let stem = getStem(baseName);
637+
return (stem === "index")
638+
? folder
639+
: joinModulePath(folder, stem);
640+
}
641+
556642
/**
557643
* Emits module bindings for a module with relative path `folder/baseName`.
558644
*/
559-
function addModuleBindingFromRelativePath(sourceFile: ts.SourceFile, folder: string, baseName: string) {
645+
function addModuleBindingFromImportPath(sourceFile: ts.SourceFile, importPath: string) {
560646
let symbol = typeChecker.getSymbolAtLocation(sourceFile);
561647
if (symbol == null) return; // Happens if the source file is not a module.
562648

563-
let stem = getStem(baseName);
564-
let importPath = (stem === "index")
565-
? folder
566-
: joinModulePath(folder, stem);
567-
568649
let canonicalSymbol = getEffectiveExportTarget(symbol); // Follow `export = X` declarations.
569650
let symbolId = state.typeTable.getSymbolId(canonicalSymbol);
570651

@@ -576,7 +657,7 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
576657
// Note: the `globalExports` map is stored on the original symbol, not the target of `export=`.
577658
if (symbol.globalExports != null) {
578659
symbol.globalExports.forEach((global: ts.Symbol) => {
579-
state.typeTable.addGlobalMapping(symbolId, global.name);
660+
state.typeTable.addGlobalMapping(symbolId, global.name);
580661
});
581662
}
582663
}
@@ -605,11 +686,30 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
605686
let fullPath = sourceFile.fileName;
606687
let index = fullPath.lastIndexOf('/node_modules/');
607688
if (index === -1) return;
689+
608690
let relativePath = fullPath.substring(index + '/node_modules/'.length);
691+
609692
// Ignore node_modules/@types folders here as they are typically handled as type roots.
610693
if (relativePath.startsWith("@types/")) return;
694+
695+
// If the enclosing package has a "typings" field, only add module bindings for that file.
696+
let packageJsonFile = getEnclosingPackageJson(fullPath);
697+
if (packageJsonFile != null) {
698+
let json = getPackageJson(packageJsonFile);
699+
let typings = getPackageTypings(packageJsonFile);
700+
if (json != null && typings != null) {
701+
let name = json.name;
702+
if (typings === fullPath && typeof name === 'string') {
703+
addModuleBindingFromImportPath(sourceFile, name);
704+
} else if (typings != null) {
705+
return; // Typings field prevents access to other files in package.
706+
}
707+
}
708+
}
709+
710+
// Add module bindings relative to package directory.
611711
let { dir, base } = pathlib.parse(relativePath);
612-
addModuleBindingFromRelativePath(sourceFile, dir, base);
712+
addModuleBindingFromImportPath(sourceFile, getImportPathFromFileInFolder(dir, base));
613713
}
614714

615715
/**

javascript/extractor/lib/typescript/src/virtual_source_root.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,15 @@ export class VirtualSourceRoot {
5555
}
5656
return null;
5757
}
58+
59+
/**
60+
* Maps a path under the real source root to the corresponding path in the virtual source root.
61+
*/
62+
public toVirtualPathIfDirectoryExists(path: string) {
63+
let virtualPath = this.toVirtualPath(path);
64+
if (virtualPath != null && ts.sys.directoryExists(virtualPath)) {
65+
return virtualPath;
66+
}
67+
return null;
68+
}
5869
}

0 commit comments

Comments
 (0)