Skip to content

Commit b538537

Browse files
committed
feat: #7 - Ability to import files from tsconfig.
BREAKING CHANGE: Files are added based on the tsconfig by default. `getCompilerOptionsFromTsConfig` now returns an object that includes the diagnostics.
1 parent e8c2339 commit b538537

21 files changed

+383
-99
lines changed

src/TsSimpleAst.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as errors from "./errors";
44
import * as compiler from "./compiler";
55
import * as factories from "./factories";
66
import {SourceFileStructure} from "./structures";
7-
import {getCompilerOptionsFromTsConfig, FileUtils, ArrayUtils} from "./utils";
7+
import {getInfoFromTsConfig, TsConfigInfo, FileUtils, ArrayUtils} from "./utils";
88
import {DefaultFileSystemHost, VirtualFileSystemHost, FileSystemHost, Directory} from "./fileSystem";
99
import {ManipulationSettings, ManipulationSettingsContainer} from "./ManipulationSettings";
1010
import {GlobalContainer} from "./GlobalContainer";
@@ -14,6 +14,8 @@ export interface Options {
1414
compilerOptions?: ts.CompilerOptions;
1515
/** File path to the tsconfig.json file */
1616
tsConfigFilePath?: string;
17+
/** Whether to add the source files from the specified tsconfig.json or not. Defaults to true. */
18+
addFilesFromTsConfig?: boolean;
1719
/** Manipulation settings */
1820
manipulationSettings?: Partial<ManipulationSettings>;
1921
/** Whether to use a virtual file system. */
@@ -33,16 +35,33 @@ export class TsSimpleAst {
3335
* @param fileSystem - Optional file system host. Useful for mocking access to the file system.
3436
*/
3537
constructor(options: Options = {}, fileSystem?: FileSystemHost) {
38+
// setup file system
3639
if (fileSystem != null && options.useVirtualFileSystem)
3740
throw new errors.InvalidOperationError("Cannot provide a file system when specifying to use a virtual file system.");
3841
else if (options.useVirtualFileSystem)
3942
fileSystem = new VirtualFileSystemHost();
4043
else if (fileSystem == null)
4144
fileSystem = new DefaultFileSystemHost();
4245

43-
this.global = new GlobalContainer(fileSystem, getCompilerOptionsFromOptions(options, fileSystem), { createLanguageService: true });
46+
// get tsconfig info
47+
const tsConfigInfo = getTsConfigInfo();
48+
49+
// setup global container
50+
this.global = new GlobalContainer(fileSystem, getCompilerOptions(options, tsConfigInfo), { createLanguageService: true });
51+
52+
// initialize manipulation settings
4453
if (options.manipulationSettings != null)
4554
this.global.manipulationSettings.set(options.manipulationSettings);
55+
56+
// add any file paths from the tsconfig if necessary
57+
if (tsConfigInfo != null && tsConfigInfo.filePaths != null)
58+
tsConfigInfo.filePaths.forEach(filePath => this.addExistingSourceFile(filePath));
59+
60+
function getTsConfigInfo() {
61+
if (options.tsConfigFilePath == null)
62+
return undefined;
63+
return getInfoFromTsConfig(options.tsConfigFilePath, fileSystem!, { shouldGetFilePaths: options.addFilesFromTsConfig !== false });
64+
}
4665
}
4766

4867
/** Gets the manipulation settings. */
@@ -161,6 +180,19 @@ export class TsSimpleAst {
161180
return sourceFile;
162181
}
163182

183+
/**
184+
* Adds all the source files from the specified tsconfig.json.
185+
*
186+
* Note that this is done by default when specifying a tsconfig file in the constructor and not explicitly setting the
187+
* addFilesFromTsConfig option to false.
188+
* @param tsConfigFilePath - File path to the tsconfig.json file.
189+
*/
190+
addSourceFilesFromTsConfig(tsConfigFilePath: string) {
191+
const info = getInfoFromTsConfig(tsConfigFilePath, this.global.fileSystem, { shouldGetFilePaths: true });
192+
for (const filePath of info.filePaths!)
193+
this.addExistingSourceFile(filePath);
194+
}
195+
164196
/**
165197
* Creates a source file at the specified file path.
166198
*
@@ -354,7 +386,7 @@ export class TsSimpleAst {
354386
/**
355387
* Gets the compiler options.
356388
*/
357-
getCompilerOptions() {
389+
getCompilerOptions(): ts.CompilerOptions {
358390
// return a copy
359391
return {...this.global.compilerOptions};
360392
}
@@ -378,9 +410,9 @@ export class TsSimpleAst {
378410
}
379411
}
380412

381-
function getCompilerOptionsFromOptions(options: Options, fileSystem: FileSystemHost) {
413+
function getCompilerOptions(options: Options, tsConfigInfo: TsConfigInfo | undefined): ts.CompilerOptions {
382414
return {
383-
...(options.tsConfigFilePath == null ? {} : getCompilerOptionsFromTsConfig(options.tsConfigFilePath, fileSystem)),
415+
...(tsConfigInfo == null ? {} : tsConfigInfo.compilerOptions),
384416
...(options.compilerOptions || {}) as ts.CompilerOptions
385417
};
386418
}

src/compiler/tools/results/Diagnostic.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import {DiagnosticMessageChain} from "./DiagnosticMessageChain";
88
*/
99
export class Diagnostic {
1010
/** @internal */
11-
readonly global: GlobalContainer;
11+
readonly global: GlobalContainer | undefined;
1212
/** @internal */
1313
readonly _compilerObject: ts.Diagnostic;
1414

1515
/** @internal */
16-
constructor(global: GlobalContainer, compilerObject: ts.Diagnostic) {
16+
constructor(global: GlobalContainer | undefined, compilerObject: ts.Diagnostic) {
1717
this.global = global;
1818
this._compilerObject = compilerObject;
1919
}
@@ -29,6 +29,8 @@ export class Diagnostic {
2929
* Gets the source file.
3030
*/
3131
getSourceFile(): SourceFile | undefined {
32+
if (this.global == null)
33+
return undefined;
3234
const file = this.compilerObject.file;
3335
return file == null ? undefined : this.global.compilerFactory.getSourceFile(file);
3436
}
@@ -41,7 +43,7 @@ export class Diagnostic {
4143
if (typeof messageText === "string")
4244
return messageText;
4345

44-
return this.global.compilerFactory.getDiagnosticMessageChain(messageText);
46+
return new DiagnosticMessageChain(messageText);
4547
}
4648

4749
/**

src/compiler/tools/results/DiagnosticMessageChain.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
11
import * as ts from "typescript";
2-
import {GlobalContainer} from "./../../../GlobalContainer";
32

43
/**
54
* Diagnostic message chain.
65
*/
76
export class DiagnosticMessageChain {
8-
/** @internal */
9-
readonly global: GlobalContainer;
107
/** @internal */
118
readonly _compilerObject: ts.DiagnosticMessageChain;
129

1310
/** @internal */
14-
constructor(
15-
global: GlobalContainer,
16-
compilerObject: ts.DiagnosticMessageChain
17-
) {
18-
this.global = global;
11+
constructor(compilerObject: ts.DiagnosticMessageChain) {
1912
this._compilerObject = compilerObject;
2013
}
2114

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

44-
return this.global.compilerFactory.getDiagnosticMessageChain(next);
37+
return new DiagnosticMessageChain(next);
4538
}
4639

4740
/**

src/errors/helpers.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import * as ts from "typescript";
22
import {Node} from "./../compiler";
3+
import {FileSystemHost} from "./../fileSystem";
34
import {ArgumentError} from "./ArgumentError";
45
import {ArgumentTypeError} from "./ArgumentTypeError";
56
import {ArgumentNullOrWhitespaceError} from "./ArgumentNullOrWhitespaceError";
67
import {ArgumentOutOfRangeError} from "./ArgumentOutOfRangeError";
78
import {InvalidOperationError} from "./InvalidOperationError";
89
import {NotImplementedError} from "./NotImplementedError";
10+
import {FileNotFoundError} from "./FileNotFoundError";
911

1012
/**
1113
* Thows if not a type.
@@ -124,3 +126,13 @@ export function throwIfTrue(value: boolean | undefined, errorMessage: string) {
124126
if (value === true)
125127
throw new InvalidOperationError(errorMessage);
126128
}
129+
130+
/**
131+
* Throws if the file does not exist.
132+
* @param fileSystem - File system host.
133+
* @param filePath - File path.
134+
*/
135+
export function throwIfFileNotExists(fileSystem: FileSystemHost, filePath: string) {
136+
if (!fileSystem.fileExistsSync(filePath))
137+
throw new FileNotFoundError(filePath);
138+
}

src/factories/CompilerFactory.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -337,14 +337,6 @@ export class CompilerFactory {
337337
return new compiler.Diagnostic(this.global, diagnostic);
338338
}
339339

340-
/**
341-
* Gets a wrapped diagnostic message chain from a compiler diagnostic message chain.
342-
* @param diagnostic - Compiler diagnostic message chain.
343-
*/
344-
getDiagnosticMessageChain(diagnosticMessageChain: ts.DiagnosticMessageChain): compiler.DiagnosticMessageChain {
345-
return new compiler.DiagnosticMessageChain(this.global, diagnosticMessageChain);
346-
}
347-
348340
/**
349341
* Gets a warpped JS doc tag info from a compiler object.
350342
* @param jsDocTagInfo - Compiler object.

src/fileSystem/DefaultFileSystemHost.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@ export class DefaultFileSystemHost implements FileSystemHost {
2020
fs.unlinkSync(path);
2121
}
2222

23+
isDirectorySync(path: string) {
24+
const stat = this.getStatSync(path);
25+
return stat == null ? false : stat.isDirectory();
26+
}
27+
28+
isFileSync(path: string) {
29+
const stat = this.getStatSync(path);
30+
return stat == null ? false : stat.isFile();
31+
}
32+
33+
private getStatSync(path: string) {
34+
try {
35+
return fs.lstatSync(path);
36+
} catch {
37+
return undefined;
38+
}
39+
}
40+
41+
readDirSync(dirPath: string) {
42+
return fs.readdirSync(dirPath).map(name => FileUtils.pathJoin(dirPath, name));
43+
}
44+
2345
readFile(filePath: string, encoding = "utf-8") {
2446
return new Promise<string>((resolve, reject) => {
2547
fs.readFile(filePath, encoding, (err, data) => {

src/fileSystem/FileSystemHost.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export interface FileSystemHost {
22
delete(path: string): Promise<void>;
33
deleteSync(path: string): void;
4+
readDirSync(dirPath: string): string[];
45
readFile(filePath: string, encoding?: string): Promise<string>;
56
readFileSync(filePath: string, encoding?: string): string;
67
writeFile(filePath: string, fileText: string): Promise<void>;

src/fileSystem/VirtualFileSystemHost.ts

Lines changed: 69 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
import * as errors from "./../errors";
2-
import {KeyValueCache, FileUtils, createHashSet, StringUtils, ArrayUtils} from "./../utils";
2+
import {KeyValueCache, FileUtils, StringUtils, ArrayUtils} from "./../utils";
33
import {Minimatch} from "minimatch";
44
import {FileSystemHost} from "./FileSystemHost";
55

6+
interface VirtualDirectory {
7+
path: string;
8+
files: KeyValueCache<string, string>;
9+
}
10+
611
export class VirtualFileSystemHost implements FileSystemHost {
7-
private readonly files = new KeyValueCache<string, string>();
8-
private readonly directories = createHashSet<string>();
12+
private readonly directories = new KeyValueCache<string, VirtualDirectory>();
913

1014
constructor() {
11-
this.directories.add("/");
15+
this.getOrCreateDir("/");
1216
}
1317

1418
delete(path: string) {
@@ -19,17 +23,36 @@ export class VirtualFileSystemHost implements FileSystemHost {
1923
deleteSync(path: string) {
2024
path = FileUtils.getStandardizedAbsolutePath(this, path);
2125
if (this.directories.has(path)) {
22-
for (const filePath of ArrayUtils.from(this.files.getKeys())) {
23-
if (StringUtils.startsWith(filePath, path))
24-
this.files.removeByKey(filePath);
25-
}
26-
for (const directoryPath of ArrayUtils.from(this.directories.values())) {
26+
// remove descendant dirs
27+
for (const directoryPath of ArrayUtils.from(this.directories.getKeys())) {
2728
if (StringUtils.startsWith(directoryPath, path))
28-
this.directories.delete(directoryPath);
29+
this.directories.removeByKey(directoryPath);
30+
}
31+
// remove this dir
32+
this.directories.removeByKey(path);
33+
return;
34+
}
35+
36+
const parentDir = this.directories.get(FileUtils.getDirPath(path));
37+
if (parentDir != null)
38+
parentDir.files.removeByKey(path);
39+
}
40+
41+
readDirSync(dirPath: string) {
42+
dirPath = FileUtils.getStandardizedAbsolutePath(this, dirPath);
43+
const dir = this.directories.get(dirPath);
44+
if (dir == null)
45+
return [] as string[];
46+
47+
return [...getDirectories(this.directories.getKeys()), ...dir.files.getKeys()];
48+
49+
function* getDirectories(dirPaths: IterableIterator<string>) {
50+
for (const path of dirPaths) {
51+
const parentDir = FileUtils.getDirPath(path);
52+
if (parentDir === dirPath && parentDir !== path)
53+
yield path;
2954
}
3055
}
31-
else
32-
this.files.removeByKey(path);
3356
}
3457

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

4366
readFileSync(filePath: string, encoding = "utf-8") {
4467
filePath = FileUtils.getStandardizedAbsolutePath(this, filePath);
45-
const fileText = this.files.get(filePath);
46-
if (fileText == null)
68+
const parentDir = this.directories.get(FileUtils.getDirPath(filePath));
69+
if (parentDir == null)
70+
throw new errors.FileNotFoundError(filePath);
71+
72+
const fileText = parentDir.files.get(filePath);
73+
if (fileText === undefined)
4774
throw new errors.FileNotFoundError(filePath);
4875
return fileText;
4976
}
@@ -55,8 +82,8 @@ export class VirtualFileSystemHost implements FileSystemHost {
5582

5683
writeFileSync(filePath: string, fileText: string) {
5784
filePath = FileUtils.getStandardizedAbsolutePath(this, filePath);
58-
FileUtils.ensureDirectoryExistsSync(this, FileUtils.getDirPath(filePath));
59-
this.files.set(filePath, fileText);
85+
const dirPath = FileUtils.getDirPath(filePath);
86+
this.getOrCreateDir(dirPath).files.set(filePath, fileText);
6087
}
6188

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

6794
mkdirSync(dirPath: string) {
6895
dirPath = FileUtils.getStandardizedAbsolutePath(this, dirPath);
69-
if (dirPath !== FileUtils.getDirPath(dirPath))
70-
FileUtils.ensureDirectoryExistsSync(this, FileUtils.getDirPath(dirPath));
71-
this.directories.add(dirPath);
96+
this.getOrCreateDir(dirPath);
7297
}
7398

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

78103
fileExistsSync(filePath: string) {
79104
filePath = FileUtils.getStandardizedAbsolutePath(this, filePath);
80-
return this.files.has(filePath);
105+
const dirPath = FileUtils.getDirPath(filePath);
106+
const dir = this.directories.get(dirPath);
107+
if (dir == null)
108+
return false;
109+
110+
return dir.files.has(filePath);
81111
}
82112

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

99129
for (const pattern of patterns) {
100130
const mm = new Minimatch(pattern, { matchBase: true });
101-
for (const filePath of this.files.getKeys()) {
102-
if (mm.match(filePath))
103-
filePaths.push(filePath);
131+
for (const dir of this.directories.getValues()) {
132+
for (const filePath of dir.files.getKeys()) {
133+
if (mm.match(filePath))
134+
filePaths.push(filePath);
135+
}
104136
}
105137
}
106138

107139
return filePaths;
108140
}
141+
142+
private getOrCreateDir(dirPath: string) {
143+
let dir = this.directories.get(dirPath);
144+
145+
if (dir == null) {
146+
dir = { path: dirPath, files: new KeyValueCache<string, string>() };
147+
this.directories.set(dirPath, dir);
148+
const parentDirPath = FileUtils.getDirPath(dirPath);
149+
if (parentDirPath !== dirPath)
150+
this.getOrCreateDir(parentDirPath);
151+
}
152+
153+
return dir;
154+
}
109155
}

src/main.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ export {TsSimpleAst as default} from "./TsSimpleAst";
44
export {FileSystemHost, Directory, DirectoryEmitResult} from "./fileSystem";
55
export * from "./ManipulationSettings";
66
export {createWrappedNode} from "./createWrappedNode";
7-
export {getCompilerOptionsFromTsConfig} from "./utils/getCompilerOptionsFromTsConfig";
7+
export {getCompilerOptionsFromTsConfig} from "./utils/tsconfig/getCompilerOptionsFromTsConfig";
88
export {TypeGuards} from "./utils/TypeGuards";

0 commit comments

Comments
 (0)