2020-03-03 14:53:54 +01:00
|
|
|
|
2020-05-25 15:52:11 +02:00
|
|
|
import * as path from "path"
|
2020-06-01 21:49:28 +02:00
|
|
|
import { getPackage } from "./common"
|
|
|
|
import { Package } from "./package"
|
2020-05-25 15:52:11 +02:00
|
|
|
import { SourceFile, Syntax } from "./ast"
|
|
|
|
import { FastStringMap, assert, isInsideDirectory, stripExtensions } from "./util";
|
2020-03-03 14:53:54 +01:00
|
|
|
|
|
|
|
export class Program {
|
|
|
|
|
2020-05-25 15:52:11 +02:00
|
|
|
private packagesByName = new FastStringMap<string, Package>();
|
|
|
|
|
|
|
|
private sourceFilesByFilePath = new FastStringMap<string, SourceFile>();
|
2020-05-10 23:50:42 +02:00
|
|
|
|
2020-05-23 14:18:20 +02:00
|
|
|
constructor(
|
2020-05-28 14:08:49 +02:00
|
|
|
private pkgs: Package[]
|
2020-05-23 14:18:20 +02:00
|
|
|
) {
|
2020-05-25 11:29:19 +02:00
|
|
|
for (const pkg of pkgs) {
|
2020-05-28 14:08:49 +02:00
|
|
|
if (pkg.name !== null) {
|
|
|
|
this.packagesByName.set(pkg.name, pkg);
|
|
|
|
}
|
|
|
|
for (const sourceFile of pkg.getAllSourceFiles()) {
|
2020-05-25 15:52:11 +02:00
|
|
|
this.sourceFilesByFilePath.set(stripExtensions(sourceFile.span!.file.fullPath), sourceFile);
|
2020-05-25 11:29:19 +02:00
|
|
|
}
|
2020-05-10 11:58:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-23 14:18:20 +02:00
|
|
|
public getAllSourceFiles() {
|
2020-05-25 15:52:11 +02:00
|
|
|
return this.sourceFilesByFilePath.values();
|
|
|
|
}
|
|
|
|
|
|
|
|
public getSourceFile(filepath: string): SourceFile | null {
|
|
|
|
assert(path.isAbsolute(filepath));
|
|
|
|
if (!this.sourceFilesByFilePath.has(filepath)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return this.sourceFilesByFilePath.get(filepath);
|
|
|
|
}
|
|
|
|
|
2020-05-28 14:08:49 +02:00
|
|
|
public getAllPackages(): IterableIterator<Package> {
|
|
|
|
return this.pkgs[Symbol.iterator]();
|
|
|
|
}
|
|
|
|
|
|
|
|
public *getAllGloballyDeclaredSourceFiles(): IterableIterator<SourceFile> {
|
|
|
|
for (const pkg of this.getAllPackages()) {
|
|
|
|
if (pkg.isAutoImported) {
|
|
|
|
const mainLibrarySourceFile = pkg.getMainLibrarySourceFile();
|
|
|
|
if (mainLibrarySourceFile !== null) {
|
|
|
|
yield mainLibrarySourceFile;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-25 15:52:11 +02:00
|
|
|
public getPackageNamed(name: string): Package {
|
|
|
|
return this.packagesByName.get(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
public resolveToSourceFile(importPath: string, fromNode: Syntax): SourceFile | null {
|
|
|
|
let resolvedFilePath: string;
|
|
|
|
if (importPath.startsWith('.')) {
|
|
|
|
const pkg = getPackage(fromNode);
|
|
|
|
resolvedFilePath = path.join(pkg.rootDir, importPath.substring(2));
|
|
|
|
assert(isInsideDirectory(resolvedFilePath, pkg.rootDir));
|
|
|
|
} else {
|
|
|
|
const elements = importPath.split('/');
|
|
|
|
const pkg = this.getPackageNamed(elements[0]);
|
|
|
|
let filename: string;
|
|
|
|
if (elements.length === 1) {
|
|
|
|
filename = 'lib';
|
|
|
|
} else {
|
|
|
|
assert(elements.length > 0);
|
|
|
|
assert(!elements.slice(1).some(element => element.startsWith('.')));
|
|
|
|
filename = elements.slice(1).join(path.sep);
|
|
|
|
}
|
|
|
|
resolvedFilePath = path.join(pkg.rootDir, filename)
|
|
|
|
assert(isInsideDirectory(resolvedFilePath, pkg.rootDir));
|
|
|
|
}
|
|
|
|
return this.getSourceFile(resolvedFilePath);
|
2020-03-03 14:53:54 +01:00
|
|
|
}
|
|
|
|
|
2020-05-23 14:18:20 +02:00
|
|
|
public updateSourceFile(oldSourceFile: SourceFile, newSourceFile: SourceFile): void {
|
2020-05-25 15:52:11 +02:00
|
|
|
if (!this.sourceFilesByFilePath.has(oldSourceFile.span!.file.fullPath)) {
|
2020-05-23 14:18:20 +02:00
|
|
|
throw new Error(`Could not update ${oldSourceFile.span!.file.origPath} because it was not found in this program.`);
|
2020-03-03 14:53:54 +01:00
|
|
|
}
|
2020-05-25 15:52:11 +02:00
|
|
|
this.sourceFilesByFilePath.delete(oldSourceFile.span!.file.fullPath);
|
|
|
|
this.sourceFilesByFilePath.set(newSourceFile.span!.file.fullPath, newSourceFile);
|
2020-03-03 14:53:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|