Skip to content

Commit

Permalink
feat: #7 - Ability to import files from tsconfig.
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Files are added based on the tsconfig by default. `getCompilerOptionsFromTsConfig` now returns an object that includes the diagnostics.
  • Loading branch information
dsherret committed Jan 6, 2018
1 parent eff1a28 commit 6c094ba
Show file tree
Hide file tree
Showing 21 changed files with 383 additions and 99 deletions.
42 changes: 37 additions & 5 deletions src/TsSimpleAst.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import * as errors from "./errors";
import * as compiler from "./compiler";
import * as factories from "./factories";
import {SourceFileStructure} from "./structures";
import {getCompilerOptionsFromTsConfig, FileUtils, ArrayUtils} from "./utils";
import {getInfoFromTsConfig, TsConfigInfo, FileUtils, ArrayUtils} from "./utils";
import {DefaultFileSystemHost, VirtualFileSystemHost, FileSystemHost, Directory} from "./fileSystem";
import {ManipulationSettings, ManipulationSettingsContainer} from "./ManipulationSettings";
import {GlobalContainer} from "./GlobalContainer";
Expand All @@ -14,6 +14,8 @@ export interface Options {
compilerOptions?: ts.CompilerOptions;
/** File path to the tsconfig.json file */
tsConfigFilePath?: string;
/** Whether to add the source files from the specified tsconfig.json or not. Defaults to true. */
addFilesFromTsConfig?: boolean;
/** Manipulation settings */
manipulationSettings?: Partial<ManipulationSettings>;
/** Whether to use a virtual file system. */
Expand All @@ -33,16 +35,33 @@ export class TsSimpleAst {
* @param fileSystem - Optional file system host. Useful for mocking access to the file system.
*/
constructor(options: Options = {}, fileSystem?: FileSystemHost) {
// setup file system
if (fileSystem != null && options.useVirtualFileSystem)
throw new errors.InvalidOperationError("Cannot provide a file system when specifying to use a virtual file system.");
else if (options.useVirtualFileSystem)
fileSystem = new VirtualFileSystemHost();
else if (fileSystem == null)
fileSystem = new DefaultFileSystemHost();

this.global = new GlobalContainer(fileSystem, getCompilerOptionsFromOptions(options, fileSystem), { createLanguageService: true });
// get tsconfig info
const tsConfigInfo = getTsConfigInfo();

// setup global container
this.global = new GlobalContainer(fileSystem, getCompilerOptions(options, tsConfigInfo), { createLanguageService: true });

// initialize manipulation settings
if (options.manipulationSettings != null)
this.global.manipulationSettings.set(options.manipulationSettings);

// add any file paths from the tsconfig if necessary
if (tsConfigInfo != null && tsConfigInfo.filePaths != null)
tsConfigInfo.filePaths.forEach(filePath => this.addExistingSourceFile(filePath));

function getTsConfigInfo() {
if (options.tsConfigFilePath == null)
return undefined;
return getInfoFromTsConfig(options.tsConfigFilePath, fileSystem!, { shouldGetFilePaths: options.addFilesFromTsConfig !== false });
}
}

/** Gets the manipulation settings. */
Expand Down Expand Up @@ -161,6 +180,19 @@ export class TsSimpleAst {
return sourceFile;
}

/**
* Adds all the source files from the specified tsconfig.json.
*
* Note that this is done by default when specifying a tsconfig file in the constructor and not explicitly setting the
* addFilesFromTsConfig option to false.
* @param tsConfigFilePath - File path to the tsconfig.json file.
*/
addSourceFilesFromTsConfig(tsConfigFilePath: string) {
const info = getInfoFromTsConfig(tsConfigFilePath, this.global.fileSystem, { shouldGetFilePaths: true });
for (const filePath of info.filePaths!)
this.addExistingSourceFile(filePath);
}

/**
* Creates a source file at the specified file path.
*
Expand Down Expand Up @@ -354,7 +386,7 @@ export class TsSimpleAst {
/**
* Gets the compiler options.
*/
getCompilerOptions() {
getCompilerOptions(): ts.CompilerOptions {
// return a copy
return {...this.global.compilerOptions};
}
Expand All @@ -378,9 +410,9 @@ export class TsSimpleAst {
}
}

function getCompilerOptionsFromOptions(options: Options, fileSystem: FileSystemHost) {
function getCompilerOptions(options: Options, tsConfigInfo: TsConfigInfo | undefined): ts.CompilerOptions {
return {
...(options.tsConfigFilePath == null ? {} : getCompilerOptionsFromTsConfig(options.tsConfigFilePath, fileSystem)),
...(tsConfigInfo == null ? {} : tsConfigInfo.compilerOptions),
...(options.compilerOptions || {}) as ts.CompilerOptions
};
}
8 changes: 5 additions & 3 deletions src/compiler/tools/results/Diagnostic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import {DiagnosticMessageChain} from "./DiagnosticMessageChain";
*/
export class Diagnostic {
/** @internal */
readonly global: GlobalContainer;
readonly global: GlobalContainer | undefined;
/** @internal */
readonly _compilerObject: ts.Diagnostic;

/** @internal */
constructor(global: GlobalContainer, compilerObject: ts.Diagnostic) {
constructor(global: GlobalContainer | undefined, compilerObject: ts.Diagnostic) {
this.global = global;
this._compilerObject = compilerObject;
}
Expand All @@ -29,6 +29,8 @@ export class Diagnostic {
* Gets the source file.
*/
getSourceFile(): SourceFile | undefined {
if (this.global == null)
return undefined;
const file = this.compilerObject.file;
return file == null ? undefined : this.global.compilerFactory.getSourceFile(file);
}
Expand All @@ -41,7 +43,7 @@ export class Diagnostic {
if (typeof messageText === "string")
return messageText;

return this.global.compilerFactory.getDiagnosticMessageChain(messageText);
return new DiagnosticMessageChain(messageText);
}

/**
Expand Down
11 changes: 2 additions & 9 deletions src/compiler/tools/results/DiagnosticMessageChain.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,14 @@
import * as ts from "typescript";
import {GlobalContainer} from "./../../../GlobalContainer";

/**
* Diagnostic message chain.
*/
export class DiagnosticMessageChain {
/** @internal */
readonly global: GlobalContainer;
/** @internal */
readonly _compilerObject: ts.DiagnosticMessageChain;

/** @internal */
constructor(
global: GlobalContainer,
compilerObject: ts.DiagnosticMessageChain
) {
this.global = global;
constructor(compilerObject: ts.DiagnosticMessageChain) {
this._compilerObject = compilerObject;
}

Expand All @@ -41,7 +34,7 @@ export class DiagnosticMessageChain {
if (next == null)
return undefined;

return this.global.compilerFactory.getDiagnosticMessageChain(next);
return new DiagnosticMessageChain(next);
}

/**
Expand Down
12 changes: 12 additions & 0 deletions src/errors/helpers.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import * as ts from "typescript";
import {Node} from "./../compiler";
import {FileSystemHost} from "./../fileSystem";
import {ArgumentError} from "./ArgumentError";
import {ArgumentTypeError} from "./ArgumentTypeError";
import {ArgumentNullOrWhitespaceError} from "./ArgumentNullOrWhitespaceError";
import {ArgumentOutOfRangeError} from "./ArgumentOutOfRangeError";
import {InvalidOperationError} from "./InvalidOperationError";
import {NotImplementedError} from "./NotImplementedError";
import {FileNotFoundError} from "./FileNotFoundError";

/**
* Thows if not a type.
Expand Down Expand Up @@ -124,3 +126,13 @@ export function throwIfTrue(value: boolean | undefined, errorMessage: string) {
if (value === true)
throw new InvalidOperationError(errorMessage);
}

/**
* Throws if the file does not exist.
* @param fileSystem - File system host.
* @param filePath - File path.
*/
export function throwIfFileNotExists(fileSystem: FileSystemHost, filePath: string) {
if (!fileSystem.fileExistsSync(filePath))
throw new FileNotFoundError(filePath);
}
8 changes: 0 additions & 8 deletions src/factories/CompilerFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,14 +337,6 @@ export class CompilerFactory {
return new compiler.Diagnostic(this.global, diagnostic);
}

/**
* Gets a wrapped diagnostic message chain from a compiler diagnostic message chain.
* @param diagnostic - Compiler diagnostic message chain.
*/
getDiagnosticMessageChain(diagnosticMessageChain: ts.DiagnosticMessageChain): compiler.DiagnosticMessageChain {
return new compiler.DiagnosticMessageChain(this.global, diagnosticMessageChain);
}

/**
* Gets a warpped JS doc tag info from a compiler object.
* @param jsDocTagInfo - Compiler object.
Expand Down
22 changes: 22 additions & 0 deletions src/fileSystem/DefaultFileSystemHost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,28 @@ export class DefaultFileSystemHost implements FileSystemHost {
fs.unlinkSync(path);
}

isDirectorySync(path: string) {
const stat = this.getStatSync(path);
return stat == null ? false : stat.isDirectory();
}

isFileSync(path: string) {
const stat = this.getStatSync(path);
return stat == null ? false : stat.isFile();
}

private getStatSync(path: string) {
try {
return fs.lstatSync(path);
} catch {
return undefined;
}
}

readDirSync(dirPath: string) {
return fs.readdirSync(dirPath).map(name => FileUtils.pathJoin(dirPath, name));
}

readFile(filePath: string, encoding = "utf-8") {
return new Promise<string>((resolve, reject) => {
fs.readFile(filePath, encoding, (err, data) => {
Expand Down
1 change: 1 addition & 0 deletions src/fileSystem/FileSystemHost.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export interface FileSystemHost {
delete(path: string): Promise<void>;
deleteSync(path: string): void;
readDirSync(dirPath: string): string[];
readFile(filePath: string, encoding?: string): Promise<string>;
readFileSync(filePath: string, encoding?: string): string;
writeFile(filePath: string, fileText: string): Promise<void>;
Expand Down
92 changes: 69 additions & 23 deletions src/fileSystem/VirtualFileSystemHost.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
import * as errors from "./../errors";
import {KeyValueCache, FileUtils, createHashSet, StringUtils, ArrayUtils} from "./../utils";
import {KeyValueCache, FileUtils, StringUtils, ArrayUtils} from "./../utils";
import {Minimatch} from "minimatch";
import {FileSystemHost} from "./FileSystemHost";

interface VirtualDirectory {
path: string;
files: KeyValueCache<string, string>;
}

export class VirtualFileSystemHost implements FileSystemHost {
private readonly files = new KeyValueCache<string, string>();
private readonly directories = createHashSet<string>();
private readonly directories = new KeyValueCache<string, VirtualDirectory>();

constructor() {
this.directories.add("/");
this.getOrCreateDir("/");
}

delete(path: string) {
Expand All @@ -19,17 +23,36 @@ export class VirtualFileSystemHost implements FileSystemHost {
deleteSync(path: string) {
path = FileUtils.getStandardizedAbsolutePath(this, path);
if (this.directories.has(path)) {
for (const filePath of ArrayUtils.from(this.files.getKeys())) {
if (StringUtils.startsWith(filePath, path))
this.files.removeByKey(filePath);
}
for (const directoryPath of ArrayUtils.from(this.directories.values())) {
// remove descendant dirs
for (const directoryPath of ArrayUtils.from(this.directories.getKeys())) {
if (StringUtils.startsWith(directoryPath, path))
this.directories.delete(directoryPath);
this.directories.removeByKey(directoryPath);
}
// remove this dir
this.directories.removeByKey(path);
return;
}

const parentDir = this.directories.get(FileUtils.getDirPath(path));
if (parentDir != null)
parentDir.files.removeByKey(path);
}

readDirSync(dirPath: string) {
dirPath = FileUtils.getStandardizedAbsolutePath(this, dirPath);
const dir = this.directories.get(dirPath);
if (dir == null)
return [] as string[];

return [...getDirectories(this.directories.getKeys()), ...dir.files.getKeys()];

function* getDirectories(dirPaths: IterableIterator<string>) {
for (const path of dirPaths) {
const parentDir = FileUtils.getDirPath(path);
if (parentDir === dirPath && parentDir !== path)
yield path;
}
}
else
this.files.removeByKey(path);
}

readFile(filePath: string, encoding = "utf-8") {
Expand All @@ -42,8 +65,12 @@ export class VirtualFileSystemHost implements FileSystemHost {

readFileSync(filePath: string, encoding = "utf-8") {
filePath = FileUtils.getStandardizedAbsolutePath(this, filePath);
const fileText = this.files.get(filePath);
if (fileText == null)
const parentDir = this.directories.get(FileUtils.getDirPath(filePath));
if (parentDir == null)
throw new errors.FileNotFoundError(filePath);

const fileText = parentDir.files.get(filePath);
if (fileText === undefined)
throw new errors.FileNotFoundError(filePath);
return fileText;
}
Expand All @@ -55,8 +82,8 @@ export class VirtualFileSystemHost implements FileSystemHost {

writeFileSync(filePath: string, fileText: string) {
filePath = FileUtils.getStandardizedAbsolutePath(this, filePath);
FileUtils.ensureDirectoryExistsSync(this, FileUtils.getDirPath(filePath));
this.files.set(filePath, fileText);
const dirPath = FileUtils.getDirPath(filePath);
this.getOrCreateDir(dirPath).files.set(filePath, fileText);
}

mkdir(dirPath: string) {
Expand All @@ -66,9 +93,7 @@ export class VirtualFileSystemHost implements FileSystemHost {

mkdirSync(dirPath: string) {
dirPath = FileUtils.getStandardizedAbsolutePath(this, dirPath);
if (dirPath !== FileUtils.getDirPath(dirPath))
FileUtils.ensureDirectoryExistsSync(this, FileUtils.getDirPath(dirPath));
this.directories.add(dirPath);
this.getOrCreateDir(dirPath);
}

fileExists(filePath: string) {
Expand All @@ -77,7 +102,12 @@ export class VirtualFileSystemHost implements FileSystemHost {

fileExistsSync(filePath: string) {
filePath = FileUtils.getStandardizedAbsolutePath(this, filePath);
return this.files.has(filePath);
const dirPath = FileUtils.getDirPath(filePath);
const dir = this.directories.get(dirPath);
if (dir == null)
return false;

return dir.files.has(filePath);
}

directoryExists(dirPath: string) {
Expand All @@ -98,12 +128,28 @@ export class VirtualFileSystemHost implements FileSystemHost {

for (const pattern of patterns) {
const mm = new Minimatch(pattern, { matchBase: true });
for (const filePath of this.files.getKeys()) {
if (mm.match(filePath))
filePaths.push(filePath);
for (const dir of this.directories.getValues()) {
for (const filePath of dir.files.getKeys()) {
if (mm.match(filePath))
filePaths.push(filePath);
}
}
}

return filePaths;
}

private getOrCreateDir(dirPath: string) {
let dir = this.directories.get(dirPath);

if (dir == null) {
dir = { path: dirPath, files: new KeyValueCache<string, string>() };
this.directories.set(dirPath, dir);
const parentDirPath = FileUtils.getDirPath(dirPath);
if (parentDirPath !== dirPath)
this.getOrCreateDir(parentDirPath);
}

return dir;
}
}
2 changes: 1 addition & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ export {TsSimpleAst as default} from "./TsSimpleAst";
export {FileSystemHost, Directory, DirectoryEmitResult} from "./fileSystem";
export * from "./ManipulationSettings";
export {createWrappedNode} from "./createWrappedNode";
export {getCompilerOptionsFromTsConfig} from "./utils/getCompilerOptionsFromTsConfig";
export {getCompilerOptionsFromTsConfig} from "./utils/tsconfig/getCompilerOptionsFromTsConfig";
export {TypeGuards} from "./utils/TypeGuards";
Loading

0 comments on commit 6c094ba

Please sign in to comment.