JS: Prefer extracting file with tsconfig that included it

This commit is contained in:
Asger Feldthaus
2020-06-12 21:49:43 +01:00
parent 4c4acd50bd
commit 675c64d9d4
5 changed files with 96 additions and 27 deletions

View File

@@ -58,6 +58,9 @@ interface LoadCommand {
interface OpenProjectCommand extends LoadCommand {
command: "open-project";
}
interface GetOwnFilesCommand extends LoadCommand {
command: "get-own-files";
}
interface CloseProjectCommand {
command: "close-project";
tsConfig: string;
@@ -78,7 +81,7 @@ interface PrepareFilesCommand {
interface GetMetadataCommand {
command: "get-metadata";
}
type Command = ParseCommand | OpenProjectCommand | CloseProjectCommand
type Command = ParseCommand | OpenProjectCommand | GetOwnFilesCommand | CloseProjectCommand
| GetTypeTableCommand | ResetCommand | QuitCommand | PrepareFilesCommand | GetMetadataCommand;
/** The state to be shared between commands. */
@@ -374,6 +377,7 @@ interface LoadedConfig {
packageEntryPoints: Map<string, string>;
packageJsonFiles: Map<string, string>;
virtualSourceRoot: VirtualSourceRoot;
ownFiles: string[];
}
function loadTsConfig(command: LoadCommand): LoadedConfig {
@@ -428,11 +432,26 @@ function loadTsConfig(command: LoadCommand): LoadedConfig {
};
let config = ts.parseJsonConfigFileContent(tsConfig.config, parseConfigHost, basePath);
return { config, basePath, packageJsonFiles, packageEntryPoints, virtualSourceRoot };
let ownFiles = config.fileNames.map(file => pathlib.resolve(file));
return { config, basePath, packageJsonFiles, packageEntryPoints, virtualSourceRoot, ownFiles };
}
/**
* Returns the list of files included in the given tsconfig.json file's include pattern,
* (not including those only references through imports).
*/
function handleGetFileListCommand(command: GetOwnFilesCommand) {
let { config, ownFiles } = loadTsConfig(command);
console.log(JSON.stringify({
type: "file-list",
ownFiles,
}));
}
function handleOpenProjectCommand(command: OpenProjectCommand) {
let { config, packageEntryPoints, virtualSourceRoot, basePath } = loadTsConfig(command);
let { config, packageEntryPoints, virtualSourceRoot, basePath, ownFiles } = loadTsConfig(command);
let project = new Project(command.tsConfig, config, state.typeTable, packageEntryPoints, virtualSourceRoot);
project.load();
@@ -606,9 +625,14 @@ function handleOpenProjectCommand(command: OpenProjectCommand) {
return symbol;
}
// Unlike in the get-own-files command, this command gets all files we can possibly
// extract type information for, including files referenced outside the tsconfig's inclusion pattern.
let allFiles = program.getSourceFiles().map(sf => pathlib.resolve(sf.fileName));
console.log(JSON.stringify({
type: "project-opened",
files: config.fileNames.map(file => pathlib.resolve(file)),
ownFiles,
allFiles,
}));
}
@@ -704,6 +728,9 @@ function runReadLineInterface() {
case "open-project":
handleOpenProjectCommand(req);
break;
case "get-own-files":
handleGetFileListCommand(req);
break;
case "close-project":
handleCloseProjectCommand(req);
break;

View File

@@ -949,6 +949,16 @@ public class AutoBuild {
TypeScriptParser tsParser = extractorState.getTypeScriptParser();
verifyTypeScriptInstallation(extractorState);
// Collect all files included in a tsconfig.json inclusion pattern.
// If a given file is referenced by multiple tsconfig files, we prefer to extract it using
// one that includes it rather than just references it.
Set<File> explicitlyIncludedFiles = new LinkedHashSet<>();
if (tsconfig.size() > 1) { // No prioritization needed if there's only one tsconfig.
for (Path projectPath : tsconfig) {
explicitlyIncludedFiles.addAll(tsParser.getOwnFiles(projectPath.toFile(), deps));
}
}
// Extract TypeScript projects
for (Path projectPath : tsconfig) {
File projectFile = projectPath.toFile();
@@ -958,9 +968,10 @@ public class AutoBuild {
// Extract all files belonging to this project which are also matched
// by our include/exclude filters.
List<Path> typeScriptFiles = new ArrayList<Path>();
for (File sourceFile : project.getSourceFiles()) {
for (File sourceFile : project.getAllFiles()) {
Path sourcePath = sourceFile.toPath();
if (!files.contains(normalizePath(sourcePath))) continue;
if (!project.getOwnFiles().contains(sourceFile) && explicitlyIncludedFiles.contains(sourceFile)) continue;
if (!FileType.TYPESCRIPT.getExtensions().contains(FileUtil.extension(sourcePath))) {
// For the time being, skip non-TypeScript files, even if the TypeScript
// compiler can parse them for us.

View File

@@ -157,7 +157,7 @@ public class Main {
// Extract all files belonging to this project which are also matched
// by our include/exclude filters.
List<File> filesToExtract = new ArrayList<>();
for (File sourceFile : project.getSourceFiles()) {
for (File sourceFile : project.getOwnFiles()) {
if (files.contains(normalizeFile(sourceFile))
&& !extractedFiles.contains(sourceFile.getAbsoluteFile())
&& FileType.TYPESCRIPT.getExtensions().contains(FileUtil.extension(sourceFile))) {

View File

@@ -1,15 +1,17 @@
package com.semmle.js.parser;
import java.io.File;
import java.util.LinkedHashSet;
import java.util.Set;
public class ParsedProject {
private final File tsConfigFile;
private final Set<File> sourceFiles = new LinkedHashSet<>();
private final Set<File> ownFiles;
private final Set<File> allFiles;
public ParsedProject(File tsConfigFile) {
public ParsedProject(File tsConfigFile, Set<File> ownFiles, Set<File> allFiles) {
this.tsConfigFile = tsConfigFile;
this.ownFiles = ownFiles;
this.allFiles = allFiles;
}
/** Returns the <tt>tsconfig.json</tt> file that defines this project. */
@@ -18,11 +20,12 @@ public class ParsedProject {
}
/** Absolute paths to the files included in this project. */
public Set<File> getSourceFiles() {
return sourceFiles;
public Set<File> getOwnFiles() {
return allFiles;
}
public void addSourceFile(File file) {
sourceFiles.add(file);
/** Absolute paths to the files included in or referenced by this project. */
public Set<File> getAllFiles() {
return allFiles;
}
}

View File

@@ -15,8 +15,10 @@ import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import com.google.gson.JsonArray;
@@ -488,6 +490,29 @@ public class TypeScriptParser {
return result;
}
private static Set<File> getFilesFromJsonArray(JsonArray array) {
Set<File> files = new LinkedHashSet<>();
for (JsonElement elm : array) {
files.add(new File(elm.getAsString()));
}
return files;
}
/**
* Returns the set of files included by the inclusion pattern in the given tsconfig.json file.
*/
public Set<File> getOwnFiles(File tsConfigFile, DependencyInstallationResult deps) {
JsonObject request = makeLoadCommand("get-own-files", tsConfigFile, deps);
JsonObject response = talkToParserWrapper(request);
try {
checkResponseType(response, "file-list");
return getFilesFromJsonArray(response.get("ownFiles").getAsJsonArray());
} catch (IllegalStateException e) {
throw new CatastrophicError(
"TypeScript parser wrapper sent unexpected response: " + response, e);
}
}
/**
* Opens a new project based on a tsconfig.json file. The compiler will analyze all files in the
* project.
@@ -497,8 +522,23 @@ public class TypeScriptParser {
* <p>Only one project should be opened at once.
*/
public ParsedProject openProject(File tsConfigFile, DependencyInstallationResult deps) {
JsonObject request = makeLoadCommand("open-project", tsConfigFile, deps);
JsonObject response = talkToParserWrapper(request);
try {
checkResponseType(response, "project-opened");
ParsedProject project = new ParsedProject(tsConfigFile,
getFilesFromJsonArray(response.get("ownFiles").getAsJsonArray()),
getFilesFromJsonArray(response.get("allFiles").getAsJsonArray()));
return project;
} catch (IllegalStateException e) {
throw new CatastrophicError(
"TypeScript parser wrapper sent unexpected response: " + response, e);
}
}
private JsonObject makeLoadCommand(String command, File tsConfigFile, DependencyInstallationResult deps) {
JsonObject request = new JsonObject();
request.add("command", new JsonPrimitive("open-project"));
request.add("command", new JsonPrimitive(command));
request.add("tsConfig", new JsonPrimitive(tsConfigFile.getPath()));
request.add("packageEntryPoints", mapToArray(deps.getPackageEntryPoints()));
request.add("packageJsonFiles", mapToArray(deps.getPackageJsonFiles()));
@@ -508,19 +548,7 @@ public class TypeScriptParser {
request.add("virtualSourceRoot", deps.getVirtualSourceRoot() == null
? JsonNull.INSTANCE
: new JsonPrimitive(deps.getVirtualSourceRoot().toString()));
JsonObject response = talkToParserWrapper(request);
try {
checkResponseType(response, "project-opened");
ParsedProject project = new ParsedProject(tsConfigFile);
JsonArray filesJson = response.get("files").getAsJsonArray();
for (JsonElement elm : filesJson) {
project.addSourceFile(new File(elm.getAsString()));
}
return project;
} catch (IllegalStateException e) {
throw new CatastrophicError(
"TypeScript parser wrapper sent unexpected response: " + response, e);
}
return request;
}
/**