Skip to content

Commit 675c64d

Browse files
committed
JS: Prefer extracting file with tsconfig that included it
1 parent 4c4acd5 commit 675c64d

File tree

5 files changed

+96
-27
lines changed

5 files changed

+96
-27
lines changed

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

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ interface LoadCommand {
5858
interface OpenProjectCommand extends LoadCommand {
5959
command: "open-project";
6060
}
61+
interface GetOwnFilesCommand extends LoadCommand {
62+
command: "get-own-files";
63+
}
6164
interface CloseProjectCommand {
6265
command: "close-project";
6366
tsConfig: string;
@@ -78,7 +81,7 @@ interface PrepareFilesCommand {
7881
interface GetMetadataCommand {
7982
command: "get-metadata";
8083
}
81-
type Command = ParseCommand | OpenProjectCommand | CloseProjectCommand
84+
type Command = ParseCommand | OpenProjectCommand | GetOwnFilesCommand | CloseProjectCommand
8285
| GetTypeTableCommand | ResetCommand | QuitCommand | PrepareFilesCommand | GetMetadataCommand;
8386

8487
/** The state to be shared between commands. */
@@ -374,6 +377,7 @@ interface LoadedConfig {
374377
packageEntryPoints: Map<string, string>;
375378
packageJsonFiles: Map<string, string>;
376379
virtualSourceRoot: VirtualSourceRoot;
380+
ownFiles: string[];
377381
}
378382

379383
function loadTsConfig(command: LoadCommand): LoadedConfig {
@@ -428,11 +432,26 @@ function loadTsConfig(command: LoadCommand): LoadedConfig {
428432
};
429433
let config = ts.parseJsonConfigFileContent(tsConfig.config, parseConfigHost, basePath);
430434

431-
return { config, basePath, packageJsonFiles, packageEntryPoints, virtualSourceRoot };
435+
let ownFiles = config.fileNames.map(file => pathlib.resolve(file));
436+
437+
return { config, basePath, packageJsonFiles, packageEntryPoints, virtualSourceRoot, ownFiles };
438+
}
439+
440+
/**
441+
* Returns the list of files included in the given tsconfig.json file's include pattern,
442+
* (not including those only references through imports).
443+
*/
444+
function handleGetFileListCommand(command: GetOwnFilesCommand) {
445+
let { config, ownFiles } = loadTsConfig(command);
446+
447+
console.log(JSON.stringify({
448+
type: "file-list",
449+
ownFiles,
450+
}));
432451
}
433452

434453
function handleOpenProjectCommand(command: OpenProjectCommand) {
435-
let { config, packageEntryPoints, virtualSourceRoot, basePath } = loadTsConfig(command);
454+
let { config, packageEntryPoints, virtualSourceRoot, basePath, ownFiles } = loadTsConfig(command);
436455

437456
let project = new Project(command.tsConfig, config, state.typeTable, packageEntryPoints, virtualSourceRoot);
438457
project.load();
@@ -606,9 +625,14 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
606625
return symbol;
607626
}
608627

628+
// Unlike in the get-own-files command, this command gets all files we can possibly
629+
// extract type information for, including files referenced outside the tsconfig's inclusion pattern.
630+
let allFiles = program.getSourceFiles().map(sf => pathlib.resolve(sf.fileName));
631+
609632
console.log(JSON.stringify({
610633
type: "project-opened",
611-
files: config.fileNames.map(file => pathlib.resolve(file)),
634+
ownFiles,
635+
allFiles,
612636
}));
613637
}
614638

@@ -704,6 +728,9 @@ function runReadLineInterface() {
704728
case "open-project":
705729
handleOpenProjectCommand(req);
706730
break;
731+
case "get-own-files":
732+
handleGetFileListCommand(req);
733+
break;
707734
case "close-project":
708735
handleCloseProjectCommand(req);
709736
break;

javascript/extractor/src/com/semmle/js/extractor/AutoBuild.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -949,6 +949,16 @@ private Set<Path> extractTypeScript(
949949
TypeScriptParser tsParser = extractorState.getTypeScriptParser();
950950
verifyTypeScriptInstallation(extractorState);
951951

952+
// Collect all files included in a tsconfig.json inclusion pattern.
953+
// If a given file is referenced by multiple tsconfig files, we prefer to extract it using
954+
// one that includes it rather than just references it.
955+
Set<File> explicitlyIncludedFiles = new LinkedHashSet<>();
956+
if (tsconfig.size() > 1) { // No prioritization needed if there's only one tsconfig.
957+
for (Path projectPath : tsconfig) {
958+
explicitlyIncludedFiles.addAll(tsParser.getOwnFiles(projectPath.toFile(), deps));
959+
}
960+
}
961+
952962
// Extract TypeScript projects
953963
for (Path projectPath : tsconfig) {
954964
File projectFile = projectPath.toFile();
@@ -958,9 +968,10 @@ private Set<Path> extractTypeScript(
958968
// Extract all files belonging to this project which are also matched
959969
// by our include/exclude filters.
960970
List<Path> typeScriptFiles = new ArrayList<Path>();
961-
for (File sourceFile : project.getSourceFiles()) {
971+
for (File sourceFile : project.getAllFiles()) {
962972
Path sourcePath = sourceFile.toPath();
963973
if (!files.contains(normalizePath(sourcePath))) continue;
974+
if (!project.getOwnFiles().contains(sourceFile) && explicitlyIncludedFiles.contains(sourceFile)) continue;
964975
if (!FileType.TYPESCRIPT.getExtensions().contains(FileUtil.extension(sourcePath))) {
965976
// For the time being, skip non-TypeScript files, even if the TypeScript
966977
// compiler can parse them for us.

javascript/extractor/src/com/semmle/js/extractor/Main.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public void run(String[] args) {
157157
// Extract all files belonging to this project which are also matched
158158
// by our include/exclude filters.
159159
List<File> filesToExtract = new ArrayList<>();
160-
for (File sourceFile : project.getSourceFiles()) {
160+
for (File sourceFile : project.getOwnFiles()) {
161161
if (files.contains(normalizeFile(sourceFile))
162162
&& !extractedFiles.contains(sourceFile.getAbsoluteFile())
163163
&& FileType.TYPESCRIPT.getExtensions().contains(FileUtil.extension(sourceFile))) {
Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
package com.semmle.js.parser;
22

33
import java.io.File;
4-
import java.util.LinkedHashSet;
54
import java.util.Set;
65

76
public class ParsedProject {
87
private final File tsConfigFile;
9-
private final Set<File> sourceFiles = new LinkedHashSet<>();
8+
private final Set<File> ownFiles;
9+
private final Set<File> allFiles;
1010

11-
public ParsedProject(File tsConfigFile) {
11+
public ParsedProject(File tsConfigFile, Set<File> ownFiles, Set<File> allFiles) {
1212
this.tsConfigFile = tsConfigFile;
13+
this.ownFiles = ownFiles;
14+
this.allFiles = allFiles;
1315
}
1416

1517
/** Returns the <tt>tsconfig.json</tt> file that defines this project. */
@@ -18,11 +20,12 @@ public File getTsConfigFile() {
1820
}
1921

2022
/** Absolute paths to the files included in this project. */
21-
public Set<File> getSourceFiles() {
22-
return sourceFiles;
23+
public Set<File> getOwnFiles() {
24+
return allFiles;
2325
}
2426

25-
public void addSourceFile(File file) {
26-
sourceFiles.add(file);
27+
/** Absolute paths to the files included in or referenced by this project. */
28+
public Set<File> getAllFiles() {
29+
return allFiles;
2730
}
2831
}

javascript/extractor/src/com/semmle/js/parser/TypeScriptParser.java

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
import java.util.ArrayList;
1616
import java.util.Arrays;
1717
import java.util.Collections;
18+
import java.util.LinkedHashSet;
1819
import java.util.List;
1920
import java.util.Map;
21+
import java.util.Set;
2022
import java.util.concurrent.TimeUnit;
2123

2224
import com.google.gson.JsonArray;
@@ -488,6 +490,29 @@ private JsonArray mapToArray(Map<String, Path> map) {
488490
return result;
489491
}
490492

493+
private static Set<File> getFilesFromJsonArray(JsonArray array) {
494+
Set<File> files = new LinkedHashSet<>();
495+
for (JsonElement elm : array) {
496+
files.add(new File(elm.getAsString()));
497+
}
498+
return files;
499+
}
500+
501+
/**
502+
* Returns the set of files included by the inclusion pattern in the given tsconfig.json file.
503+
*/
504+
public Set<File> getOwnFiles(File tsConfigFile, DependencyInstallationResult deps) {
505+
JsonObject request = makeLoadCommand("get-own-files", tsConfigFile, deps);
506+
JsonObject response = talkToParserWrapper(request);
507+
try {
508+
checkResponseType(response, "file-list");
509+
return getFilesFromJsonArray(response.get("ownFiles").getAsJsonArray());
510+
} catch (IllegalStateException e) {
511+
throw new CatastrophicError(
512+
"TypeScript parser wrapper sent unexpected response: " + response, e);
513+
}
514+
}
515+
491516
/**
492517
* Opens a new project based on a tsconfig.json file. The compiler will analyze all files in the
493518
* project.
@@ -497,8 +522,23 @@ private JsonArray mapToArray(Map<String, Path> map) {
497522
* <p>Only one project should be opened at once.
498523
*/
499524
public ParsedProject openProject(File tsConfigFile, DependencyInstallationResult deps) {
525+
JsonObject request = makeLoadCommand("open-project", tsConfigFile, deps);
526+
JsonObject response = talkToParserWrapper(request);
527+
try {
528+
checkResponseType(response, "project-opened");
529+
ParsedProject project = new ParsedProject(tsConfigFile,
530+
getFilesFromJsonArray(response.get("ownFiles").getAsJsonArray()),
531+
getFilesFromJsonArray(response.get("allFiles").getAsJsonArray()));
532+
return project;
533+
} catch (IllegalStateException e) {
534+
throw new CatastrophicError(
535+
"TypeScript parser wrapper sent unexpected response: " + response, e);
536+
}
537+
}
538+
539+
private JsonObject makeLoadCommand(String command, File tsConfigFile, DependencyInstallationResult deps) {
500540
JsonObject request = new JsonObject();
501-
request.add("command", new JsonPrimitive("open-project"));
541+
request.add("command", new JsonPrimitive(command));
502542
request.add("tsConfig", new JsonPrimitive(tsConfigFile.getPath()));
503543
request.add("packageEntryPoints", mapToArray(deps.getPackageEntryPoints()));
504544
request.add("packageJsonFiles", mapToArray(deps.getPackageJsonFiles()));
@@ -508,19 +548,7 @@ public ParsedProject openProject(File tsConfigFile, DependencyInstallationResult
508548
request.add("virtualSourceRoot", deps.getVirtualSourceRoot() == null
509549
? JsonNull.INSTANCE
510550
: new JsonPrimitive(deps.getVirtualSourceRoot().toString()));
511-
JsonObject response = talkToParserWrapper(request);
512-
try {
513-
checkResponseType(response, "project-opened");
514-
ParsedProject project = new ParsedProject(tsConfigFile);
515-
JsonArray filesJson = response.get("files").getAsJsonArray();
516-
for (JsonElement elm : filesJson) {
517-
project.addSourceFile(new File(elm.getAsString()));
518-
}
519-
return project;
520-
} catch (IllegalStateException e) {
521-
throw new CatastrophicError(
522-
"TypeScript parser wrapper sent unexpected response: " + response, e);
523-
}
551+
return request;
524552
}
525553

526554
/**

0 commit comments

Comments
 (0)