@ts-ast-parser/core
Version:
Reflects a simplified version of the TypeScript AST for generating documentation
378 lines • 12.2 kB
JavaScript
import { knownLibFiles } from './known-lib-files.js';
import { isBrowser } from './is-browser.js';
import ts from 'typescript';
// The root directory of the in memory file system
const ROOT_DIR = 'root';
// The separator used in the in memory file system
const SEP = '/';
var FSNodeType;
(function (FSNodeType) {
FSNodeType[FSNodeType["File"] = 0] = "File";
FSNodeType[FSNodeType["Directory"] = 1] = "Directory";
})(FSNodeType || (FSNodeType = {}));
/**
* System interface to interact with an in memory file system.
*
* All interaction of the TypeScript compiler with the operating system goes
* through a System interface.
*
* This is a very simplistic approach. For complex use cases,
* this may not be enough.
*/
export class InMemorySystem {
constructor(files) {
Object.defineProperty(this, "newLine", {
enumerable: true,
configurable: true,
writable: true,
value: '\n'
});
Object.defineProperty(this, "useCaseSensitiveFileNames", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
Object.defineProperty(this, "args", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
Object.defineProperty(this, "_fs", {
enumerable: true,
configurable: true,
writable: true,
value: {
type: FSNodeType.Directory,
name: ROOT_DIR,
children: [],
parent: null,
}
});
const entries = files?.entries() ?? [];
for (const [name, data] of entries) {
this.writeFile(name, data);
}
}
static async create(files) {
const system = new InMemorySystem(files);
if (isBrowser) {
await system._writeLibFilesFromCDN(ts.version);
}
else {
await system._writeLibFilesFromNodeModules();
}
return system;
}
/**
* This method is not implemented and calling it, will throw
* an exception
*
* @param _content
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
write(_content) {
throw new Error('Method \'write\' is not supported.');
}
/**
* Reads the data encoded inside a file
*
* @param path
*/
readFile(path) {
const absolutePath = this.getAbsolutePath(path);
const node = this._get(absolutePath);
if (node?.type === FSNodeType.File) {
return node.data;
}
return undefined;
}
writeFile(path, data) {
const absolutePath = this.getAbsolutePath(path);
this._setFile(absolutePath, data);
}
resolvePath(path) {
return this.getAbsolutePath(path);
}
/**
* Checks whether the file exists
*
* @param path
* @returns True if the file exists, otherwise false
*/
fileExists(path) {
const absolutePath = this.getAbsolutePath(path);
const node = this._get(absolutePath);
return node?.type === FSNodeType.File;
}
directoryExists(path) {
const absolutePath = this.getAbsolutePath(path);
const node = this._get(absolutePath);
return node?.type === FSNodeType.Directory;
}
createDirectory(path) {
const absolutePath = this.getAbsolutePath(path);
this._setDirectory(absolutePath);
}
/**
* This method is not implemented and calling it, will throw
* an exception
*/
getExecutingFilePath() {
throw new Error('Method \'getExecutingFilePath\' is not supported.');
}
/**
* The root directory name
*/
getCurrentDirectory() {
return ROOT_DIR;
}
/**
* Returns the directory names (not the absolute path)
*
* @param path - The path from where to search
*/
getDirectories(path) {
const absolutePath = this.getAbsolutePath(path);
const node = this._get(absolutePath);
return node?.type === FSNodeType.Directory
? node.children.filter(d => d.type === FSNodeType.Directory).map(d => d.name)
: [];
}
readDirectory(path, extensions) {
const absolutePath = this.getAbsolutePath(path);
const node = this._get(absolutePath);
if (!node || node.type === FSNodeType.File) {
return [];
}
const basePath = this._getPath(node);
const fileNames = node.children.filter(f => f.type === FSNodeType.File).map(f => this.join(basePath, f.name));
if (!extensions) {
return fileNames;
}
return fileNames.filter(f => extensions.some(ext => f.endsWith(ext)));
}
deleteFile(path) {
const absolutePath = this.getAbsolutePath(path);
const node = this._get(absolutePath);
if (!node) {
return;
}
if (node.type === FSNodeType.Directory) {
throw new Error(`Unable to delete the file "${absolutePath}" because it's a directory.`);
}
const index = node.parent.children.findIndex(c => c.name === node.name);
if (index < -1) {
return;
}
node.parent.children.splice(index, 1);
}
/**
* This method is not implemented and calling it, will throw
* an exception
*/
exit() {
throw new Error('Method \'exit\' is not supported.');
}
/**
* Normalizes the path based on the OS and makes it
* relative to the current working directory.
*
* @param path
*/
normalizePath(path) {
const absolutePath = this.getAbsolutePath(path);
return absolutePath.split(SEP).slice(1).join(SEP);
}
/**
* Returns the path of the directory
*
* @param path
*/
getDirectoryName(path) {
return this.getAbsolutePath(path).split(SEP).slice(0, -1).join(SEP);
}
/**
* Returns a string with the filename portion of the path
*
* @param path
*/
getBaseName(path) {
const absolutePath = this.getAbsolutePath(path);
const node = this._get(absolutePath);
if (!node || node.type === FSNodeType.Directory) {
return '';
}
return node.name;
}
/**
* Joins the segments using the path separator of the OS/Browser
*
* @param segments
*/
join(...segments) {
return segments.join(SEP);
}
/**
* Checks if the path is an absolute path. An absolute
* path is a path that starts with the ROOT directory.
*
* @param path
* @returns True if the path is absolute
*/
isAbsolute(path) {
return path.startsWith(ROOT_DIR);
}
realpath(path) {
return this.getAbsolutePath(path);
}
/**
* Transforms the path to an absolute path.
*
* It imposes some restrictions about how path are handled to simplify things.
*
* The following restrictions will apply:
*
* - No directories with only dots in its name
* - Paths are converted to lowercase
* - In the name of the file you can have dots
*
* A few examples to show how the functions transforms the path:
*
* - "./foo.js" -> "<ROOT_DIR>/foo.js"
* - "." -> "<ROOT_DIR>"
* - "/foo.js" -> "<ROOT_DIR>/foo.js"
* - "foo.js" -> "<ROOT_DIR>/foo.js"
* - "Bar/foo.js" or "./Bar/foo.js" -> "<ROOT_DIR>/bar/foo.js"
*
* @param path
*/
getAbsolutePath(path) {
// Convert segment to lowercase and remove dots
const segments = path
.toLowerCase()
.split(SEP)
.filter(segment => !!segment && !/^\.+$/.test(segment));
if (segments[0] === ROOT_DIR) {
return segments.join(SEP);
}
return [ROOT_DIR, ...segments].join(SEP);
}
async _writeLibFilesFromNodeModules() {
const m = await import('./get-lib-files-from-node.js');
const libFiles = m.getLibFilesFromNode();
const typeFiles = m.getTypesFromNode();
for (const [name, content] of libFiles.entries()) {
this.writeFile(name, content);
}
for (const [name, content] of typeFiles.entries()) {
this.writeFile(name, content);
}
}
_writeLibFilesFromCDN(version) {
const cdn = `https://typescript.azureedge.net/cdn/${version}/typescript/lib/`;
const promises = [];
for (const lib of knownLibFiles) {
const promise = fetch(cdn + lib)
.then(resp => resp.text())
.then(text => this.writeFile(lib, text))
.catch(err => console.error(err));
promises.push(promise);
}
return Promise.all(promises);
}
_get(absolutePath) {
const segments = absolutePath.split(SEP).slice(1); // The first directory is ROOT
if (!segments.length) {
return this._fs;
}
let node = this._fs;
for (const segment of segments) {
if (!node) {
break;
}
// There are still segments to process and
// we are in a file node
if (node.type === FSNodeType.File) {
node = undefined;
break;
}
node = node.children.find(c => c.name === segment);
}
return node;
}
_setFile(absolutePath, data) {
const segments = absolutePath.split(SEP).slice(1); // The first directory is ROOT
const parentDirectories = segments.slice(0, -1);
const newFileName = segments[segments.length - 1] ?? '';
if (!newFileName) {
throw new Error(`Empty file name found when trying to write to: "${absolutePath}".`);
}
let node = this._fs;
for (const directoryName of parentDirectories) {
const child = node.children.find(c => c.name === directoryName);
if (child?.type === FSNodeType.File) {
throw new Error(`Expected a directory but found a file when trying to write to: "${absolutePath}".`);
}
if (child?.type === FSNodeType.Directory) {
node = child;
continue;
}
const newChild = {
type: FSNodeType.Directory,
name: directoryName,
children: [],
parent: node,
};
node.children.push(newChild);
node = newChild;
}
const existingFileNode = node.children.find((f) => {
return f.name === newFileName && f.type === FSNodeType.File;
});
if (existingFileNode) {
existingFileNode.data = data;
return;
}
const newFileNode = {
data,
type: FSNodeType.File,
name: newFileName,
parent: node,
};
node.children.push(newFileNode);
}
_setDirectory(absolutePath) {
const segments = absolutePath.split(SEP).slice(1); // The first directory is ROOT
let node = this._fs;
for (const segment of segments) {
const child = node.children.find(c => c.name === segment);
if (child?.type === FSNodeType.File) {
throw new Error(`Expected a directory but found a file when trying to write to: "${absolutePath}".`);
}
if (child?.type === FSNodeType.Directory) {
node = child;
continue;
}
const newChild = {
type: FSNodeType.Directory,
name: segment,
children: [],
parent: node,
};
node.children.push(newChild);
node = newChild;
}
}
_getPath(node) {
let curr = node;
const path = [];
while (curr) {
path.push(curr.name);
curr = curr.parent;
}
return path.reverse().join(SEP);
}
}
//# sourceMappingURL=in-memory-system.js.map