Skip to content

Commit

Permalink
docs: a few more examples + changeset
Browse files Browse the repository at this point in the history
  • Loading branch information
astahmer committed Nov 10, 2023
1 parent 8376739 commit 08c809b
Show file tree
Hide file tree
Showing 2 changed files with 101 additions and 11 deletions.
6 changes: 6 additions & 0 deletions .changeset/tough-bikes-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'typemorph': minor
---

ast.kind, ast.exportDeclaration, ast.some, ast.every, ast.object Partial, ast.when/refine list matching,
Pattern.toString, Pattern.matches, ast.maybeNode, ast.not, ast.contains, Pattern.collectCaptures
106 changes: 95 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,114 @@ pnpm install typemorph

## Usage

Here is a basic example of how to use TypeMorph to match and manipulate TypeScript AST nodes:
Here is a basic example of how to use TypeMorph to match TypeScript AST nodes:

```typescript
import { ast } from 'typemorph'

// Match a string literal
const stringMatcher = ast.string('hello')
// will match: `const myString = 'hello'`
// ^^^^^^^

// Match a numeric literal
const numberMatcher = ast.number(42)
// will match: `const myNumber = 42`
// ^^

// Match any node
const anyMatcher = ast.any()
// will match: `const myNumber = 42`
// ^^^^^^^^^^^^^^^^^^^

// Will resolve to any node with a name or identifier of 'something'
const namedMatcher = ast.named('something')
// will match: `const something = 42`
// ^^^^^^^^^

// Match a type-only import declaration
ast.importDeclaration('node:path', 'path', true)
// will match: `import type * as path from 'node:path'`

// Match a named import declaration
ast.importDeclaration('node:fs', ['writeFile', 'readFile'])
// will match: `import { writeFile, readFile } from 'node:fs'`

// Match using a tuple pattern
ast.importDeclaration('node:fs', ast.tuple(ast.importSpecifier('writeFile'), ast.importSpecifier('readFile')))
// Match using a tuple pattern for a more complex case
ast.importDeclaration('node:fs', ast.tuple(ast.importSpecifier('writeFile'), ast.rest(ast.any())))
// will match: `import { writeFile, readFile, readFileSync, readdir } from "node:fs";`
// the ast.rest() pattern will match any number of import specifiers after the previous patterns have been matched.
```

### Project setup example

```ts
import { Node, Project, SourceFile, ts } from 'ts-morph'
import { Pattern, ast } from 'typemorph'

const createProject = () => {
return new Project({
compilerOptions: {
jsx: ts.JsxEmit.React,
jsxFactory: 'React.createElement',
jsxFragmentFactory: 'React.Fragment',
module: ts.ModuleKind.ESNext,
target: ts.ScriptTarget.ESNext,
noUnusedParameters: false,
noEmit: true,
useVirtualFileSystem: true,
allowJs: true,
},
skipAddingFilesFromTsConfig: true,
skipFileDependencyResolution: true,
skipLoadingLibFiles: true,
useInMemoryFileSystem: true,
})
}

const project = createProject()

const traverse = <TPattern extends Pattern>(
sourceFile: SourceFile,
pattern: TPattern,
stopOnMatch: boolean = false,
) => {
let match: Pattern | undefined
sourceFile.forEachDescendant((node, traversal) => {
if (pattern.match(node)) {
match = pattern
stopOnMatch && traversal.stop()
}
})

return match
}

const sourceFile = project.addSourceFileAtPath('./file.tsx')
const pattern = traverse(
sourceFile,
ast.node(ts.SyntaxKind.CallExpression, {
expression: ast.ref('fn'),
arguments: ast.tuple(ast.ref('arg1'), ast.ref('arg2'), ast.rest(ast.ref('rest'))),
}),
)

// file.tsx = `someFunction({ arg1: { foo: 'bar' }}, true, "a", 2, "c")`
if (pattern) {
const captured = pattern.collectCaptures()
captured.fn // Pattern<Identifier>
captured.fn.lastMatch // Identifier
captured.fn.lastMatch.getText() // 'someFunction'

captured.arg1 // Pattern<ObjectLiteral>
captured.arg1.lastMatch // ObjectLiteral
captured.arg1.lastMatch.getText() // '{ foo: "bar" }'

// Match an import declaration with a rest pattern
ast.importDeclaration('node:fs', ast.rest(ast.any()))
captured.arg2 // Pattern<TrueKeyword>
captured.arg2.lastMatch // TrueKeyword

captured.rest // Pattern<[StringLiteral, NumericLiteral, StringLiteral]>
}
```

## Examples
Expand All @@ -55,9 +135,13 @@ AST node of a specific kind.

```ts
const specificImportSpecifierMatcher = ast.node(SyntaxKind.ImportSpecifier, {
name: ast.identifier('specificName'), // Match only import specifiers with the name "specificName".
propertyName: ast.identifier('specificPropertyName'), // Match only if the property name is "specificPropertyName".
name: ast.identifier('specificName'),
propertyName: ast.identifier('specificPropertyName'),
})

// will match:
// `import { specificName as specificPropertyName } from 'my-module'`
// `import { specificName } from 'my-module'`
```

### Example 1: Flexible Patterns
Expand All @@ -68,7 +152,7 @@ const flexibleMatcherWithRest = ast.importDeclaration(
ast.rest(ast.any()), // This will match any number of import specifiers in the import.
)

// would match:
// will match:
// `import { readFile } from 'fs'`
// `import { readFile, writeFile } from 'fs'`
// `import { type writeFile, createReadStream } from 'fs'`
Expand All @@ -85,7 +169,7 @@ const typeImportMatcher = ast.refine(
},
)

// would match:
// will match:
// `import type { MyType, MyOtherType } from 'my-module'`
// `import type { MyType as MyRenamedType } from 'another-module'`
```
Expand All @@ -94,7 +178,7 @@ const typeImportMatcher = ast.refine(

```ts
const functionReturningPromiseOfSpecificTypeMatcher = ast.node(SyntaxKind.FunctionDeclaration, {
name: ast.identifier('myFunction'), // Match a function named "myFunction".
name: ast.identifier('myFunction'), // Match a function declaration named "myFunction".
type: ast.node(SyntaxKind.TypeReference, {
// Match the return type.
typeName: ast.identifier('Promise'),
Expand All @@ -106,7 +190,7 @@ const functionReturningPromiseOfSpecificTypeMatcher = ast.node(SyntaxKind.Functi
}),
})

// would match:
// will match:
// `function myFunction(): Promise<MySpecificType> { ... }`
```

Expand Down

0 comments on commit 08c809b

Please sign in to comment.