Супермощный структурный поиск и замена для 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,
})
`
Я только что наконец-то выпустил версию 2 в декабре 2022 года после тонны тяжелой работы? Сейчас я работаю над расширением VSCode. После этого я хочу создать веб-сайт с документацией, который лучше иллюстрирует, как использовать astx
.
Расширение VSCode в настоящее время находится в стадии бета-тестирования, но попробуйте!
Пока я думал об этом, я обнаружил, что это понятный инструмент, который вдохновил синтаксис $
capture. Есть несколько причин, по которым я все равно решил сделать astx
:
grasp -e 'setValue($k, $v, true)' -R 'setValueSilently({{k}}, {{v}})' file.js
не соответствует синтаксису шаблона поиска: grasp -e 'setValue($k, $v, true)' -R 'setValueSilently({{k}}, {{v}})' file.js
jscodeshift
который я мог бы использовать в JS для сложных случаев использования, которые, вероятно, неудобны/невозможны в Grasp. Итак, философия astx
такова:
Вставьте свой код в AST Explorer, если вам нужно узнать о структуре AST.
Шаблоны поиска Astx — это просто код JavaScript или TypeScript, который может содержать подстановочные знаки или другие специальные конструкции, такие как $Or(A, B)
. Вообще говоря, части шаблона, которые не являются подстановочными знаками или специальными конструкциями, должны точно совпадать.
Например, шаблон поиска foo($a)
соответствует любому вызову функции foo
с одним аргументом. Аргумент может быть любым и записывается как $a
.
Шаблоны замены почти идентичны шаблонам поиска, за исключением того, что заполнители заменяются тем, что было зафиксировано в имени заполнителя шаблоном поиска, а специальные конструкции поиска, такие как $Or(A, B)
не имеют особого значения в шаблонах замены. (В будущем могут появиться специальные конструкции replace, которые выполняют какое-то преобразование захваченных узлов.)
Например, шаблон поиска 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 }
(ведущий $_
является escape-символом $
).
В настоящее время нет способа сопоставлять свойства в определенном порядке, но он может быть добавлен в будущем.
Во многих случаях, когда в AST есть список узлов, вы можете сопоставить несколько элементов с помощью заполнителя, начинающегося с $$
. Например, [$$before, 3, $$after]
будет соответствовать любому выражению массива, содержащему элемент 3
; элементы до первых 3
будут записаны в $$before
, а элементы после первых 3
будут записаны в $$after
.
Это работает даже с операторами блоков. Например, function foo() { $$before; throw new Error('test'); $$after; }
будет соответствовать function foo()
, которая содержит throw new Error('test')
, а операторы до и после этого оператора 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 | TODO | ||
DeclareClass.body | TODO | ✅ | |
DeclareClass.implements | TODO | ✅ | |
DeclareExportDeclaration.specifiers | TODO | ✅ | |
DeclareInterface.body | TODO | ||
DeclareInterface.extends | TODO | ||
DoExpression.body | TODO | ||
ExportNamedDeclaration.specifiers | ✅ | ✅ | |
Function.decorators | TODO | ||
Function.params | ✅ | ||
FunctionTypeAnnotation/TSFunctionType.params | ✅ | ||
GeneratorExpression.blocks | TODO | ||
ImportDeclaration.specifiers | ✅ | ✅ | |
(TS)InterfaceDeclaration.body | TODO | ✅ | |
(TS)InterfaceDeclaration.extends | TODO | ✅ | |
IntersectionTypeAnnotation/TSIntersectionType.types | ✅ | ✅ | |
JSX(Element/Fragment).children | ✅ | ||
JSX(Opening)Element.attributes | ✅ | ✅ | |
MethodDefinition.decorators | TODO | ||
NewExpression.arguments | ✅ | ||
ObjectExpression.properties | ✅ | ✅ | |
ObjectPattern.decorators | TODO | ||
ObjectPattern.properties | ✅ | ✅ | |
(ObjectTypeAnnotation/TSTypeLiteral).properties | ✅ | ✅ | Используйте $a: $ для сопоставления одного свойства, $$a: $ или $$$a: $ для сопоставления нескольких свойств. |
Program.body | ✅ | ||
Property.decorators | TODO | ||
SequenceExpression | ✅ | ||
SwitchCase.consequent | ✅ | ||
SwitchStatement.cases | TODO | ||
TemplateLiteral.quasis/expressions | ❓ не уверен, смогу ли придумать синтаксис | ||
TryStatement.guardedHandlers | TODO | ||
TryStatement.handlers | TODO | ||
TSFunctionType.parameters | ✅ | ||
TSCallSignatureDeclaration.parameters | TODO | ||
TSConstructorType.parameters | TODO | ||
TSConstructSignatureDeclaration.parameters | TODO | ||
TSDeclareFunction.params | TODO | ||
TSDeclareMethod.params | TODO | ||
EnumDeclaration.body/TSEnumDeclaration.members | TODO | ✅ | |
TSIndexSignature.parameters | TODO | ||
TSMethodSignature.parameters | TODO | ||
TSModuleBlock.body | TODO | ||
TSTypeLiteral.members | ✅ | ✅ | |
TupleTypeAnnotation/TSTupleType.types | ✅ | ||
(TS)TypeParameterDeclaration.params | ✅ | ||
(TS)TypeParameterInstantiation.params | ✅ | ||
UnionTypeAnnotation/TSUnionType.types | ✅ | ✅ | |
VariableDeclaration.declarations | ✅ | ||
WithStatement.body | кто использует операторы... |
Строка, которая является просто заполнителем, например '$foo'
будет соответствовать любой строке и записывать ее содержимое в match.stringCaptures.$foo
. Применяются те же правила экранирования, что и для идентификаторов. Это также работает для литералов шаблонов, таких как `$foo`
, и литералов шаблонов с тегами, таких как doSomething`$foo`
.
Это может быть полезно при работе с операторами импорта. Например, см. Преобразование операторов require в импорт.
Пустой комментарий ( /**/
) в шаблоне «извлечет» узел для сопоставления. Например, шаблон const x = { /**/ $key: $value }
будет просто сопоставлять узлы ObjectProperty
с $key: $value
.
Анализатор не сможет анализировать $key: $value
сам по себе или знать, что вы имеете в виду ObjectProperty
, а не что-то другое, например x: number
в const x: number = 1
, поэтому использование /**/
позволяет вам чтобы обойти это. Вы можете использовать это для сопоставления любого типа узла, который сам по себе не является допустимым выражением или оператором. Например, 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)
будет соответствовать объявлениям let
, где инициализатор соответствует $a + $b
, и захватывать инициализатор как $init
.
$Maybe<pattern>
Соответствует либо данной аннотации типа, либо отсутствию узла на своем месте. Например, let $a: $Maybe<number>
будет соответствовать let foo: number
и let foo
(без аннотации типа), но не let foo: string``let foo: string
.
$Or<...>
Соответствует узлам, которые соответствуют хотя бы одной из заданных аннотаций типа. Например, let $x: $Or<number[], string[]>
будет соответствовать объявлениям let
типа number[]
или string[]
.
$And<...>
Соответствует узлам, которые соответствуют всем заданным аннотациям типа. Это в основном полезно для сужения типов узлов, которые можно захватить в данный заполнитель. Например, let $a: $And<$type, $elem[]>
будет соответствовать объявлениям let
, где аннотация типа соответствует $elem[]
, и захватывать аннотацию типа как $type
.
$Ordered
Заставляет шаблон соответствовать одноуровневым узлам в том же порядке.
$Unordered
Заставляет шаблон соответствовать одноуровневым узлам в любом порядке.
import { NodePath } from 'astx'
Это тот же интерфейс NodePath
, что и ast-types
, с некоторыми улучшениями в определениях типов методов. astx
использует ast-types
для обхода кода в надежде на поддержку различных парсеров в будущем.
import { Astx } from 'astx'
constructor(backend: Backend, paths: NodePath<any>[] | Match[], options?: { withCaptures?: Match[] })
backend
— это используемая реализация синтаксического анализатора/генератора.
paths
указывает NodePath
или Match
, с которыми вы хотите, чтобы методы Astx
выполняли поиск/работу.
.find(...)
( Astx
) Находит совпадения для данного шаблона в начальных путях этого экземпляра и возвращает экземпляр Astx
, содержащий совпадения.
Если вы вызовете astx.find('foo($$args)')
в исходном экземпляре, переданном вашей функции преобразования, он найдет все вызовы foo
в файле и вернет эти совпадения в новом экземпляре Astx
.
Методы возвращаемого экземпляра будут работать только с совпадающими путями.
Например, если вы выполняете astx.find('foo($$args)').find('$a + $b')
, второй вызов find
будет искать только $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 }
) Где условия для захвата узла. Например, если ваш шаблон поиска — $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'`
не будет соответствовать ни одному из этих случаев.
Шаблон должен содержать только операторы импорта.
.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
) Удаляет совпадения из .find()
или фокусированного захвата(ов) в этом экземпляре Astx
.
.matched
( this | null
) Возвращает этот экземпляр Astx
, если он имеет хотя бы одно совпадение, в противном случае возвращает null
.
Поскольку .find()
, .closest()
и .destruct()
всегда возвращают экземпляр Astx
, даже если совпадений не было, вы можете использовать .find(...).matched
если вам нужно только определенное значение, когда есть был хотя бы один матч.
.size()
( number
) Возвращает количество совпадений из вызова .find()
или .closest()
, который вернул этот экземпляр.
[name: `$${string}` | `$$${string}` | `$$$${string}`]
( Astx
) Получает экземпляр Astx
ориентированный на захват(ы) с заданным name
.
Например, вы можете сделать:
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
unelss не возвращает 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
s.
.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
. Дополнительную информацию см. в FindOptions.where
( { [captureName: string]: (path: NodePath<any>) => boolean }
).
exports.replace
(необязательно) Строка кода, узел AST или функция замены для замены совпадений с файлом exports.find
.
Аргументы функции такие же, как описано в .find().replace()
.
exports.astx
(необязательно) Функция для выполнения произвольного преобразования с использованием API Astx
. Он вызывается с объектом со следующими свойствами:
file
( string
) — Путь к преобразуемому файлуsource
( string
) — Исходный код преобразуемого файла.astx
( Astx
) — экземпляр API Astx
t
( AstTypes
) — определения ast-types
для выбранного парсераexpression
— помеченный литерал шаблона для анализа кода как выраженияstatement
— помеченный литерал шаблона для анализа кода как оператораstatements
— помеченный литерал шаблона для анализа кода как массива операторов.report
( (message: unknown) => void
)mark
( (...matches: Astx[]) => void
) — отмечает указанные совпадения для отображения в списке совпадений vscode-astx и т. д. В отличие от jscodeshift
, ваша функция преобразования может быть асинхронной, и ей не обязательно возвращать преобразованный код, но вы можете вернуть string
. Вы также можете вернуть null
чтобы пропустить файл.
exports.onReport
(необязательно) Если вы вызываете report(x)
из функции exports.astx
, она будет вызываться с помощью onReport({ file, report: x })
.
Если вы используете несколько рабочих потоков, onReport
будет вызываться в родительском процессе, поэтому сообщение отчета должно быть сериализуемым значением. Это позволяет преобразованию собирать отчеты от всех воркеров (а затем, возможно, что-то делать с ними в finish
).
Если onReport
возвращает Promise
, оно будет ожидаемо.
exports.finish
(необязательно)Это будет вызвано после того, как преобразование будет выполнено для всех входных файлов.
Если вы используете несколько рабочих потоков, finish
будет вызван в родительском процессе. Вы можете использовать onReport
и finish
вместе, чтобы собрать информацию из каждого входного файла и в конце создать какой-то объединенный вывод.
Если finish
вернет Promise
оно будет ожидаемо.
astx
поддерживает настройку в следующих местах (через cosmiconfig
):
astx
в package.json.astxrc
в формате JSON или YAML..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'
использует экспериментальный хак для сохранения формата всех неизмененных узлов путем перехвата внутреннего API @babel/generator
.prettier
Если false
, не пытайтесь использовать prettier
для переформатирования преобразованного исходного кода. По умолчанию true
.
Astx включает CLI для выполнения преобразований. CLI обработает данные файлы, затем распечатает разницу того, что будет изменено, и предложит вам подтвердить, что вы хотите записать изменения.
По умолчанию он будет анализировать с помощью Babel, используя версию, установленную в вашем проекте, и конфигурацию Babel вашего проекта, если таковая имеется. Вы можете передать --parser recast/babel
если хотите использовать recast
чтобы попытаться сохранить форматирование вывода, но я иногда вижу синтаксические ошибки в его выводе.
В отличие от 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]