Pesquisa estrutural super poderosa e substituição para JavaScript e TypeScript para automatizar sua refatoração
$<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
(opcional)exports.where
(opcional)exports.replace
(opcional)exports.astx
(opcional)exports.onReport
(opcional)exports.finish
(opcional)parser
parserOptions
prettier
Refatoradores simples podem ser tediosos e repetitivos. Por exemplo, digamos que você queira fazer a seguinte alteração em uma base de código:
// before:
rmdir ( 'old/stuff' )
rmdir ( 'new/stuff' , true )
// after:
rmdir ( 'old/stuff' )
rmdir ( 'new/stuff' , { force : true } )
Alterar um monte de chamadas para rmdir
manualmente seria uma droga. Você poderia tentar usar a substituição de regex, mas é complicado e não toleraria bem espaços em branco e quebras de linha, a menos que você trabalhasse muito no regex. Você poderia até usar jscodeshift
, mas demora muito para casos simples como esse, e começa a parecer mais difícil do que o necessário...
Agora existe uma opção melhor... você pode refatorar com confiança usando astx
!
astx
--find ' rmdir($path, $force) '
--replace ' rmdir($path, { force: $force }) '
Este é um exemplo básico de padrões astx
, que são apenas códigos JS ou TS que podem conter espaços reservados e outras construções especiais de correspondência; astx
procura código que corresponda ao padrão, aceitando qualquer expressão no lugar de $path
e $force
. Em seguida, astx
substitui cada correspondência pelo padrão de substituição, substituindo as expressões capturadas por $path
( 'new/stuff'
) e $force
( true
).
Mas isto é apenas o começo; Os padrões astx
podem ser muito mais complexos e poderosos do que isso e, para casos de uso realmente avançados, possuem uma API intuitiva que você pode usar:
for ( const match of astx . find `rmdir($path, $force)` ) {
const { $path , $force } = match
// do stuff with $path.node, $path.code, etc...
}
Tem muitos erros Do not access Object.prototype method 'hasOwnProperty' from target object
?
// astx.js
exports . find = `$a.hasOwnProperty($b)`
exports . replace = `Object.hasOwn($a, $b)`
Recentemente, para o trabalho, eu queria fazer esta alteração:
// 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 } ) ,
]
Isso é simples de fazer com correspondência de lista:
// 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`
Em jscodeshift-add-imports
tive vários casos de teste seguindo este padrão:
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 )
} )
Queria deixá-los mais SECOS, assim:
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' } ,
} )
} )
Aqui estava uma transformação para o acima. (Claro, tive que executar algumas variações disso para casos em que o código esperado era diferente, etc.)
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,
})
`
Finalmente consegui lançar a versão 2 em dezembro de 2022, depois de muito trabalho duro? No momento estou trabalhando na extensão VSCode. Depois disso quero fazer um site de documentação que ilustre melhor como usar astx
.
A extensão VSCode está atualmente em beta, mas experimente!
Enquanto pensava em fazer isso, descobri o grip, uma ferramenta semelhante que inspirou a sintaxe $
capture. Existem vários motivos pelos quais decidi fazer astx
de qualquer maneira:
grasp -e 'setValue($k, $v, true)' -R 'setValueSilently({{k}}, {{v}})' file.js
jscodeshift
que pudesse usar em JS para casos de uso avançados que provavelmente são estranhos/impossíveis no Grasp Portanto, a filosofia do astx
é:
Cole seu código no AST Explorer se precisar aprender sobre a estrutura do AST.
Os padrões de localização Astx são apenas códigos JavaScript ou TypeScript que podem conter curingas de espaço reservado ou outras construções especiais como $Or(A, B)
. De modo geral, as partes do padrão que não são curingas ou construções especiais precisam corresponder exatamente.
Por exemplo, o padrão find foo($a)
corresponde a qualquer chamada à função foo
com um único argumento. O argumento pode qualquer coisa e é capturado como $a
.
Os padrões de substituição são quase idênticos aos padrões de localização, exceto que os espaços reservados são substituídos por tudo o que foi capturado no nome do espaço reservado pelo padrão de localização, e construções especiais de localização como $Or(A, B)
não têm significado especial nos padrões de substituição. (No futuro, poderá haver construções de substituição especiais que realizem algum tipo de transformação nos nós capturados.)
Por exemplo, o padrão de localização foo($a)
corresponde foo(1 + 2)
, então o padrão de substituição foo({ value: $a })
gerará o código foo({ value: 1 + 2 })
.
De modo geral, um identificador que começa com $
é um espaço reservado que funciona como um curinga. Existem três tipos de espaços reservados:
$<name>
corresponde a qualquer nó único ("espaço reservado para nó")$$<name>
corresponde a uma lista contígua de nós ("array placeholder")$$$<name>
: corresponde a todos os outros irmãos ("rest placeholder") O <name>
(se fornecido) deve começar com uma letra ou número; caso contrário, o identificador não será tratado como espaço reservado.
Os espaços reservados restantes ( $$$
) não podem ser irmãos de espaços reservados de lista ordenada ( $$
).
A menos que um espaço reservado seja anônimo, ele "capturará" o(s) nó(s) correspondente(s), o que significa que você pode usar o mesmo espaço reservado no padrão de substituição para interpolar o(s) nó(s) correspondente(s) na substituição gerada. Na API do Node, você também pode acessar os caminhos/nós AST capturados por meio do nome do espaço reservado.
$<name>
) Esses espaços reservados correspondem a um único nó. Por exemplo, o padrão [$a, $b]
corresponde a uma expressão de array com dois elementos, e esses elementos são capturados como $a
e $b
.
$$<name>
) Esses espaços reservados correspondem a uma lista contígua de nós. Por exemplo, o padrão [1, $$a, 2, $$b]
corresponde a uma expressão de matriz com 1
como o primeiro elemento e 2
como o elemento seguinte. Quaisquer elementos entre 1
e os 2
primeiros são capturados como $$a
, e os elementos após os 2
primeiros são capturados como $$b
.
$$$<name>
) Esses espaços reservados correspondem ao restante dos irmãos que não foram correspondidos por outra coisa. Por exemplo, o padrão [1, $$$a, 2]
corresponde a uma expressão de array que possui os elementos 1
e 2
em qualquer índice. Quaisquer outros elementos (incluindo ocorrências adicionais de 1
e 2
) são capturados como $$$a
.
Você pode usar um espaço reservado sem nome para corresponder aos nós sem capturá-los. $
corresponderá a qualquer nó único, $$
corresponderá a uma lista contígua de nós e $$$
corresponderá a todos os outros irmãos.
Se você usar o mesmo espaço reservado de captura mais de uma vez, as posições subsequentes terão que corresponder ao que foi capturado na primeira ocorrência do espaço reservado.
Por exemplo, o padrão foo($a, $a, $b, $b)
corresponderá apenas foo(1, 1, {foo: 1}, {foo: 1})
no seguinte:
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
Nota : os espaços reservados de captura de array ( $$a
) e os espaços reservados de captura de resto ( $$$a
) não suportam atualmente referência retroativa.
Um padrão ObjectExpression
(também conhecido como objeto literal) corresponderá a qualquer ObjectExpression
em seu código com as mesmas propriedades em qualquer ordem. Não corresponderá se houver propriedades ausentes ou adicionais. Por exemplo, { foo: 1, bar: $bar }
corresponderá a { foo: 1, bar: 2 }
ou { bar: 'hello', foo: 1 }
mas não { foo: 1 }
ou { foo: 1, bar: 2, baz: 3 }
.
Você pode combinar propriedades adicionais usando ...$$captureName
, por exemplo { foo: 1, ...$$rest }
corresponderá a { foo: 1 }
, { foo: 1, bar: 2 }
, { foo: 1, bar: 2, ...props }
etc. As propriedades adicionais serão capturadas em match.arrayCaptures
/ match.arrayPathCaptures
e podem ser espalhadas em expressões de substituição. Por exemplo, astx.find`{ foo: 1, ...$$rest }`.replace`{ bar: 1, ...$$rest }`
transformará { foo: 1, qux: {}, ...props }
em { bar: 1, qux: {}, ...props }
.
Uma propriedade spread que não tem o formato /^$$[a-z0-9]+$/i
não é um espaço reservado de captura, por exemplo { ...foo }
corresponderá apenas a { ...foo }
e { ...$_$foo }
corresponderá apenas { ...$$foo }
(iniciar $_
é um escape para $
).
Atualmente não há como combinar as propriedades em uma ordem específica, mas isso poderá ser adicionado no futuro.
Em muitos casos em que há uma lista de nós no AST, você pode combinar vários elementos com um espaço reservado começando com $$
. Por exemplo, [$$before, 3, $$after]
corresponderá a qualquer expressão de array contendo um elemento 3
; os elementos anteriores aos 3
primeiros serão capturados em $$before
e os elementos após os 3
primeiros serão capturados em $$after
.
Isso funciona mesmo com instruções em bloco. Por exemplo, function foo() { $$before; throw new Error('test'); $$after; }
corresponderá function foo()
que contém um throw new Error('test')
, e as instruções antes e depois dessa instrução throw serão capturadas em $$before
e $$after
, respectivamente.
Em alguns casos, a correspondência da lista será ordenada por padrão e, em alguns casos, será desordenada. Por exemplo, as correspondências da propriedade ObjectExpression
não são ordenadas por padrão, conforme mostrado na tabela abaixo. Usar um espaço reservado $$
ou o espaço reservado especial $Ordered
forçará a correspondência ordenada. Usar um espaço reservado $$$
ou o espaço reservado especial $Unordered
forçará a correspondência não ordenada.
Se você usar um espaço reservado começando com $$$
, ele será tratado como uma captura "restante" e todos os outros elementos da expressão de correspondência serão correspondidos fora de ordem. Por exemplo, import {a, b, $$$rest} from 'foo'
corresponderia import {c, b, d, e, a} from 'foo'
, colocando os especificadores c
, d
e e
, no $$$rest
Espaço reservado $$$rest
.
Os espaços reservados restantes ( $$$
) não podem ser irmãos de espaços reservados de lista ordenada ( $$
).
Alguns itens marcados como TODO provavelmente funcionam, mas não foram testados.
Tipo | Suporta correspondência de lista? | Não ordenado por padrão? | Notas |
---|---|---|---|
ArrayExpression.elements | ✅ | ||
ArrayPattern.elements | ✅ | ||
BlockStatement.body | ✅ | ||
CallExpression.arguments | ✅ | ||
Class(Declaration/Expression).implements | ✅ | ✅ | |
ClassBody.body | ✅ | ✅ | |
ComprehensionExpression.blocks | PENDÊNCIA | ||
DeclareClass.body | PENDÊNCIA | ✅ | |
DeclareClass.implements | PENDÊNCIA | ✅ | |
DeclareExportDeclaration.specifiers | PENDÊNCIA | ✅ | |
DeclareInterface.body | PENDÊNCIA | ||
DeclareInterface.extends | PENDÊNCIA | ||
DoExpression.body | PENDÊNCIA | ||
ExportNamedDeclaration.specifiers | ✅ | ✅ | |
Function.decorators | PENDÊNCIA | ||
Function.params | ✅ | ||
FunctionTypeAnnotation/TSFunctionType.params | ✅ | ||
GeneratorExpression.blocks | PENDÊNCIA | ||
ImportDeclaration.specifiers | ✅ | ✅ | |
(TS)InterfaceDeclaration.body | PENDÊNCIA | ✅ | |
(TS)InterfaceDeclaration.extends | PENDÊNCIA | ✅ | |
IntersectionTypeAnnotation/TSIntersectionType.types | ✅ | ✅ | |
JSX(Element/Fragment).children | ✅ | ||
JSX(Opening)Element.attributes | ✅ | ✅ | |
MethodDefinition.decorators | PENDÊNCIA | ||
NewExpression.arguments | ✅ | ||
ObjectExpression.properties | ✅ | ✅ | |
ObjectPattern.decorators | PENDÊNCIA | ||
ObjectPattern.properties | ✅ | ✅ | |
(ObjectTypeAnnotation/TSTypeLiteral).properties | ✅ | ✅ | Use $a: $ para corresponder a uma propriedade, $$a: $ ou $$$a: $ para corresponder a várias |
Program.body | ✅ | ||
Property.decorators | PENDÊNCIA | ||
SequenceExpression | ✅ | ||
SwitchCase.consequent | ✅ | ||
SwitchStatement.cases | PENDÊNCIA | ||
TemplateLiteral.quasis/expressions | ❓ não tenho certeza se consigo criar uma sintaxe | ||
TryStatement.guardedHandlers | PENDÊNCIA | ||
TryStatement.handlers | PENDÊNCIA | ||
TSFunctionType.parameters | ✅ | ||
TSCallSignatureDeclaration.parameters | PENDÊNCIA | ||
TSConstructorType.parameters | PENDÊNCIA | ||
TSConstructSignatureDeclaration.parameters | PENDÊNCIA | ||
TSDeclareFunction.params | PENDÊNCIA | ||
TSDeclareMethod.params | PENDÊNCIA | ||
EnumDeclaration.body/TSEnumDeclaration.members | PENDÊNCIA | ✅ | |
TSIndexSignature.parameters | PENDÊNCIA | ||
TSMethodSignature.parameters | PENDÊNCIA | ||
TSModuleBlock.body | PENDÊNCIA | ||
TSTypeLiteral.members | ✅ | ✅ | |
TupleTypeAnnotation/TSTupleType.types | ✅ | ||
(TS)TypeParameterDeclaration.params | ✅ | ||
(TS)TypeParameterInstantiation.params | ✅ | ||
UnionTypeAnnotation/TSUnionType.types | ✅ | ✅ | |
VariableDeclaration.declarations | ✅ | ||
WithStatement.body | quem usa com declarações... |
Uma string que é apenas um espaço reservado como '$foo'
corresponderá a qualquer string e capturará seu conteúdo em match.stringCaptures.$foo
. As mesmas regras de escape se aplicam aos identificadores. Isso também funciona para literais de modelo como `$foo`
e literais de modelo marcados como doSomething`$foo`
.
Isto pode ser útil para trabalhar com instruções de importação. Por exemplo, consulte Convertendo instruções require em importações.
Um comentário vazio ( /**/
) em um padrão "extrairá" um nó para correspondência. Por exemplo, o padrão const x = { /**/ $key: $value }
apenas corresponderá aos nós ObjectProperty
com $key: $value
.
O analisador não seria capaz de analisar $key: $value
por si só ou saber que você quer dizer um ObjectProperty
, em oposição a algo diferente como x: number
em const x: number = 1
, então usar /**/
permite que você para contornar isso. Você pode usar isso para corresponder a qualquer tipo de nó que não seja uma expressão ou instrução válida por si só. Por exemplo, type T = /**/ Array<number>
corresponderia às anotações do tipo Array<number>
.
/**/
também funciona em padrões de substituição.
$Maybe(pattern)
Corresponde à expressão fornecida ou a nenhum nó em seu lugar. Por exemplo, let $a = $Maybe(2)
corresponderá let foo = 2
e let foo
(sem inicializador), mas não let foo = 3
.
$Or(...)
Corresponde aos nós que correspondem a pelo menos um dos padrões fornecidos. Por exemplo $Or(foo($$args), {a: $value})
corresponderá chamadas para foo
e literais de objeto com apenas uma propriedade a
.
$And(...)
Corresponde aos nós que correspondem a todos os padrões fornecidos. Isso é útil principalmente para restringir os tipos de nós que podem ser capturados em um determinado espaço reservado. Por exemplo, let $a = $And($init, $a + $b)
corresponderá às declarações let
onde o inicializador corresponde $a + $b
e capturará o inicializador como $init
.
$Maybe<pattern>
Corresponde à anotação de tipo fornecida ou a nenhum nó em seu lugar. Por exemplo, let $a: $Maybe<number>
corresponderá let foo: number
e let foo
(sem anotação de tipo), mas não let foo: string``let foo: string
.
$Or<...>
Corresponde aos nós que correspondem a pelo menos uma das anotações de tipo fornecidas. Por exemplo, let $x: $Or<number[], string[]>
corresponderá às declarações let
do tipo number[]
ou string[]
.
$And<...>
Corresponde aos nós que correspondem a todas as anotações de tipo fornecidas. Isso é útil principalmente para restringir os tipos de nós que podem ser capturados em um determinado espaço reservado. Por exemplo, let $a: $And<$type, $elem[]>
corresponderá às declarações let
onde a anotação de tipo corresponde $elem[]
e capturará a anotação de tipo como $type
.
$Ordered
Força o padrão a corresponder aos nós irmãos na mesma ordem.
$Unordered
Força o padrão a corresponder aos nós irmãos em qualquer ordem.
import { NodePath } from 'astx'
Esta é a mesma interface NodePath
que ast-types
, com algumas melhorias nas definições de tipo de método. astx
usa ast-types
para percorrer o código, na esperança de oferecer suporte a diferentes analisadores no futuro.
import { Astx } from 'astx'
constructor(backend: Backend, paths: NodePath<any>[] | Match[], options?: { withCaptures?: Match[] })
backend
é a implementação do analisador/gerador que está sendo usada.
paths
especifica os NodePath
s ou Match
es nos quais você deseja que os métodos Astx
pesquisem/operem.
.find(...)
( Astx
) Encontra correspondências para o padrão fornecido nos caminhos iniciais desta instância e retorna uma instância Astx
contendo as correspondências.
Se você chamar astx.find('foo($$args)')
na instância inicial passada para sua função de transformação, ela encontrará todas as chamadas para foo
dentro do arquivo e retornará essas correspondências em uma nova instância Astx
.
Os métodos na instância retornada funcionarão apenas nos caminhos correspondentes.
Por exemplo, se você fizer astx.find('foo($$args)').find('$a + $b')
, a segunda chamada find
procurará apenas $a + $b
nas correspondências de foo($$args)
, em vez de em qualquer lugar do arquivo.
Você pode chamar .find
como um método ou literal de modelo marcado:
.find`pattern`
.find(pattern: string | string[] | Node | Node[] | NodePath | NodePath[] | ((wrapper: Astx) => boolean), options?: FindOptions)
Se você fornecer o padrão como uma string, ele deverá ser uma expressão ou instrução(ões) válida(s). Caso contrário, deverão ser nós AST válidos que você já analisou ou construiu. Você pode interpolar strings, nós AST, matrizes de nós AST e instâncias Astx
no modelo literal marcado.
Por exemplo, você poderia fazer astx.find`${t.identifier('foo')} + 3`
.
Ou você pode combinar várias instruções fazendo
astx . find `
const $a = $b;
$$c;
const $d = $a + $e;
`
Isso corresponderia (por exemplo) às instruções const foo = 1; const bar = foo + 5;
, com qualquer número de instruções entre eles.
.closest(...)
( Astx
) Como .find()
, mas pesquisa os ancestrais AST em vez de descendentes; encontra o nó envolvente mais próximo de cada caminho de entrada que corresponde ao padrão fornecido.
.destruct(...)
( Astx
) Como .find()
, mas não testa os descendentes do(s) caminho(s) de entrada em relação ao padrão; apenas os caminhos de entrada que correspondam ao padrão serão incluídos no resultado.
FindOptions
Um objeto com as seguintes propriedades opcionais:
FindOptions.where
( { [captureName: string]: (path: Astx) => boolean }
) Onde estão as condições para capturas de nós. Por exemplo, se o seu padrão de localização for $a()
, você poderia ter { where: { $a: astx => /foo|bar/.test(astx.node.name) } }
, que corresponderia apenas a chamadas com argumento zero para foo
ou bar
.
.find(...).replace(...)
( void
) Encontra e substitui correspondências para o padrão fornecido em root
.
Existem várias maneiras diferentes de chamar .replace
. Você pode chamar .find
da maneira descrita acima.
.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[])
Se você fornecer a substituição como uma string, ela deverá ser uma expressão ou instrução válida. Você pode fornecer a substituição como nós AST que você já analisou ou construiu. Ou você pode fornecer uma função de substituição, que será chamada a cada correspondência e deve retornar uma string ou Node | Node[]
(você pode usar a função de string de modelo com tag parse
fornecida como o segundo argumento para analisar o código em uma string. Por exemplo, você pode colocar os nomes das funções em letras maiúsculas em todas as chamadas de função com argumento zero ( foo(); bar()
torna-se FOO(); BAR()
) com isto:
astx
.find`$fn()`
.replace(({ captures: { $fn } }) => `${$fn.name.toUpperCase()}()`)
.findImports(...)
( Astx
) Uma versão conveniente de .find()
para encontrar importações que toleram especificadores extras, corresponde a importações de valor de mesmo nome se importações de tipo foram solicitadas, etc.
Por exemplo, .findImports`import $a from 'a'`
corresponderia import A, { b, c } from 'a'
ou import { default as a } from 'a'
, capturando $a
, enquanto .find`import $a from 'a'`
não corresponderia a nenhum desses casos.
O padrão deve conter apenas instruções de importação.
.addImports(...)
( Astx
) Como .findImports()
, mas adiciona quaisquer importações que não foram encontradas. Por exemplo, dado o código-fonte:
import { foo , type bar as qux } from 'foo'
import 'g'
E a operação
const { $bar } = astx . addImports `
import type { bar as $bar } from 'foo'
import FooDefault from 'foo'
import * as g from 'g'
`
A saída seria
import FooDefault , { foo , type bar as qux } from 'foo'
import * as g from 'g'
Com $bar
capturando o identificador qux
.
.removeImports(...)
( boolean
) Recebe instruções de importação no mesmo formato que .findImports()
mas remove todos os especificadores fornecidos.
.replaceImport(...).with(...)
( boolean
)Substitui um único especificador de importação por outro. Por exemplo, dada a entrada
import { Match , Route , Location } from 'react-router-dom'
import type { History } from 'history'
E operação
astx . replaceImport `
import { Location } from 'react-router-dom'
` . with `
import type { Location } from 'history'
`
A saída seria
import { Match , Route } from 'react-router-dom'
import type { History , Location } from 'history'
Os padrões de localização e substituição devem conter uma única instrução de importação com um único especificador.
.remove()
( void
) Remove as correspondências de .find()
ou captura(s) focada(s) nesta instância Astx
.
.matched
( this | null
) Retorna esta instância Astx
se tiver pelo menos uma correspondência, caso contrário retorna null
.
Como .find()
, .closest()
e .destruct()
sempre retornam uma instância Astx
, mesmo que não haja correspondências, você pode usar .find(...).matched
se desejar apenas um valor definido quando houver foi pelo menos uma partida.
.size()
( number
) Retorna o número de correspondências da chamada .find()
ou .closest()
que retornou esta instância.
[name: `$${string}` | `$$${string}` | `$$$${string}`]
( Astx
) Obtém uma instância Astx
focada nas capturas com o name
fornecido.
Por exemplo, você pode fazer:
for ( const { $v } of astx . find `process.env.$v` ) {
report ( $v . code )
}
.placeholder
( string | undefined
)O nome do espaço reservado que esta instância representa. Por exemplo:
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
)Retorna o primeiro nó da primeira correspondência. Lança um erro se não houver correspondências.
.path
( NodePath
)Retorna o primeiro caminho da primeira correspondência. Lança um erro se não houver correspondências.
.code
( string
)Gera código do primeiro nó da primeira correspondência. Lança um erro se não houver correspondências.
.stringValue
( string
)Retorna o valor da string do primeiro nó se a captura em foco for uma captura de string. Lança um erro se não houver correspondências.
[Symbol.iterator]
( Iterator<Astx>
) Itera em cada correspondência,devolvendo uma instância Astx
para cada correspondência.
.matches
( Match[]
) Obtém as correspondências da chamada .find()
ou .closest()
que retornou esta instância.
.match
( Match
) Obtém a primeira correspondência da chamada .find()
ou .closest()
que retornou esta instância.
Lança um erro se não houver correspondências.
.paths
( NodePath[]
) Retorna os caminhos que .find()
e .closest()
irão pesquisar. Se esta instância foi retornada por .find()
ou .closest()
, estes são os caminhos dos nós que correspondem ao padrão de pesquisa.
.nodes
( Node[]
) Retorna os nós nos quais .find()
e .closest()
irão pesquisar. Se esta instância foi retornada por .find()
ou .closest()
, estes são os nós que correspondem ao padrão de pesquisa.
.some(predicate)
( boolean
) Retorna false
a menos que predicate
retorne verdadeiro para pelo menos uma correspondência.
iteratee
é a função que será chamada com match: Astx, index: number, parent: Astx
e retorna true
ou false
.
.every(predicate)
( boolean
) Retorna true
, a menos que predicate
retorne falso para pelo menos uma correspondência.
iteratee
é a função que será chamada com match: Astx, index: number, parent: Astx
e retorna true
ou false
.
.filter(iteratee)
( Astx
)Filtra as correspondências.
iteratee
é a função que será chamada com match: Astx, index: number, parent: Astx
e retorna true
ou false
. Somente correspondências para as quais iteratee
retorna true
serão incluídas no resultado.
.map<T>(iteratee)
( T[]
)Mapeia as partidas.
iteratee
é a função que será chamada com match: Astx, index: number, parent: Astx
e retorna o valor a ser incluído na matriz de resultados.
.at(index)
( Astx
) Seleciona a correspondência no index
fornecido.
.withCaptures(...captures)
( Astx
) Retorna uma instância Astx
que contém capturas das capturas fornecidas ...captures
além das capturas presentes nesta instância.
Você pode passar os seguintes tipos de argumentos:
Astx
- todas as capturas da instância serão incluídas.Astx[placeholder]
- captura(s) para o placeholder
fornecido serão incluídas.{ $name: Astx[placeholder] }
- captura(s) para o placeholder
fornecido, renomeado para $name
.Match
objetos import { type Match } from 'astx'
.type
O tipo de correspondência: 'node'
ou 'nodes'
.
.path
O NodePath
do nó correspondente. Se type
for 'nodes'
, será paths[0]
.
.node
O Node
correspondente. Se type
for 'nodes'
, será nodes[0]
.
.paths
Os NodePaths
dos nós correspondentes.
.nodes
Os Node
s correspondentes.
.captures
Os Node
s são capturados de espaços reservados no padrão de correspondência. Por exemplo, se o padrão for foo($bar)
, .captures.$bar
será o Node
do primeiro argumento.
.pathCaptures
Os NodePath
são capturados de espaços reservados no padrão de correspondência. Por exemplo, se o padrão for foo($bar)
, .pathCaptures.$bar
será o NodePath
do primeiro argumento.
.arrayCaptures
Os Node[]
s capturados de espaços reservados de array no padrão de correspondência. Por exemplo, se o padrão for foo({ ...$bar })
, .arrayCaptures.$bar
serão os Node[]
s das propriedades do objeto.
.arrayPathCaptures
Os NodePath[]
s capturados de marcadores de matriz no padrão de correspondência. Por exemplo, se o padrão for foo({ ...$bar })
, .pathArrayCaptures.$bar
serão os NodePath[]
s das propriedades do objeto.
.stringCaptures
Os valores de string capturados de espaços reservados de string no padrão de correspondência. Por exemplo, se o padrão foi import foo from '$foo'
, stringCaptures.$foo
será o caminho de importação.
Assim como jscodeshift
, você pode colocar código para realizar uma transformação em um arquivo .ts
ou .js
(o padrão é astx.ts
ou astx.js
no diretório de trabalho, a menos que você especifique um arquivo diferente com a opção -t
CLI).
A API do arquivo de transformação é um pouco diferente do jscodeshift
. Você pode ter as seguintes exportações:
exports.find
(opcional)Uma sequência de código ou nó AST do padrão a ser localizado nos arquivos que estão sendo transformados.
exports.where
(opcional) Onde as condições para capturar espaços reservados em exports.find
. Consulte FindOptions.where
( { [captureName: string]: (path: NodePath<any>) => boolean }
) para obter mais informações.
exports.replace
(opcional) Uma string de código, nó AST ou função de substituição para substituir correspondências de exports.find
.
Os argumentos da função são os mesmos descritos em .find().replace()
.
exports.astx
(opcional) Uma função para realizar uma transformação arbitrária usando a API Astx
. Ele é chamado com um objeto com as seguintes propriedades:
file
( string
) - O caminho para o arquivo que está sendo transformadosource
( string
) - O código fonte do arquivo que está sendo transformadoastx
( Astx
) - a instância da API Astx
t
( AstTypes
) - definições ast-types
para o analisador escolhidoexpression
- literal de modelo marcado para analisar código como uma expressãostatement
- modelo literal marcado para analisar código como uma declaraçãostatements
- modelo literal marcado para análise de código como uma matriz de instruçõesreport
( (message: unknown) => void
)mark
( (...matches: Astx[]) => void
) - marca as correspondências fornecidas para serem exibidas na lista de correspondências de vscode-astx, etc. Ao contrário de jscodeshift
, sua função transform pode ser assíncrona e não precisa retornar o código transformado, mas você pode retornar uma string
. Você também pode retornar null
para pular o arquivo.
exports.onReport
(opcional) Se você chamar report(x)
de uma função exports.astx
, ela será chamada com onReport({ file, report: x })
.
Se você estiver usando vários threads de trabalho, onReport
será chamado no processo pai, portanto, a mensagem do relatório deverá ser um valor serializável. Isso permite que uma transformação colete relatórios de todos os trabalhadores (e potencialmente faça algo com eles no finish
).
Se onReport
retornar uma Promise
será aguardado.
exports.finish
(opcional)Isso será chamado depois que a transformação for executada em todos os arquivos de entrada.
Se você estiver usando vários threads de trabalho, finish
será chamado no processo pai. Você pode usar onReport
e finish
juntos para coletar informações de cada arquivo de entrada e produzir algum tipo de saída combinada no final.
Se finish
retornar uma Promise
ela será aguardada.
astx
suporta configuração nos seguintes locais (via cosmiconfig
):
astx
em package.json.astxrc
no formato JSON ou YAML.astxrc.json
, .astxrc.yaml
, .astxrc.yml
, .astxrc.js
ou .astxrc.cjs
astx.config.js
ou astx.config.cjs
CommonJS exportando um objetoSe sua base de código estiver formatada com mais bonita, recomendo tentar isto primeiro:
{
"parser" : " babel/auto " ,
"parserOptions" : {
"preserveFormat" : " generatorHack "
}
}
(ou como opções CLI)
--parser babel/auto --parserOptions '{"preserveFormat": "generatorHack"}'
Se isso falhar, você pode tentar parser: 'recast/babel/auto'
ou os analisadores não- /auto
.
Sua milhagem pode variar com recast
; eles simplesmente não conseguem mantê-lo atualizado com os novos recursos de sintaxe em JS e TS com rapidez suficiente, e já vi isso gerar sintaxe inválida muitas vezes.
De agora em diante vou trabalhar em uma solução confiável usando @babel/generator
ou prettier
para imprimir o AST modificado, com um gancho para usar a fonte original literalmente para nós não modificados.
parser
O analisador a ser usado. Opções:
babel/auto
(padrão)babel
(mais rápido que babel/auto
, mas usa opções de análise padrão, talvez seja necessário configurar parserOptions
)recast/babel
recast/babel/auto
babel/auto
determina automaticamente as opções de análise de sua configuração do babel, se presente. babel
usa opções de análise fixas, por isso é mais rápido que babel/auto
, mas pode ser necessário configurar parserOptions
. As opções recast/babel(/auto)
usam recast
para preservar a formatação. Já vi sintaxe inválida de saída recast
em alguns arquivos, portanto, use com cuidado.
parserOptions
Opções a serem passadas para o analisador. No momento, essas são apenas as opções @babel/parser
mais as seguintes opções adicionais:
preserveFormat
(aplica-se a: babel
, babel/auto
) preserveFormat: 'generatorHack'
usa um hack experimental para preservar o formato de todos os nós inalterados sequestrando a API interna @babel/generator
.prettier
Se false
, não tente usar prettier
para reformatar o código-fonte transformado. O padrão é true
.
Astx inclui uma CLI para realizar transformações. A CLI processará os arquivos fornecidos, depois imprimirá uma comparação do que será alterado e solicitará que você confirme que deseja gravar as alterações.
Ele irá analisar o babel por padrão usando a versão instalada no seu projeto e a configuração do babel do seu projeto, se houver. Você pode passar --parser recast/babel
se quiser usar recast
para tentar preservar a formatação na saída, mas às vezes vejo erros de sintaxe em sua saída.
Ao contrário do jscodeshift
, se prettier
estiver instalado no seu projeto, ele formatará o código transformado com 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]