View CHANGELOG.md for more detail on releases. This file is only a high level overview of breaking changes.
- Upgraded to TS 5.3
- Underlying
@ts-morph/common
file system methods return undefined instead of throwing when not exists (for perf). - Private fields are actually private (default build requires private fields).
- Upgraded to TS 5.2
- Upgraded to TS 4.8
- Decorators are now modifiers due to TS 4.8
ts.createX
functions seem almost completely deprecated in TS 4.8, so make sure to update your code to use thetraversal.context.createX
functions insteadType#isArray()
returns true for readonly arrays
- Upgraded to TS 4.6
- To align with the ts compiler:
AssertEntry#getValue
now returns an expression instead of a string literal.AssertEntryStructure#value
now represents an expression instead of a string literal.
- Upgraded to TS 4.5
- Node type guards like
Node.isXNode(node)
have dropped theNode
suffix because it was overly verbose—useNode.isX(node)
now (ex.Node.isAmbientable(node)
). - The already deprecated
WriterFunctions
andTypeGuards
exports were removed. UseWriters
andNode
. FileSystemHost#readDirSync
returns directory entries instead of file and dir path names.- Target ES2018 instead of ES2015.
- Upgraded to TS 4.4
- Upgraded to TS 4.3
JSDoc#getComment()
andJSDocTag#getComment()
no longer possibly return just a string based on changes in the compiler API. Use#getCommentText()
instead now to just get the text.- Support for the new
override
keyword.
- Upgraded to TS 4.2.
- TypeScript is now bundled with ts-morph. You may still access TypeScript via
import { ts } from "ts-morph"
as you should have been doing anyway. - The lib files/lib.d.ts files are now served from memory regardless of the file system host. If you want the old behaviour, specify a
libFolderPath
in theProject
constructor's options. NamespaceDeclaration
and similar names are now calledModuleDeclaration
. See #924 for why this was done. In most cases you can just do a search forNamespace
and replace withModule
to do the upgrade. Note that this node now has an optional body as it should have had in the first place.- The
ExportedDeclarations
union type now properly includesSourceFile
. It previously could have done that anyway and your code might not have handled it.
Previously when using an in memory file system host, ts-morph had all the TypeScript "lib files"/"lib.d.ts files" at /node_modules/TypeScript/lib
. This meant if you wrote the following code, it would give you the Set<string>
type:
const project = new Project({ useInMemoryFileSystem: true });
const sourceFile = project.createSourceFile(
"index.ts",
`const mySet = new Set<string>();`,
);
const mySetDecl = sourceFile.getVariableDeclarationOrThrow("mySet");
console.log(mySetDecl.getType().getText()); // Set<string>
However, this behaviour is not what happens when you write a bare TypeScript file and attempt to use the Set
type (it will show a diagnostic for the Set
type not existing because the default target is ES5
and not at least ES2015
). As part of the changes to where the lib files are stored, the behaviour was changed here to align more with TypeScript. The above code will now have a diagnostic for Set
not existing.
To get around this, you must now do what you would do when using tsc
and specify either a lib
compiler option:
const project = new Project({
useInMemoryFileSystem: true,
compilerOptions: {
lib: ["lib.es2015.d.ts"],
},
});
Or specify a target that will implicitly load in the lib files that you need:
import { Project, ts } from "ts-morph";
const project = new Project({
useInMemoryFileSystem: true,
compilerOptions: {
target: ts.ScriptTarget.ES2015,
},
});
Note that if you want to include all the lib files, which is similar to the previous behaviour, you may specify lib.esnext.full.d.ts
as a lib
option:
const project = new Project({
useInMemoryFileSystem: true,
compilerOptions: {
lib: ["lib.esnext.full.d.ts"],
},
});
- Upgraded to TS 4.1.
addFilesFromTsConfig
is now inverted toskipAddingFilesFromTsConfig
. Default behaviour is the same. This was done to make the option align with the other options.BooleanLiteral
is now a type alias ofTrueLiteral
andFalseLiteral
. This was done to match the same change made in the compiler API.
- Upgraded to TypeScript 4.0
TupleTypeNode#getElementTypeNodes()
is nowgetElements()
to match the change in the compiler API.
- Only Node 10+ has full support. Some functionality might not work on older versions of Node.
- Update to TypeScript 3.8.
Renames:
StructureTypeGuards
->Structure
TypeGuards
->Node
(Soft deprecation... will be deprecated in next major)ProjectOptions#useVirtualFileSystem
->useInMemoryFileSystem
FileSystemHost#glob
->globSync
Directory/Project#addExistingSourceFile
->addSourceFileAtPath
Directory/Project#addExistingSourceFileIfExists
->addSourceFileAtPathIfExists
Directory/Project#addExistingSourceFiles
->addSourceFilesAtPaths
Directory/Project#addExistingDirectory
->addDirectoryAtPath
Directory/Project#addExistingDirectoryIfExists
->addDirectoryAtPathIfExists
Removed:
JSDoc#setComment(...)
and#getComment()
. These are nowsetDescription(...)
andgetDescription()
and work according to #764.
Other:
- PropertyDeclarationDeclaration and PropertyDeclarationStructure now can have a
declare
keyword (new in TS 3.7). - Creating a Project with
useInMemoryFileSystem: true
will now load in the lib files into thenode_modules/typescript/lib
folder. If you want the old behaviour, specify{ skipLoadingLibFiles: true }
in theProject
's constructor. - Due to the fix for #702, when using
Project#addSourceFileAtPaths
directories that do not have an ancestor directory with a source file included in the results will no longer be added to the project. If you want to ensure that a directory and all its subfolders are added, useProject#addDirectoryAtPath(path, { recursive: true })
. - JS docs will be written as a single line unless multi-line or starting with a newline. Additionally, getting a JS doc structure will have a newline at the start if the JS doc description is one line, but the JS doc is multi-line.
- TypeScript 3.7.x support only.
- New
@ts-morph/common
base package. This was done to support@ts-morph/bootstrap
. TypePredicateNode#getTypeNode()
now possible returns undefined. This was a change done in the compiler api to support the newasserts
modifier on type predicates.- Recommended to use the new
Project#addSourceFileAtPath(path)
methods instead of the ones likeProject#addExistingSourceFile(path)
. The old way will be deprecated in Version 6. Special thanks to @ChristianIvicevic for helping out with this one.
Mostly renames:
SourceFile#getReferencedFiles()
is nowgetPathReferenceDirectives()
. This was done to prevent confusion with upcoming methods in #680. The name was chosen because it is similar to the methodsgetTypeReferenceDirectives()
andgetLibReferenceDirectives()
.CodeBlockWriter#indentBlock
is nowindent
.withHangingIndentation
is nowhangingIndent
.withHangingIndentationUnlessBlock
is nowhangingIndentUnlessBlock
.DiagnosticMessageChain#getNext()
now returns an array to match TS 3.6.DirectoryEmitResult#getEmitSkipped()
was removed. Check the output file paths and skipped file paths instead as that's more accurate.CompilerExtendedComment
is now calledCompilerCommentNode
.
The Node#forEachChild
method now short circuits when a truthy value is returned from the callback. It will also return this value.
This allows for the following:
const classDec = sourceFile.forEachChild(node => TypeGuards.isClassDeclaration(node) ? node : undefined);
Additionally, the second control
parameter has been removed. So instead of writing this...
node.forEachChild((child, control) => {
if (child.kind === SyntaxKind.PropertyDeclaration)
child.stop();
doSomethingWithChild(child);
});
...you can just return a truthy value to stop. This now aligns exactly with how the compiler api behaves.
BUG WARNING! Previous code that returned a truthy value should be updated to not return one unless the functionality described above is desired.
Read more in issue #633.
In addition to what is described in the previous section, the forEachDescendant
method has also been updated to stop and return on truthy values returned in the callback.
StructureTypeGuards
was added in version 2. This class unfortunately had some poorly named methods.
In version 3, some StructureTypeGuards
static methods have removed the word Node
and Declaration
in certain cases.
For example the following:
if (StructureTypeGuards.isExportableNode(structure))
structure.isExported = false;
...is now updated to be...
if (StructureTypeGuards.isExportable(structure))
structure.isExported = false;
The ObjectLiteralElementMemberStructures
alias is now called ObjectLiteralExpressionPropertyStructures
. This is a more correct name as an object literal expression has "properties" rather than "members" in the compiler api.
These methods used to return source files, but now they return objects similar to what the compiler API returns. There is some loss of functionality here because you will need to resolve the source file reference yourself (previously it wasn't working properly). Please open an issue if you need an easy way to resolve the source file.
It wasn't consistent for the language service to have methods that manipulate nodes. These methods are very old from the beginning of the library, but it is better to use identifier.rename("newName")
.
For a while now it's been recommended to use the named export instead of the default export for the project. From now on, only the named export can be used:
import { Project } from "ts-morph";
New .statements
property on structures with statements / Removed .bodyText
, .classes
, .enums
, etc. properties
Instead of writing code like the following:
const sourceFileStructure: SourceFileStructure = {
classes: [{
name: "MyClass",
}],
enums: [{
name: "MyEnum",
members: [{ name: "value" }],
}],
bodyText: "console.log(5);",
};
Write the following:
const sourceFileStructure: SourceFileStructure = {
statements: [{
kind: StructureKind.Class,
name: "MyClass",
}, {
kind: StructureKind.Enum,
name: "MyEnum",
members: [{ name: "value" }],
}, "console.log(5);"],
};
This may not be as nice for simple scenarios, but will make the library more flexible and simpler to maintain in the future.
Note the new .kind
property. This property is necessary in the statements array to differentiate the different structures, but it is not required
when using the specific methods:
sourceFile.addClass({ name: "MyClass" }); // ok
Methods like project.emit()
are now async. Corresponding emitSync
methods have also been added.
The new async emit methods should be much faster than the previous synchronous methods.
The ByName
suffix was overly verbose, so I removed it. The following methods have been renamed:
Symbol#getExportByName(name: string)
->getExport
Symbol#getExportByNameOrThrow(name: string)
->getExportOrThrow
Symbol#getGlobalExportByName(name: string)
->getGlobalExport
Symbol#getGlobalExportByNameOrThrow(name: string)
->getGlobalExportOrThrow
Symbol#getMemberByName(name: string)
->getMember
Symbol#getMemberByNameOrThrow(name: string)
->getMemberOrThrow
The FileSystemHost
interface now requires implementing realpathSync()
and isCaseSensitive()
.
This was done to match the change in TS 3.4.
These are now all covered by StatementedNodeStructure
.
BodyableNodeStructure
BodiedNodeStructure
ModuledNodeStructure
These two structures are now differentiated based on their new .kind
property. Previously it used the isSpreadAttribute
property.
These two structures are now differentiated based on their new .kind
property. Previously it used the isSelfClosing
property.
Instead of just returning an array of nodes, it now returns a map. The key is the name it was exported on and the value is an array of declarations for that value. This will make it much easier to identify the name a node was exported on.
Removed SourceFile#getLineNumberAtPos(pos)
in favour of #getLineAndColumnAtPos(pos)
which returns an object with both line and column number at the provided position:
const { line, column } = sourceFile.getLineAndColumnAtPos(position);
Instead of calling:
project.applyFileTextChanges(fileTextChanges);
Do the following:
fileTextChanges.forEach(change => change.applyChanges());
This new method name makes more sense:
const arrayType = ...;
const arrayElementType = arrayType.getArrayType(); // confusing
// better
const arrayElementType = arrayType.getArrayElementType();
Previously it would return both the initializer
and value
. value
should be seen as more of a convenience property for setting the initializer.
Renamed library to ts-morph
and reset version to 1.0.0.
node.getFirstChildByKind
andnode.getChildrenOfKind
now search the parsed tree via.forEachChild(...)
when specifying a parsed node's syntax kind. Previously it would only search the results ofnode.getChildren()
.
Resolved node_module source files and directories are no longer returned from Project#getSourceFiles() and getDirectories()
Previously resolved source files in node_modules
would be returned when calling project.getSourceFiles()
. This was sometimes confusing and meant that people iterating over the source files in a project would need to ensure they weren't looking at the files in the node_modules
directory.
Now they are not added unless done so explicitly:
project.addExistingSourceFiles("node_modules/**/*.ts");
Note that the directory and source files are still available when you explicitly specify their path (ex. project.getDirectory("node_modules")
).
- Added support for TS 3.2.
- Removed
JSDocTag#getAtToken()
. TheatToken
property was removed in the compiler api.
Options
interface renamed toProjectOptions
in order to be more specific.
Referenced source files in module specifiers and references are now added to the project when constructing a project and providing a tsconfig.
For example, say you had the following files:
// src/main.ts
export * from "./classes";
// src/classes.ts
export class Test {}
// tsconfig.json
{
"files": ["src/main.ts"],
"compilerOptions" {
// etc...
}
}
Now when constructing a project like so...
const project = new Project({ tsConfigFilePath: "tsconfig.json" });
...the project will now include both source files instead of only src/main.ts.
Doing this requires an extra analysis step so if you want to revert back to the old behaviour, provide the skipFileDependencyResolution
option and set it to true:
const project = new Project({
tsConfigFilePath: "tsconfig.json",
skipFileDependencyResolution: true,
});
Previously a custom file system host could be passed to the constructor like so:
const project = new Project({}, fileSystem);
This was mostly for legacy reasons. It's now been moved to be an option.
const project = new Project({ fileSystem });
JSDocTag.getName()
is now.getTagName()
. This was originally incorrectly named and.getName()
is necessary for js doc tags that have one (such asJSDocPropertyLikeTag
).
- Removed
CompilerNodeBrandPropertyNamesType
type alias. - More nodes are being written with hanging indentation when doing a newline.
In ambient declarations, there exists namespace declarations that look like the following:
global {
export class Test {}
}
The following changes were made:
Deprecated:
.setHasNamespaceKeyword
.setHasModuleKeyword
NamespaceDeclarationStructure
-hasModuleKeyword
andhasNamespaceKeyword
Added:
enum NamespaceDeclarationKind { Namespace = "namespace", Module = "module", Global = "global" }
setDeclarationKind(kind: NamespaceDeclarationKind)
getDeclarationKind(): NamespaceDeclarationKind;
NamespaceDeclarationStructure
-declarationKind: NamespaceDeclarationKind
Even though it's a compile error, index signatures may look like the following:
interface MyInterface {
[key: string];
}
For this reason, the .getReturnTypeNode()
method on IndexSignatureDeclaration
may now return undefined.
Previously there were a lot of XExtensionType
type aliases that were used internally within the library, but they were
being exported. These are now not exported from the libraries declaration file and made internal. See #441
for more details.
TypeParameterDeclaration
-getConstraintNode()
andgetDefaultNode()
are nowgetConstraint()
andgetDefault()
.JsxTagNamedNode
-getTagName()
is now.getTagNameNode()
for consistency.
Previously, the following methods would rename with the language service:
ImportDeclaration.setDefaultImport
ImportSpecifier.setAlias
ExportSpecifier.setAlias
These no longer rename using the language service. Use the corresponding renameX
methods instead.
The .fill
methods are now called .set
and their behaviour has changed.
Previously calling...
classDeclaration.fill({
properties: [{ name: "newProp" }],
});
...would add a property. Now with .set
it will remove all existing properties and replace it with the specified properties.
If you want the .fill
behaviour, use the .addX
methods or provide the structures of the nodes by using .getStructure()
(Ex. classDeclaration.set({ properties: [...classDeclaration.getParameters().map(p => p.getStructure()), { name: "NewProperty" }] });
)`
Previously, doing classDeclaration.fill({ name: "NewName" })
would do a rename with the language service. Now with .set({ name: "NewName" })
it sets the name without renaming.
Use project.getPreEmitDiagnostics()
and sourceFile.getPreEmitDiagnostics()
instead. Read why in #384.
Also, deprecated program.getPreEmitDiagnostics()
. It didn't make sense for this method to be on Program
.
For example, given the following code:
const { a, b } = { a: 1, b: 2 };
Doing the following will now correctly return a binding element:
const statement = sourceFile.getVariableStatements()[0];
const declaration = statement.getDeclarations();
const name = declaration.getNameNode();
if (TypeGuards.isBindingElement(name))
console.log(name.getElements().map(e => e.getName())); // outputs: ["a", "b"]
CompilerApiNodeBrandPropertyNamesType
is nowCompilerNodeBrandPropertyNamesType
.renameName
onImportSpecifier
andExportSpecifier
is now deprecated. UseimportSpecifier.getNameNode().rename(newName)
.- Renamed
getAliasIdentifier()
togetAliasNode()
onImportSpecifier
andExportSpecifier
. Done for consistency. - Deprecated
node.getStartColumn()
andnode.getEndColumn()
. - Renamed
sourceFile.getColumnAtPos(pos)
to.getLengthFromLineStartAtPos(pos)
for correctness. - Renamed
sourceFile.getLineNumberFromPos(pos)
togetLineNumberAtPos(pos)
for consistency. getImplementations()[i].getNode()
now returns the identifier range (compiler API changed behaviour).
Similarly to ClassDeclaration
, FunctionDeclaration
can have an optional name when used as a default export:
export default function() {
}
To support this scenario, FunctionDeclaration
's name has become optional.
Previously, stopping traveral in node.forEachDescendant(...)
was done like the following:
node.forEachDescendant((node, stop) => {
if (node.getKind() === SyntaxKind.FunctionDeclaration)
stop();
});
The new code is the following:
node.forEachDescendant((node, traversal) => {
if (node.getKind() === SyntaxKind.FunctionDeclaration)
traversal.stop();
});
This is to allow for more advanced scenarios:
node.forEachDescendant((node, traversal) => {
switch (node.getKind()) {
case SyntaxKind.ClassDeclaration:
// skips traversal of the current node's descendants
traversal.skip();
break;
case SyntaxKind.ParameterDeclaration:
// skips traversal of the current node, siblings, and all their descendants
traversal.up();
break;
case SyntaxKind.FunctionDeclaration:
// stop traversal completely
traversal.stop();
break;
}
});
Also, take note that node.forEachChild
has been updated for consistency with forEachDescendant
:
node.forEachChild((node, traversal) => {
if (node.getKind() === SyntaxKind.FunctionDeclaration)
traversal.stop();
});
The declaration file now uses some conditional types and that requires TypeScript 2.8+.
Methods in the form of Type.isXType()
are now Type.isX()
. For example:
type.isArrayType();
Is now:
type.isArray();
This was done so it aligns with the TypeScript compiler API methods and to remove the needless repetition in the naming.
getReferencingNodes()
onReferenceFindableNode
s is nowfindReferencesAsNodes()
andlanguageService.getDefinitionReferencingNodes()
is also nowfindReferencesAsNodes()
. This was done to improve its discoverability.
Instead of access code-block-writer by doing:
import CodeBlockWriter from "code-block-writer";
It's now a named export from this library:
import { CodeBlockWriter } from "ts-morph";
The FileSystemHost
interface now has move
, moveSync
, copy
, and copySync
methods.
Directory
.remove()
is now.forget()
- This was done for consistency withSourceFile
.
SourceFile
getRelativePathToSourceFileAsModuleSpecifier
is nowgetRelativePathAsModuleSpecifierTo
.getRelativePathToSourceFile
is nowgetRelativePathTo
.
Identifier
getDefinitionReferencingNodes()
is nowgetReferencingNodes()
for consistency.
ClassDeclarationStructure
ctor
is nowctors
- There can be multiple constructors on ambient classes.
ExportAssignmentStructure
isEqualsExport
is nowisExportEquals
- I originally named this incorrectly by accident.
I had no idea you can have class declarations with no names...
export default class {
// ...etc...
}
...and so .getName()
and .getNameNode()
on ClassDeclaration
is now nullable. I recommend using .getNameOrThrow()
if you don't want to check for null each time or assert null.
Project
and Directory
addSourceFileIfExists
is nowaddExistingSourceFileIfExists
(standardizes withaddExistingSourceFile
)addDirectoryIfExists
is nowaddExistingDirectoryIfExists
(standardizes withaddExistingDirectory
)
SourceFile
getReferencingImportAndExportDeclarations
is removed. UsegetReferencingNodesInOtherSourceFiles
.
VariableDeclarationType
and QuoteType
are now called VariableDeclarationKind
and QuoteKind
respectively.
This was done to reduce the confusion with the word "type" and to standardize it with other enums.
Script target was removed from the manipulation settings (it was there for legacy reasons) and can now be found only on the compiler options.
Instead of being written as:
import { SomeExport } from "./SomeFile";
Imports will now be written with spaces (as is the default in the compiler):
import { SomeExport } from "./SomeFile";
If you don't want this behaviour, then set the insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces
to false
in the manipulation settings.
The library has been upgraded to TypeScript 2.8.
To celebrate there is a new sourceFile.organizeImports()
method. It takes a long time
to run it on an entire code base though... (depends how big your project is, of course). At least it's still faster than a human :)
There were some "Base" declarations (ex. ClassDeclarationBase
) variables that were previously exported
from the library. It was previously necessary to export these because the TypeScript compiler would complain
about them needing to be public, but I manged to get around this by writing a script that modifies the declaration file to hide them after the fact.
The library is smarter about populating directories. See #285, #286, #287 for more details.
Improved the wrapping of non-node wrapped compiler objects (symbols, types, diagnostics, etc.). Previously symbols had to be compared like so:
someSymbol.equals(otherSymbol); // deprecated method
But now, as expected, you can compare them using an equality check:
someSymbol === otherSymbol;
Class declaration was changed to be more like the compiler. Read #266 for more details.
All file system copies, moves, and deletes are now deffered until .save()
is called on the main ast
object.
For example:
import Project from "ts-morph";
const project = new Project();
// ...lots of code here that manipulates, copies, moves, and deletes files...
sourceFile.delete();
directory.delete();
otherSourceFile.move("OtherFile.ts");
otherSourceFile.copy("NewFile.ts");
// ...more code that changes files...
// copies, moves, deletes, and other changes are executed on the file system on this line
project.save();
This was done so that nothing will be passed to the file system until you are all done manipulating.
If you want the previous behaviour, then use the "Immediately" methods:
await sourceFile.copyImmediately("NewFile.ts"); // or use 'Sync' alternatives
await sourceFile.moveImmediately("NewFile2.ts");
await sourceFile.deleteImmediately();
await directory.deleteImmediately();
Moving directories is going to come soon. Follow the progress in #256.
The TypeScript peer dependency has been dropped, but there should be no loss of functionality! If there is, please open an issue.
The TypeScript compiler object used by this library can now be accessed via the ts
named export. Also non-node TypeScript compiler objects used in the public API of this library are now exported directly as named exports:
import Project, { ScriptTarget, SyntaxKind, ts } from "ts-morph";
If you were previously using both like so:
import * as ts from "typescript";
import Project from "ts-morph";
// ... other code
const tsSourceFile = ts.createSourceFile(...etc...);
const classDeclarations = sourceFile.getDescendantsByKind(ts.SyntaxKind.ClassDeclaration);
Then you should remove the dependency on the TypeScript compiler and change this code to only use a single import declaration:
import Project, {SyntaxKind, ts} from "ts-morph";
// ... other code
const tsSourceFile = ts.createSourceFile(...etc...);
const classDeclarations = sourceFile.getDescendantsByKind(SyntaxKind.ClassDeclaration);