Skip to content

Commit

Permalink
feat: #657 - Ability to specify the script kind when creating a sourc…
Browse files Browse the repository at this point in the history
…e file.
  • Loading branch information
dsherret committed Jul 3, 2019
1 parent e92ea8d commit cb22219
Show file tree
Hide file tree
Showing 11 changed files with 100 additions and 32 deletions.
15 changes: 14 additions & 1 deletion lib/ts-morph.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -285,13 +285,18 @@ export interface DirectoryCopyOptions extends SourceFileCopyOptions {
}

export declare class DirectoryEmitResult {
private readonly _emitSkipped;
private readonly _skippedFilePaths;
private readonly _outputFilePaths;
private constructor();
/**
* Gets if the emit was skipped.
* @deprecated This is being deprecated in favour of getSkippedFilePaths().
*/
getEmitSkipped(): boolean;
/**
* Gets a collections of skipped file paths.
*/
getSkippedFilePaths(): string[];
/**
* Gets the output file paths.
*/
Expand Down Expand Up @@ -628,6 +633,10 @@ export interface SourceFileCreateOptions {
* @remarks When false, the method will throw when a file exists.
*/
overwrite?: boolean;
/**
* Specifies the script kind of the source file.
*/
scriptKind?: ScriptKind;
}

export declare type Constructor<T> = new (...args: any[]) => T;
Expand Down Expand Up @@ -7805,6 +7814,10 @@ export declare class SourceFile extends SourceFileBase<ts.SourceFile> {
* Gets the language variant of the source file.
*/
getLanguageVariant(): LanguageVariant;
/**
* Gets the script kind of the source file.
*/
getScriptKind(): ScriptKind;
/**
* Gets if this is a declaration file.
*/
Expand Down
6 changes: 5 additions & 1 deletion src/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ProjectContext } from "./ProjectContext";
import { CompilerOptionsContainer, ManipulationSettings, ManipulationSettingsContainer } from "./options";
import { SourceFileStructure, OptionalKind } from "./structures";
import { WriterFunction } from "./types";
import { ts, CompilerOptions } from "./typescript";
import { ts, CompilerOptions, ScriptKind } from "./typescript";
import { IterableUtils, FileUtils, matchGlobs, TsConfigResolver, Memoize } from "./utils";

/** Options for creating a project. */
Expand Down Expand Up @@ -39,6 +39,10 @@ export interface SourceFileCreateOptions {
* @remarks When false, the method will throw when a file exists.
*/
overwrite?: boolean;
/**
* Specifies the script kind of the source file.
*/
scriptKind?: ScriptKind;
}

/**
Expand Down
11 changes: 10 additions & 1 deletion src/compiler/ast/module/SourceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { getNextMatchingPos, getPreviousMatchingPos } from "../../../manipulatio
import { ProjectContext } from "../../../ProjectContext";
import { SourceFileSpecificStructure, SourceFileStructure, StructureKind } from "../../../structures";
import { Constructor } from "../../../types";
import { LanguageVariant, ScriptTarget, ts } from "../../../typescript";
import { LanguageVariant, ScriptTarget, ts, ScriptKind } from "../../../typescript";
import { ArrayUtils, EventContainer, FileUtils, Memoize, ModuleUtils, SourceFileReferenceContainer, StringUtils } from "../../../utils";
import { Diagnostic, EmitOptionsBase, EmitOutput, EmitResult, FormatCodeSettings, TextChange, UserPreferences } from "../../tools";
import { ModuledNode, TextInsertableNode } from "../base";
Expand Down Expand Up @@ -544,6 +544,15 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
return this.compilerNode.languageVariant;
}

/**
* Gets the script kind of the source file.
*/
getScriptKind(): ScriptKind {
// todo: open issue on typescript repo about making this not internal?
// otherwise, store a collection of what each source file should be.
return (this.compilerNode as any).scriptKind;
}

/**
* Gets if this is a declaration file.
*/
Expand Down
10 changes: 8 additions & 2 deletions src/compiler/tools/LanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@ export class LanguageService {
getScriptSnapshot: fileName => {
if (!fileExistsSync(fileName))
return undefined;
return ts.ScriptSnapshot.fromString(this._context.compilerFactory.addOrGetSourceFileFromFilePath(fileName, { markInProject: false })!.getFullText());
return ts.ScriptSnapshot.fromString(this._context.compilerFactory.addOrGetSourceFileFromFilePath(fileName, {
markInProject: false,
scriptKind: undefined
})!.getFullText());
},
getCurrentDirectory: () => context.fileSystemWrapper.getCurrentDirectory(),
getDefaultLibFileName: options => {
Expand All @@ -88,7 +91,10 @@ export class LanguageService {
this._compilerHost = {
getSourceFile: (fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void) => {
// todo: use languageVersion here?
const sourceFile = this._context.compilerFactory.addOrGetSourceFileFromFilePath(fileName, { markInProject: false });
const sourceFile = this._context.compilerFactory.addOrGetSourceFileFromFilePath(fileName, {
markInProject: false,
scriptKind: undefined
});
return sourceFile == null ? undefined : sourceFile.compilerNode;
},
// getSourceFileByPath: (...) => {}, // not providing these will force it to use the file name as the file path
Expand Down
22 changes: 11 additions & 11 deletions src/factories/CompilerFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { ProjectContext } from "../ProjectContext";
import { SourceFileCreateOptions } from "../Project";
import { SourceFileStructure, OptionalKind } from "../structures";
import { WriterFunction } from "../types";
import { SyntaxKind, ts, TypeFlags } from "../typescript";
import { SyntaxKind, ts, TypeFlags, ScriptKind } from "../typescript";
import { replaceSourceFileForCacheUpdate } from "../manipulation";
import { EventContainer, FileUtils, KeyValueCache, WeakCache, StringUtils, getTextFromStringOrWriter } from "../utils";
import { DirectoryCache } from "./DirectoryCache";
Expand Down Expand Up @@ -142,10 +142,10 @@ export class CompilerFactory {
*/
createSourceFileFromText(filePath: string, sourceText: string, options: SourceFileCreateOptions & { markInProject: boolean; }) {
filePath = this.context.fileSystemWrapper.getStandardizedAbsolutePath(filePath);
if (options != null && options.overwrite === true)
return this.createOrOverwriteSourceFileFromText(filePath, sourceText, options);
if (options.overwrite === true)
return this.createOrOverwriteSourceFileFromText(filePath, sourceText, options as MakeOptionalUndefined<typeof options>);
this.throwIfFileExists(filePath);
return this.createSourceFileFromTextInternal(filePath, sourceText, options);
return this.createSourceFileFromTextInternal(filePath, sourceText, options as MakeOptionalUndefined<typeof options>);
}

/**
Expand All @@ -160,12 +160,12 @@ export class CompilerFactory {
throw new errors.InvalidOperationError(`${prefixMessage}A source file already exists at the provided file path: ${filePath}`);
}

private createOrOverwriteSourceFileFromText(filePath: string, sourceText: string, options: { markInProject: boolean; }) {
private createOrOverwriteSourceFileFromText(filePath: string, sourceText: string, options: { markInProject: boolean; scriptKind: ScriptKind | undefined; }) {
filePath = this.context.fileSystemWrapper.getStandardizedAbsolutePath(filePath);
const existingSourceFile = this.addOrGetSourceFileFromFilePath(filePath, options);
if (existingSourceFile != null) {
existingSourceFile.getChildren().forEach(c => c.forget());
this.replaceCompilerNode(existingSourceFile, this.createCompilerSourceFileFromText(filePath, sourceText));
this.replaceCompilerNode(existingSourceFile, this.createCompilerSourceFileFromText(filePath, sourceText, options.scriptKind));
return existingSourceFile;
}

Expand All @@ -185,7 +185,7 @@ export class CompilerFactory {
* Gets a source file from a file path. Will use the file path cache if the file exists.
* @param filePath - File path to get the file from.
*/
addOrGetSourceFileFromFilePath(filePath: string, options: { markInProject: boolean; }): SourceFile | undefined {
addOrGetSourceFileFromFilePath(filePath: string, options: { markInProject: boolean; scriptKind: ScriptKind | undefined; }): SourceFile | undefined {
filePath = this.context.fileSystemWrapper.getStandardizedAbsolutePath(filePath);
let sourceFile = this.sourceFileCacheByFilePath.get(filePath);
if (sourceFile == null && this.context.fileSystemWrapper.fileExistsSync(filePath)) {
Expand Down Expand Up @@ -305,18 +305,18 @@ export class CompilerFactory {
}
}

private createSourceFileFromTextInternal(filePath: string, text: string, options: { markInProject: boolean; }): SourceFile {
private createSourceFileFromTextInternal(filePath: string, text: string, options: { markInProject: boolean; scriptKind: ScriptKind | undefined; }): SourceFile {
const hasBom = StringUtils.hasBom(text);
if (hasBom)
text = StringUtils.stripBom(text);
const sourceFile = this.getSourceFile(this.createCompilerSourceFileFromText(filePath, text), options);
const sourceFile = this.getSourceFile(this.createCompilerSourceFileFromText(filePath, text, options.scriptKind), options);
if (hasBom)
sourceFile._hasBom = true;
return sourceFile;
}

createCompilerSourceFileFromText(filePath: string, text: string): ts.SourceFile {
return this.documentRegistry.createOrUpdateSourceFile(filePath, this.context.compilerOptions.get(), ts.ScriptSnapshot.fromString(text));
createCompilerSourceFileFromText(filePath: string, text: string, scriptKind: ScriptKind | undefined): ts.SourceFile {
return this.documentRegistry.createOrUpdateSourceFile(filePath, this.context.compilerOptions.get(), ts.ScriptSnapshot.fromString(text), scriptKind);
}

/**
Expand Down
28 changes: 16 additions & 12 deletions src/factories/DocumentRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,38 +15,38 @@ export class DocumentRegistry implements ts.DocumentRegistry {
this.sourceFileCacheByFilePath.removeByKey(fileName);
}

createOrUpdateSourceFile(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: ts.IScriptSnapshot) {
createOrUpdateSourceFile(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: ts.IScriptSnapshot, scriptKind: ScriptKind | undefined) {
let sourceFile = this.sourceFileCacheByFilePath.get(fileName);
if (sourceFile == null)
sourceFile = this.updateSourceFile(fileName, compilationSettings, scriptSnapshot, DocumentRegistry.initialVersion);
sourceFile = this.updateSourceFile(fileName, compilationSettings, scriptSnapshot, DocumentRegistry.initialVersion, scriptKind);
else
sourceFile = this.updateSourceFile(fileName, compilationSettings, scriptSnapshot, this.getNextSourceFileVersion(sourceFile));
sourceFile = this.updateSourceFile(fileName, compilationSettings, scriptSnapshot, this.getNextSourceFileVersion(sourceFile), scriptKind);
return sourceFile;
}

acquireDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: ts.IScriptSnapshot, version: string, scriptKind?: ScriptKind | undefined): ts.SourceFile {
acquireDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: ts.IScriptSnapshot, version: string, scriptKind: ScriptKind | undefined): ts.SourceFile {
let sourceFile = this.sourceFileCacheByFilePath.get(fileName);
if (sourceFile == null || this.getSourceFileVersion(sourceFile) !== version)
sourceFile = this.updateSourceFile(fileName, compilationSettings, scriptSnapshot, version);
sourceFile = this.updateSourceFile(fileName, compilationSettings, scriptSnapshot, version, scriptKind);
return sourceFile;
}

acquireDocumentWithKey(fileName: string, path: ts.Path, compilationSettings: CompilerOptions, key: ts.DocumentRegistryBucketKey, scriptSnapshot: ts.IScriptSnapshot,
version: string, scriptKind?: ScriptKind | undefined): ts.SourceFile
version: string, scriptKind: ScriptKind | undefined): ts.SourceFile
{
// ignore the key because we only ever keep track of one key
return this.acquireDocument(fileName, compilationSettings, scriptSnapshot, version, scriptKind);
}

updateDocument(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: ts.IScriptSnapshot, version: string,
scriptKind?: ScriptKind | undefined): ts.SourceFile
scriptKind: ScriptKind | undefined): ts.SourceFile
{
// the compiler will call this even when it doesn't need to update for some reason
return this.acquireDocument(fileName, compilationSettings, scriptSnapshot, version, scriptKind);
}

updateDocumentWithKey(fileName: string, path: ts.Path, compilationSettings: CompilerOptions, key: ts.DocumentRegistryBucketKey, scriptSnapshot: ts.IScriptSnapshot,
version: string, scriptKind?: ScriptKind | undefined): ts.SourceFile
version: string, scriptKind: ScriptKind | undefined): ts.SourceFile
{
// ignore the key because we only ever keep track of one key
return this.updateDocument(fileName, compilationSettings, scriptSnapshot, version, scriptKind);
Expand Down Expand Up @@ -77,15 +77,19 @@ export class DocumentRegistry implements ts.DocumentRegistry {
return (currentVersion + 1).toString();
}

private updateSourceFile(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: ts.IScriptSnapshot, version: string): ts.SourceFile {
private updateSourceFile(fileName: string, compilationSettings: CompilerOptions, scriptSnapshot: ts.IScriptSnapshot, version: string,
scriptKind: ScriptKind | undefined): ts.SourceFile
{
fileName = this.fileSystemWrapper.getStandardizedAbsolutePath(fileName);
const newSourceFile = this.createCompilerSourceFile(fileName, scriptSnapshot, compilationSettings, version);
const newSourceFile = this.createCompilerSourceFile(fileName, scriptSnapshot, compilationSettings, version, scriptKind);
this.sourceFileCacheByFilePath.set(fileName, newSourceFile);
return newSourceFile;
}

private createCompilerSourceFile(fileName: string, scriptSnapshot: ts.IScriptSnapshot, compilationSettings: CompilerOptions, version: string) {
private createCompilerSourceFile(fileName: string, scriptSnapshot: ts.IScriptSnapshot, compilationSettings: CompilerOptions, version: string,
scriptKind: ScriptKind | undefined)
{
const scriptTarget = compilationSettings.target || ScriptTarget.Latest;
return ts.createLanguageServiceSourceFile(fileName, scriptSnapshot, scriptTarget, version, true, /* scriptKind */ undefined);
return ts.createLanguageServiceSourceFile(fileName, scriptSnapshot, scriptTarget, version, true, scriptKind);
}
}
5 changes: 4 additions & 1 deletion src/fileSystem/DirectoryCoordinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ export class DirectoryCoordinator {
}

addExistingSourceFileIfExists(filePath: string, options: { markInProject: boolean; }): SourceFile | undefined {
return this.compilerFactory.addOrGetSourceFileFromFilePath(filePath, options);
return this.compilerFactory.addOrGetSourceFileFromFilePath(filePath, {
markInProject: options.markInProject,
scriptKind: undefined
});
}

addExistingSourceFile(filePath: string, options: { markInProject: boolean; }): SourceFile {
Expand Down
5 changes: 4 additions & 1 deletion src/manipulation/manipulations/doManipulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ export function doManipulation(sourceFile: SourceFile, textManipulator: TextMani
sourceFile._firePreModified();
const newFileText = textManipulator.getNewText(sourceFile.getFullText());
try {
const replacementSourceFile = sourceFile._context.compilerFactory.createCompilerSourceFileFromText(newFilePath || sourceFile.getFilePath(), newFileText);
const replacementSourceFile = sourceFile._context.compilerFactory.createCompilerSourceFileFromText(
newFilePath || sourceFile.getFilePath(),
newFileText,
sourceFile.getScriptKind());
nodeHandler.handleNode(sourceFile, replacementSourceFile, replacementSourceFile);
} catch (err) {
throw new errors.InvalidOperationError(err.message + "\n"
Expand Down
13 changes: 12 additions & 1 deletion src/tests/fileSystem/directoryTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Directory, DirectoryCopyOptions, DirectoryEmitResult, DirectoryMoveOpti
import { Project } from "../../Project";
import { SourceFileStructure, StructureKind, OptionalKind } from "../../structures";
import { WriterFunction } from "../../types";
import { CompilerOptions, ModuleResolutionKind, ScriptTarget } from "../../typescript";
import { CompilerOptions, ModuleResolutionKind, ScriptTarget, ScriptKind } from "../../typescript";
import { FileUtils } from "../../utils";
import { CustomFileSystemProps, getFileSystemHostWithFiles, testDirectoryTree } from "../testHelpers";

Expand Down Expand Up @@ -349,6 +349,17 @@ describe(nameof(Directory), () => {
const sourceFile = dir.createSourceFile("/file.ts");
expect(sourceFile._isInProject()).to.be.false;
});

it("should be able to specify a script kind", () => {
// people should not be using markdown files in here... adding tests anyway...
const directory = new Project({ useVirtualFileSystem: true }).createDirectory("/dir");
const sourceFile = directory.createSourceFile("MyFile.md", "# Header", { scriptKind: ScriptKind.External });
expect(sourceFile.getScriptKind()).to.equal(ScriptKind.External);

// should work after manipulation
sourceFile.replaceWithText("# New Header");
expect(sourceFile.getScriptKind()).to.equal(ScriptKind.External);
});
});

describe(nameof<Directory>(d => d.addExistingSourceFileIfExists), () => {
Expand Down
12 changes: 11 additions & 1 deletion src/tests/projectTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { VirtualFileSystemHost } from "../fileSystem";
import { IndentationText } from "../options";
import { Project, ProjectOptions } from "../Project";
import { SourceFileStructure, StructureKind } from "../structures";
import { CompilerOptions, ScriptTarget, SyntaxKind, ts } from "../typescript";
import { CompilerOptions, ScriptTarget, SyntaxKind, ts, ScriptKind } from "../typescript";
import { OptionalKindAndTrivia } from "./compiler/testHelpers";
import * as testHelpers from "./testHelpers";

Expand Down Expand Up @@ -807,6 +807,16 @@ describe(nameof(Project), () => {
expect(sourceFile.getFullText()).to.equal(expectedText);
});

it("should be able to specify a script kind", () => {
// people should not be using markdown files in here... adding tests anyway...
const sourceFile = new Project({ useVirtualFileSystem: true }).createSourceFile("MyFile.md", "# Header", { scriptKind: ScriptKind.External });
expect(sourceFile.getScriptKind()).to.equal(ScriptKind.External);

// should work after manipulation
sourceFile.replaceWithText("# New Header");
expect(sourceFile.getScriptKind()).to.equal(ScriptKind.External);
});

it("", () => {
// todo: remove
const project = new Project({ useVirtualFileSystem: true });
Expand Down
5 changes: 5 additions & 0 deletions src/typings/customTypings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@ type GetStrings<T> = T extends string ? T : never;
type MakeRequired<T> = Mutable<T, GetStrings<keyof T>>;
type InferArrayElementType<T> = T extends (infer U)[] ? U : T extends ReadonlyArray<infer U> ? U : never;
type OptionalProperties<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

// from https://medium.com/terria/typescript-transforming-optional-properties-to-required-properties-that-may-be-undefined-7482cb4e1585
type MakeOptionalUndefined<T> = {
[P in keyof Required<T>]: Pick<T, P> extends Required<Pick<T, P>> ? T[P] : (T[P] | undefined);
};

0 comments on commit cb22219

Please sign in to comment.