超强大的结构搜索和替换 JavaScript 和 TypeScript,以自动化重构
$<name>
)$$<name>
)$$$<name>
)$Maybe(pattern)
$Or(...)
$And(...)
$Maybe<pattern>
$Or<...>
$And<...>
$Ordered
$Unordered
constructor(backend: Backend, paths: NodePath<any>[] | Match[], options?: { withCaptures?: Match[] })
.find(...)
( Astx
).closest(...)
( Astx
).destruct(...)
( Astx
)FindOptions
FindOptions.where
( { [captureName: string]: (path: Astx) => boolean }
).find(...).replace(...)
( void
).findImports(...)
( Astx
).addImports(...)
( Astx
).removeImports(...)
( boolean
).replaceImport(...).with(...)
( boolean
).remove()
( void
).matched
( this | null
).size()
( number
)[name: `$${string}` | `$$${string}` | `$$$${string}`]
( Astx
).placeholder
( string | undefined
).node
( Node
).path
( NodePath
).code
( string
).stringValue
( string
)[Symbol.iterator]
( Iterator<Astx>
).matches
( Match[]
).match
( Match
).paths
( NodePath[]
).nodes
( Node[]
).some(predicate)
( boolean
).every(predicate)
( boolean
).filter(iteratee)
( Astx
).map<T>(iteratee)
( T[]
).at(index)
( Astx
).withCaptures(...captures)
( Astx
).type
.path
.node
.paths
.nodes
.captures
.pathCaptures
.arrayCaptures
.arrayPathCaptures
.stringCaptures
exports.find
(可选)exports.where
(可选)exports.replace
(可选)exports.astx
(可选)exports.onReport
(可选)exports.finish
(可选)parser
parserOptions
prettier
简单的重构可能是乏味且重复的。例如,假设您想在代码库中进行以下更改:
// before:
rmdir ( 'old/stuff' )
rmdir ( 'new/stuff' , true )
// after:
rmdir ( 'old/stuff' )
rmdir ( 'new/stuff' , { force : true } )
手动更改一堆对rmdir
调用会很糟糕。您可以尝试使用正则表达式替换,但它很繁琐,并且不能很好地容忍空格和换行符,除非您在正则表达式上非常努力。您甚至可以使用jscodeshift
,但是对于像这样的简单情况来说它需要太长时间,并且开始感觉比必要的更难......
现在有一个更好的选择...您可以放心地使用astx
进行重构!
astx
--find ' rmdir($path, $force) '
--replace ' rmdir($path, { force: $force }) '
这是astx
模式的一个基本示例,它们只是 JS 或 TS 代码,可以包含占位符和其他特殊匹配结构; astx
查找与模式匹配的代码,接受任何表达式来代替$path
和$force
。然后, astx
用替换模式替换每个匹配项,用它捕获的表达式替换$path
( 'new/stuff'
) 和$force
( true
)。
但这仅仅是开始; astx
模式可能比这更复杂、更强大,对于真正高级的用例,它有一个直观的 API,您可以使用:
for ( const match of astx . find `rmdir($path, $force)` ) {
const { $path , $force } = match
// do stuff with $path.node, $path.code, etc...
}
遇到很多Do not access Object.prototype method 'hasOwnProperty' from target object
错误?
// astx.js
exports . find = `$a.hasOwnProperty($b)`
exports . replace = `Object.hasOwn($a, $b)`
最近因为工作原因,我想做出这样的改变:
// before
const pkg = OrgPackage ( {
subPackage : [
'services' ,
async ? 'async' : 'blocking' ,
... namespace ,
Names . ServiceType ( { resource , async } ) ,
] ,
} )
// after
const pkg = [
... OrgPackage ( ) ,
'services' ,
async ? 'async' : 'blocking' ,
... namespace ,
Names . ServiceType ( { resource , async } ) ,
]
通过列表匹配可以很简单地做到这一点:
// astx.js
exports . find = `OrgPackage({subPackage: [$$p]})`
exports . replace = `[...OrgPackage(), $$p]`
// astx.js
exports . find = `const $id = require('$source')`
exports . replace = `import $id from '$source'`
// astx.js
export const find = `if ($a) { if ($b) $body }`
export const replace = `if ($a && $b) $body`
在jscodeshift-add-imports
中,我有很多遵循以下模式的测试用例:
it ( `leaves existing default imports untouched` , function ( ) {
const code = `import Baz from 'baz'`
const root = j ( code )
const result = addImports ( root , statement `import Foo from 'baz'` )
expect ( result ) . to . deep . equal ( { Foo : 'Baz' } )
expect ( root . toSource ( ) ) . to . equal ( code )
} )
我想让它们更干,像这样:
it ( `leaves existing default imports untouched` , function ( ) {
testCase ( {
code : `import Baz from 'baz'` ,
add : `import Foo from 'baz'` ,
expectedCode : `import Baz from 'baz'` ,
expectedReturn : { Foo : 'Baz' } ,
} )
} )
这是上面的变换。 (当然,对于预期代码不同的情况等,我必须运行一些变体。)
exports . find = `
const code = $code
const root = j(code)
const result = addImports(root, statement`$add`)
expect(result).to.deep.equal($expectedReturn)
expect(root.toSource()).to.equal(code)
`
exports . replace = `
testCase({
code: $code,
add: `$add`,
expectedCode: $code,
expectedReturn: $expectedReturn,
})
`
经过大量的努力,我终于在 2022 年 12 月发布了版本 2?现在我正在开发 VSCode 扩展。之后我想制作一个文档网站来更好地说明如何使用astx
。
VSCode 扩展目前处于测试阶段,请尝试一下!
当我考虑这样做时,我发现了一种类似的工具,它启发了$
capture 语法。无论如何,我决定制作astx
有几个原因:
grasp -e 'setValue($k, $v, true)' -R 'setValueSilently({{k}}, {{v}})' file.js
jscodeshift
的 API,可以在 JS 中用于高级用例,而这些用例在 Grasp 中可能很尴尬/不可能所以astx
的哲学是:
如果您需要了解 AST 的结构,请将代码粘贴到 AST Explorer 中。
Astx 查找模式只是 JavaScript 或 TypeScript 代码,可能包含占位符通配符或其他特殊结构,例如$Or(A, B)
。一般来说,模式中非通配符或特殊结构的部分必须完全匹配。
例如,查找模式foo($a)
与使用单个参数对函数foo
任何调用相匹配。该参数可以是任何内容,并被捕获为$a
。
替换模式与查找模式几乎相同,只是占位符被替换为查找模式捕获到占位符名称中的任何内容,并且像$Or(A, B)
这样的特殊查找结构在替换模式中没有特殊含义。 (将来,可能会有特殊的替换结构对捕获的节点执行某种转换。)
例如,查找模式foo($a)
匹配foo(1 + 2)
,则替换模式foo({ value: $a })
将生成代码foo({ value: 1 + 2 })
。
一般来说,以$
开头的标识符是一个占位符,其功能类似于通配符。占位符分为三种类型:
$<name>
匹配任何单个节点(“节点占位符”)$$<name>
匹配连续的节点列表(“数组占位符”)$$$<name>
:匹配所有其他同级(“其余占位符”) <name>
(如果给出)必须以字母或数字开头;否则标识符将不会被视为占位符。
其余占位符 ( $$$
) 不能是有序列表占位符 ( $$
) 的同级。
除非占位符是匿名的,否则它将“捕获”匹配的节点,这意味着您可以在替换模式中使用相同的占位符将匹配的节点插入到生成的替换中。在 Node API 中,您还可以通过占位符名称访问捕获的 AST 路径/节点。
$<name>
)这些占位符匹配单个节点。例如,模式[$a, $b]
与具有两个元素的数组表达式匹配,并且这些元素被捕获为$a
和$b
。
$$<name>
)这些占位符匹配连续的节点列表。例如,模式[1, $$a, 2, $$b]
匹配第一个元素为1
,后续元素为2
数组表达式。 1
和前2
之间的任何元素都将捕获为$$a
,前2
个之后的元素将捕获为$$b
。
$$$<name>
)这些占位符与其他未匹配的兄弟姐妹相匹配。例如,模式[1, $$$a, 2]
匹配在任意索引处具有元素1
和2
数组表达式。任何其他元素(包括额外出现的1
和2
)都会被捕获为$$$a
。
您可以使用没有名称的占位符来匹配节点而不捕获它们。 $
将匹配任何单个节点, $$
将匹配连续的节点列表, $$$
将匹配所有其他兄弟节点。
如果多次使用相同的捕获占位符,则后续位置必须与首次出现该占位符时捕获的位置相匹配。
例如,模式foo($a, $a, $b, $b)
将仅匹配以下内容中的foo(1, 1, {foo: 1}, {foo: 1})
:
foo ( 1 , 1 , { foo : 1 } , { foo : 1 } ) // match
foo ( 1 , 2 , { foo : 1 } , { foo : 1 } ) // no match
foo ( 1 , 1 , { foo : 1 } , { bar : 1 } ) // no match
注意:数组捕获占位符 ( $$a
) 和其余捕获占位符 ( $$$a
) 目前不支持反向引用。
ObjectExpression
(也称为对象字面量)模式将以任意顺序与代码中具有相同属性的任何ObjectExpression
进行匹配。如果缺少或附加属性,它将不匹配。例如, { foo: 1, bar: $bar }
将匹配{ foo: 1, bar: 2 }
或{ bar: 'hello', foo: 1 }
但不匹配{ foo: 1 }
或{ foo: 1, bar: 2, baz: 3 }
.
您可以使用...$$captureName
来匹配其他属性,例如{ foo: 1, ...$$rest }
将匹配{ foo: 1 }
, { foo: 1, bar: 2 }
, { foo: 1, bar: 2, ...props }
等。附加属性将在match.arrayCaptures
/ match.arrayPathCaptures
中捕获,并且可以在替换表达式中传播。例如, astx.find`{ foo: 1, ...$$rest }`.replace`{ bar: 1, ...$$rest }`
将转换{ foo: 1, qux: {}, ...props }
进入{ bar: 1, qux: {}, ...props }
。
不是/^$$[a-z0-9]+$/i
形式的展开属性不是捕获占位符,例如{ ...foo }
将仅匹配{ ...foo }
和{ ...$_$foo }
只会匹配{ ...$$foo }
(前导$_
是$
的转义)。
目前无法按特定顺序匹配属性,但将来可能会添加。
在许多情况下,如果 AST 中有节点列表,您可以使用以$$
开头的占位符来匹配多个元素。例如, [$$before, 3, $$after]
将匹配任何包含元素3
数组表达式;前3
个之前的元素将在$$before
中捕获,前3
之后的元素将在$$after
中捕获。
这甚至适用于块语句。例如, function foo() { $$before; throw new Error('test'); $$after; }
将匹配包含throw new Error('test')
的function foo()
,并且该 throw 语句之前和之后的语句将分别在$$before
和$$after
中捕获。
在某些情况下,列表匹配将默认排序,而在某些情况下,它将是无序的。例如, ObjectExpression
属性匹配默认情况下是无序的,如下表所示。使用$$
占位符或特殊的$Ordered
占位符将强制进行有序匹配。使用$$$
占位符或特殊的$Unordered
占位符将强制进行无序匹配。
如果您使用以$$$
开头的占位符,它将被视为“其余”捕获,并且匹配表达式的所有其他元素将按顺序进行匹配。例如, import {a, b, $$$rest} from 'foo'
将匹配import {c, b, d, e, a} from 'foo'
,将说明符c
、 d
和e
放入$$$rest
占位符。
其余占位符 ( $$$
) 不能是有序列表占位符 ( $$
) 的同级。
一些标记为 TODO 的项目可能确实有效,但未经测试。
类型 | 支持列表匹配吗? | 默认无序? | 笔记 |
---|---|---|---|
ArrayExpression.elements | ✅ | ||
ArrayPattern.elements | ✅ | ||
BlockStatement.body | ✅ | ||
CallExpression.arguments | ✅ | ||
Class(Declaration/Expression).implements | ✅ | ✅ | |
ClassBody.body | ✅ | ✅ | |
ComprehensionExpression.blocks | 待办事项 | ||
DeclareClass.body | 待办事项 | ✅ | |
DeclareClass.implements | 待办事项 | ✅ | |
DeclareExportDeclaration.specifiers | 待办事项 | ✅ | |
DeclareInterface.body | 待办事项 | ||
DeclareInterface.extends | 待办事项 | ||
DoExpression.body | 待办事项 | ||
ExportNamedDeclaration.specifiers | ✅ | ✅ | |
Function.decorators | 待办事项 | ||
Function.params | ✅ | ||
FunctionTypeAnnotation/TSFunctionType.params | ✅ | ||
GeneratorExpression.blocks | 待办事项 | ||
ImportDeclaration.specifiers | ✅ | ✅ | |
(TS)InterfaceDeclaration.body | 待办事项 | ✅ | |
(TS)InterfaceDeclaration.extends | 待办事项 | ✅ | |
IntersectionTypeAnnotation/TSIntersectionType.types | ✅ | ✅ | |
JSX(Element/Fragment).children | ✅ | ||
JSX(Opening)Element.attributes | ✅ | ✅ | |
MethodDefinition.decorators | 待办事项 | ||
NewExpression.arguments | ✅ | ||
ObjectExpression.properties | ✅ | ✅ | |
ObjectPattern.decorators | 待办事项 | ||
ObjectPattern.properties | ✅ | ✅ | |
(ObjectTypeAnnotation/TSTypeLiteral).properties | ✅ | ✅ | 使用$a: $ 匹配一个属性,使用$$a: $ 或$$$a: $ 匹配多个属性 |
Program.body | ✅ | ||
Property.decorators | 待办事项 | ||
SequenceExpression | ✅ | ||
SwitchCase.consequent | ✅ | ||
SwitchStatement.cases | 待办事项 | ||
TemplateLiteral.quasis/expressions | ❓ 不确定我是否能想出语法 | ||
TryStatement.guardedHandlers | 待办事项 | ||
TryStatement.handlers | 待办事项 | ||
TSFunctionType.parameters | ✅ | ||
TSCallSignatureDeclaration.parameters | 待办事项 | ||
TSConstructorType.parameters | 待办事项 | ||
TSConstructSignatureDeclaration.parameters | 待办事项 | ||
TSDeclareFunction.params | 待办事项 | ||
TSDeclareMethod.params | 待办事项 | ||
EnumDeclaration.body/TSEnumDeclaration.members | 待办事项 | ✅ | |
TSIndexSignature.parameters | 待办事项 | ||
TSMethodSignature.parameters | 待办事项 | ||
TSModuleBlock.body | 待办事项 | ||
TSTypeLiteral.members | ✅ | ✅ | |
TupleTypeAnnotation/TSTupleType.types | ✅ | ||
(TS)TypeParameterDeclaration.params | ✅ | ||
(TS)TypeParameterInstantiation.params | ✅ | ||
UnionTypeAnnotation/TSUnionType.types | ✅ | ✅ | |
VariableDeclaration.declarations | ✅ | ||
WithStatement.body | 谁使用 with 语句... |
像'$foo'
这样的占位符字符串将匹配任何字符串并将其内容捕获到match.stringCaptures.$foo
中。相同的转义规则适用于标识符。这也适用于像`$foo`
这样的模板文字和像doSomething`$foo`
这样的标记模板文字。
这对于处理导入语句很有帮助。例如,请参阅将 require 语句转换为导入。
模式中的空注释 ( /**/
) 将“提取”节点进行匹配。例如,模式const x = { /**/ $key: $value }
将仅将ObjectProperty
节点与$key: $value
进行匹配。
解析器无法自行解析$key: $value
或知道您的意思是ObjectProperty
,而不是像const x: number = 1
中的x: number
之类的不同内容,因此使用/**/
可以让您解决这个问题。您可以使用它来匹配本身不是有效表达式或语句的任何节点类型。例如, type T = /**/ Array<number>
将匹配Array<number>
类型注释。
/**/
也适用于替换模式。
$Maybe(pattern)
匹配给定表达式或不匹配其位置的节点。例如, let $a = $Maybe(2)
将匹配let foo = 2
和let foo
(没有初始化程序),但不会匹配let foo = 3
。
$Or(...)
匹配至少匹配一种给定模式的节点。例如$Or(foo($$args), {a: $value})
将匹配对foo
和仅具有a
属性的对象文字的调用。
$And(...)
匹配与所有给定模式匹配的节点。这对于缩小可以捕获到给定占位符的节点类型的范围非常有用。例如, let $a = $And($init, $a + $b)
将匹配初始化器与$a + $b
匹配的let
声明,并将初始化器捕获为$init
。
$Maybe<pattern>
匹配给定的类型注释或不匹配其位置的节点。例如, let $a: $Maybe<number>
将匹配let foo: number
和let foo
(没有类型注释),但不会let foo: string``let foo: string
。
$Or<...>
匹配至少与一个给定类型注释匹配的节点。例如let $x: $Or<number[], string[]>
将匹配number[]
或string[]
类型的let
声明。
$And<...>
匹配与所有给定类型注释匹配的节点。这对于缩小可以捕获到给定占位符的节点类型的范围非常有用。例如, let $a: $And<$type, $elem[]>
将匹配类型注释与$elem[]
匹配的let
声明,并将类型注释捕获为$type
。
$Ordered
强制模式以相同的顺序匹配同级节点。
$Unordered
强制模式以任意顺序匹配同级节点。
import { NodePath } from 'astx'
这是与ast-types
相同的NodePath
接口,对方法类型定义进行了一些改进。 astx
使用ast-types
来遍历代码,希望将来支持不同的解析器。
import { Astx } from 'astx'
constructor(backend: Backend, paths: NodePath<any>[] | Match[], options?: { withCaptures?: Match[] })
backend
是正在使用的解析器/生成器实现。
paths
指定您希望Astx
方法搜索/操作的NodePath
或Match
。
.find(...)
( Astx
)在此实例的起始路径中查找给定模式的匹配项,并返回包含匹配项的Astx
实例。
如果您在传递给转换函数的初始实例上调用astx.find('foo($$args)')
,它将查找文件中对foo
的所有调用,并在新的Astx
实例中返回这些匹配项。
返回实例上的方法将仅在匹配的路径上运行。
例如,如果您执行astx.find('foo($$args)').find('$a + $b')
,第二个find
调用将仅在与foo($$args)
匹配的内容中搜索$a + $b
foo($$args)
,而不是文件中的任何位置。
您可以将.find
作为方法或标记模板文字调用:
.find`pattern`
.find(pattern: string | string[] | Node | Node[] | NodePath | NodePath[] | ((wrapper: Astx) => boolean), options?: FindOptions)
如果将模式作为字符串给出,则它必须是有效的表达式或语句。否则它应该是您已经解析或构造的有效 AST 节点。您可以在标记的模板文字中插入字符串、AST 节点、AST 节点数组和Astx
实例。
例如,您可以执行astx.find`${t.identifier('foo')} + 3`
。
或者您可以通过执行以下操作来匹配多个语句
astx . find `
const $a = $b;
$$c;
const $d = $a + $e;
`
这将匹配(例如)语句const foo = 1; const bar = foo + 5;
,它们之间有任意数量的语句。
.closest(...)
( Astx
)与.find()
类似,但向上搜索 AST 祖先而不是向下搜索后代;查找与给定模式匹配的每个输入路径的最近封闭节点。
.destruct(...)
( Astx
)与.find()
类似,但不会根据模式测试输入路径的后代;只有与模式匹配的输入路径才会包含在结果中。
FindOptions
具有以下可选属性的对象:
FindOptions.where
( { [captureName: string]: (path: Astx) => boolean }
)节点捕获的 where 条件。例如,如果您的查找模式是$a()
,则可以使用{ where: { $a: astx => /foo|bar/.test(astx.node.name) } }
,它仅匹配零参数调用到foo
或bar
。
.find(...).replace(...)
( void
)查找并替换root
中给定模式的匹配项。
您可以通过多种不同的方式调用.replace
。您可以通过上述任何方式调用.find
。
.find(...).replace`replacement`
.find(...).replace(replacement: string | string | Node | Node[])
.find(...).replace(replacement: (match: Astx, parse: ParsePattern) => string)
.find(...).replace(replacement: (match: Astx, parse: ParsePattern) => Node | Node[])
如果将替换作为字符串给出,则它必须是有效的表达式或语句。您可以将替换作为您已经解析或构造的 AST 节点。或者您可以提供一个替换函数,该函数将在每次匹配时调用,并且必须返回字符串或Node | Node[]
(您可以使用作为第二个参数提供的parse
标记模板字符串函数将代码解析为字符串。例如,您可以在所有零参数函数调用中将函数名称大写( foo(); bar()
变为FOO(); BAR()
) 与此:
astx
.find`$fn()`
.replace(({ captures: { $fn } }) => `${$fn.name.toUpperCase()}()`)
.findImports(...)
( Astx
) .find()
的便捷版本,用于查找允许额外说明符的导入,如果请求类型导入,则匹配同名的值导入等。
例如.findImports`import $a from 'a'`
将匹配import A, { b, c } from 'a'
或import { default as a } from 'a'
,捕获$a
,而.find`import $a from 'a'`
与这两种情况都不匹配。
该模式必须仅包含 import 语句。
.addImports(...)
( Astx
)与.findImports()
类似,但添加任何未找到的导入。例如给出源代码:
import { foo , type bar as qux } from 'foo'
import 'g'
以及操作
const { $bar } = astx . addImports `
import type { bar as $bar } from 'foo'
import FooDefault from 'foo'
import * as g from 'g'
`
输出将是
import FooDefault , { foo , type bar as qux } from 'foo'
import * as g from 'g'
使用$bar
捕获标识符qux
。
.removeImports(...)
( boolean
)采用与.findImports()
相同格式的导入语句,但删除所有给定的说明符。
.replaceImport(...).with(...)
( boolean
)将单个导入说明符替换为另一个导入说明符。例如给定输入
import { Match , Route , Location } from 'react-router-dom'
import type { History } from 'history'
及操作
astx . replaceImport `
import { Location } from 'react-router-dom'
` . with `
import type { Location } from 'history'
`
输出将是
import { Match , Route } from 'react-router-dom'
import type { History , Location } from 'history'
查找和替换模式必须都包含带有单个说明符的单个导入语句。
.remove()
( void
)从此Astx
实例中删除.find()
或聚焦捕获中的匹配项。
.matched
( this | null
)如果该Astx
实例至少有一个匹配项,则返回该实例,否则返回null
。
由于.find()
、 .closest()
和.destruct()
始终返回一个Astx
实例,即使没有匹配项,如果您只想在存在时定义值,则可以使用.find(...).matched
至少一场比赛。
.size()
( number
)返回返回此实例的.find()
或.closest()
调用中的匹配项数。
[name: `$${string}` | `$$${string}` | `$$$${string}`]
( Astx
)获取专注于具有给定name
捕获的Astx
实例。
例如,您可以执行以下操作:
for ( const { $v } of astx . find `process.env.$v` ) {
report ( $v . code )
}
.placeholder
( string | undefined
)此实例代表的占位符的名称。例如:
const match = astx . find `function $fn($$params) { $$body }`
console . log ( match . placeholder ) // undefined
const { $fn , $$params } = match
console . log ( $fn . placeholder ) // $fn
console . log ( $$params . placeholder ) // $$params
.node
( Node
)返回第一个匹配的第一个节点。如果没有匹配则抛出错误。
.path
( NodePath
)返回第一个匹配的第一个路径。如果没有匹配则抛出错误。
.code
( string
)从第一个匹配的第一个节点生成代码。如果没有匹配则抛出错误。
.stringValue
( string
)如果聚焦捕获是字符串捕获,则返回第一个节点的字符串值。如果没有匹配则抛出错误。
[Symbol.iterator]
( Iterator<Astx>
)迭代每场比赛,为每场比赛返回一个Astx
实例。
.matches
( Match[]
)从返回此实例的.find()
或.closest()
调用获取匹配项。
.match
( Match
)从返回此实例的.find()
或.closest()
调用中获取第一个匹配项。
如果没有匹配则抛出错误。
.paths
( NodePath[]
)返回.find()
和.closest()
将在其中搜索的路径。如果此实例由.find()
或.closest()
返回,则这些是与搜索模式匹配的节点的路径。
.nodes
( Node[]
)返回.find()
和.closest()
将在其中搜索的节点。如果此实例是由.find()
或.closest()
返回的,则这些是与搜索模式匹配的节点。
.some(predicate)
( boolean
)返回false
除非predicate
对于至少一项匹配返回 true。
iteratee
是使用match: Astx, index: number, parent: Astx
调用的函数,并返回true
或false
。
.every(predicate)
( boolean
)返回true
除非predicate
对于至少一个匹配项返回 false。
iteratee
是使用match: Astx, index: number, parent: Astx
调用的函数,并返回true
或false
。
.filter(iteratee)
( Astx
)过滤匹配项。
iteratee
是使用match: Astx, index: number, parent: Astx
调用的函数,并返回true
或false
。只有iteratee
返回true
的匹配才会包含在结果中。
.map<T>(iteratee)
( T[]
)映射匹配项。
iteratee
是使用match: Astx, index: number, parent: Astx
调用的函数,并返回要包含在结果数组中的值。
.at(index)
( Astx
)选择给定index
处的匹配项。
.withCaptures(...captures)
( Astx
)返回一个Astx
实例,除了此实例中存在的捕获之外,还包含来自给定...captures
。
您可以传递以下类型的参数:
Astx
实例 - 将包含该实例的所有捕获。Astx[placeholder]
实例 - 将包含给定placeholder
符的捕获。{ $name: Astx[placeholder] }
- 捕获给定的placeholder
,重命名为$name
。Match
对象 import { type Match } from 'astx'
.type
匹配的类型: 'node'
或'nodes'
。
.path
匹配节点的NodePath
。如果type
是'nodes'
,则这将是paths[0]
。
.node
匹配的Node
。如果type
是'nodes'
,则这将是nodes[0]
。
.paths
匹配节点的NodePaths
。
.nodes
匹配的Node
。
.captures
从匹配模式中的占位符捕获的Node
。例如,如果模式是foo($bar)
, .captures.$bar
将是第一个参数的Node
。
.pathCaptures
NodePath
从匹配模式中的占位符中捕获。例如,如果模式为foo($bar)
,则.pathCaptures.$bar
将是第一个参数的NodePath
。
.arrayCaptures
Node[]
从匹配模式中的数组占位符中捕获。例如,如果模式是foo({ ...$bar })
, .arrayCaptures.$bar
将是对象属性的Node[]
。
.arrayPathCaptures
NodePath[]
从匹配模式中的数组占位符中捕获。例如,如果模式是foo({ ...$bar })
, .pathArrayCaptures.$bar
将是对象属性的NodePath[]
。
.stringCaptures
从匹配模式中的字符串占位符捕获的字符串值。例如,如果模式是import foo from '$foo'
,则stringCaptures.$foo
将是导入路径。
与jscodeshift
一样,您可以在.ts
或.js
文件中放置执行转换的代码(默认为工作目录中的astx.ts
或astx.js
,除非您使用-t
CLI 选项指定不同的文件)。
不过,转换文件 API 与jscodeshift
有点不同。您可以进行以下导出:
exports.find
(可选)要在正在转换的文件中查找的模式的代码字符串或 AST 节点。
exports.where
(可选)在exports.find
中捕获占位符的Where 条件。有关详细信息,请参阅FindOptions.where
( { [captureName: string]: (path: NodePath<any>) => boolean }
)。
exports.replace
(可选)用于替换exports.find
匹配项的代码字符串、AST 节点或替换函数。
函数参数与.find().replace()
中描述的相同。
exports.astx
(可选)使用Astx
API 执行任意转换的函数。它被具有以下属性的对象调用:
file
( string
) - 正在转换的文件的路径source
( string
) - 正在转换的文件的源代码astx
( Astx
) - Astx
API 实例t
( AstTypes
) - 所选解析器的ast-types
定义expression
- 用于将代码解析为表达式的标记模板文字statement
- 用于将代码解析为语句的标记模板文字statements
- 用于将代码解析为语句数组的标记模板文字report
( (message: unknown) => void
)mark
( (...matches: Astx[]) => void
) - 标记要显示在 vscode-astx 等的匹配列表中的给定匹配与jscodeshift
不同,您的转换函数可以是异步的,并且它不必返回转换后的代码,但您可以返回string
。您还可以返回null
来跳过该文件。
exports.onReport
(可选)如果您从exports.astx
函数调用report(x)
,则将使用onReport({ file, report: x })
调用该函数。
如果使用多个工作线程, onReport
将在父进程中调用,因此报告消息必须是可序列化的值。这允许转换从所有工作人员收集报告(然后可能在finish
时对他们做一些事情)。
如果onReport
返回一个Promise
它将被等待。
exports.finish
(可选)这将在对所有输入文件运行转换后调用。
如果您使用多个工作线程, finish
将在父进程中调用。您可以使用onReport
和finish
一起从每个输入文件中收集信息,并在最后生成某种组合输出。
如果finish
返回一个Promise
它将被等待。
astx
支持在以下位置进行配置(通过cosmiconfig
):
astx
属性.astxrc
文件.astxrc.json
、 .astxrc.yaml
、 .astxrc.yml
、 .astxrc.js
或.astxrc.cjs
文件astx.config.js
或astx.config.cjs
CommonJS 模块如果您的代码库采用 Prettier 格式,我建议您首先尝试以下操作:
{
"parser" : " babel/auto " ,
"parserOptions" : {
"preserveFormat" : " generatorHack "
}
}
(或作为 CLI 选项)
--parser babel/auto --parserOptions '{"preserveFormat": "generatorHack"}'
如果失败,您可以尝试parser: 'recast/babel/auto'
或非/auto
解析器。
您的里程可能会因recast
有所不同;他们只是无法足够快地使其与 JS 和 TS 中的新语法功能保持同步,而且我已经多次看到它输出无效语法。
从现在开始,我将使用@babel/generator
或prettier
来开发一个可靠的解决方案来打印修改后的 AST,并使用钩子将原始源逐字用于未修改的节点。
parser
要使用的解析器。选项:
babel/auto
(默认)babel
(比babel/auto
更快,但使用默认解析选项,您可能需要配置parserOptions
)recast/babel
recast/babel/auto
babel/auto
自动从你的 babel 配置中确定解析选项(如果存在)。 babel
使用固定解析选项,因此它比babel/auto
更快,但您可能必须配置parserOptions
。 recast/babel(/auto)
选项使用recast
来保留格式。我在某些文件上看到recast
输出无效语法,因此请谨慎使用。
parserOptions
传递给解析器的选项。现在这只是@babel/parser
选项加上以下附加选项:
preserveFormat
(适用于: babel
、 babel/auto
) preserveFormat: 'generatorHack'
使用实验性黑客通过劫持内部@babel/generator
API 来保留所有未更改节点的格式。prettier
如果false
,请勿尝试使用prettier
重新格式化转换后的源代码。默认为true
。
Astx 包含一个用于执行转换的 CLI。 CLI 将处理给定的文件,然后打印出要更改的内容的差异,并提示您确认要写入更改。
默认情况下,它将使用项目中安装的版本和项目的 babel 配置(如果有)来解析 babel。如果您想使用recast
来尝试保留输出中的格式,您可以传递--parser recast/babel
,但我有时会在其输出中看到语法错误。
与jscodeshift
不同,如果您的项目中安装了prettier
,它将使用prettier
格式化转换后的代码。
Usage:
astx -f <code> [<files...>] [<directories...>]
Searches for the -f pattern in the given files and directories
and prints out the matches in context
astx -f <code> -r <code> [<files...>] [<directories...>]
Quick search and replace in the given files and directories
(make sure to quote code)
Example:
astx -f 'rmdir($path, $force)' -r 'rmdir($path, { force: $force })' src
astx -t <transformFile> [<files ...>] [<directories ...>]
Applies a transform file to the given files and directories
astx [<files ...>] [<directories ...>]
Applies the default transform file (astx.ts or astx.js in working directory)
to the given files and directories
Options:
--help Show help [boolean]
--version Show version number [boolean]
-t, --transform path to the transform file. Can be either a local path or
url. Defaults to ./astx.ts or ./astx.js if --find isn't
given
--parser parser to use (options: babel, babel/auto, recast/babel,
recast/babel/auto) [string]
--parserOptions options for parser [string]
-f, --find search pattern [string]
-r, --replace replace pattern [string]
-y, --yes don't ask for confirmation before writing changes
[boolean]
--gitignore ignore gitignored files [boolean] [default: true]
--workers number of worker threads to use [number]