Búsqueda y reemplazo estructural súper potente para JavaScript y TypeScript para automatizar su refactorización
$<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
Los refactores simples pueden resultar tediosos y repetitivos. Por ejemplo, supongamos que desea realizar el siguiente cambio en una base de código:
// before:
rmdir ( 'old/stuff' )
rmdir ( 'new/stuff' , true )
// after:
rmdir ( 'old/stuff' )
rmdir ( 'new/stuff' , { force : true } )
Cambiar un montón de llamadas a rmdir
manualmente sería una mierda. Podrías intentar usar el reemplazo de expresiones regulares, pero es complicado y no toleraría bien los espacios en blanco y los saltos de línea a menos que trabajes muy duro en la expresión regular. Incluso podrías usar jscodeshift
, pero lleva demasiado tiempo para casos simples como este y comienza a parecer más difícil de lo necesario...
Ahora hay una mejor opción... ¡puedes refactorizar con confianza usando astx
!
astx
--find ' rmdir($path, $force) '
--replace ' rmdir($path, { force: $force }) '
Este es un ejemplo básico de patrones astx
, que son simplemente código JS o TS que puede contener marcadores de posición y otras construcciones coincidentes especiales; astx
busca código que coincida con el patrón y acepta cualquier expresión en lugar de $path
y $force
. Luego, astx
reemplaza cada coincidencia con el patrón de reemplazo, sustituyendo las expresiones que capturó por $path
( 'new/stuff'
) y $force
( true
).
Pero esto es sólo el comienzo; Los patrones astx
pueden ser mucho más complejos y poderosos que esto, y para casos de uso realmente avanzados tiene una API intuitiva que puedes usar:
for ( const match of astx . find `rmdir($path, $force)` ) {
const { $path , $force } = match
// do stuff with $path.node, $path.code, etc...
}
¿Tiene muchos errores Do not access Object.prototype method 'hasOwnProperty' from target object
?
// astx.js
exports . find = `$a.hasOwnProperty($b)`
exports . replace = `Object.hasOwn($a, $b)`
Recientemente por trabajo quería hacer este cambio:
// 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 } ) ,
]
Esto es sencillo de hacer con la coincidencia de listas:
// 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`
En jscodeshift-add-imports
tuve un montón de casos de prueba siguiendo este patrón:
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 )
} )
Quería hacerlos más SECOS, así:
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' } ,
} )
} )
Aquí hubo una transformación para lo anterior. (Por supuesto, tuve que ejecutar algunas variaciones de esto para los casos en los que el 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 logré lanzar la versión 2 en diciembre de 2022 después de mucho trabajo duro? Ahora mismo estoy trabajando en la extensión VSCode. Después de eso, quiero crear un sitio web de documentación que ilustre mejor cómo usar astx
.
La extensión VSCode se encuentra actualmente en versión beta, ¡pero pruébala!
Mientras pensaba en hacer esto, descubrí captar, una herramienta similar que inspiró la sintaxis de captura $
. De todos modos, hay varias razones por las que decidí hacer astx
:
grasp -e 'setValue($k, $v, true)' -R 'setValueSilently({{k}}, {{v}})' file.js
jscodeshift
que pudiera usar en JS para casos de uso avanzados que probablemente sean incómodos/imposibles en Grasp Entonces la filosofía de astx
es:
Pegue su código en AST Explorer si necesita conocer la estructura de AST.
Los patrones de búsqueda de Astx son solo código JavaScript o TypeScript que puede contener comodines de marcador de posición u otras construcciones especiales como $Or(A, B)
. En términos generales, las partes del patrón que no son comodines o construcciones especiales tienen que coincidir exactamente.
Por ejemplo, el patrón de búsqueda foo($a)
coincide con cualquier llamada a la función foo
con un solo argumento. El argumento puede ser cualquier cosa y se captura como $a
.
Los patrones de reemplazo son casi idénticos a los patrones de búsqueda, excepto que los marcadores de posición se reemplazan con lo que fue capturado en el nombre del marcador de posición por el patrón de búsqueda, y las construcciones de búsqueda especiales como $Or(A, B)
no tienen un significado especial en los patrones de reemplazo. (En el futuro, es posible que haya construcciones de reemplazo especiales que realicen algún tipo de transformación en los nodos capturados).
Por ejemplo, el patrón de búsqueda foo($a)
coincide con foo(1 + 2)
, luego el patrón de reemplazo foo({ value: $a })
generará el código foo({ value: 1 + 2 })
.
En términos generales, un identificador que comienza con $
es un marcador de posición que funciona como un comodín. Hay tres tipos de marcadores de posición:
$<name>
coincide con cualquier nodo ("marcador de posición de nodo")$$<name>
coincide con una lista contigua de nodos ("marcador de posición de matriz")$$$<name>
: coincide con todos los demás hermanos ("marcador de posición de descanso") El <name>
(si se proporciona) debe comenzar con una letra o número; de lo contrario, el identificador no será tratado como un marcador de posición.
Los marcadores de posición de descanso ( $$$
) no pueden ser hermanos de los marcadores de posición de lista ordenada ( $$
).
A menos que un marcador de posición sea anónimo, "capturará" los nodos coincidentes, lo que significa que puede usar el mismo marcador de posición en el patrón de reemplazo para interpolar los nodos coincidentes en el reemplazo generado. En la API de nodo también puede acceder a las rutas/nodos AST capturados a través del nombre del marcador de posición.
$<name>
) Estos marcadores de posición coinciden con un único nodo. Por ejemplo, el patrón [$a, $b]
coincide con una expresión de matriz con dos elementos, y esos elementos se capturan como $a
y $b
.
$$<name>
) Estos marcadores de posición coinciden con una lista contigua de nodos. Por ejemplo, el patrón [1, $$a, 2, $$b]
coincide con una expresión de matriz con 1
como primer elemento y 2
como elemento siguiente. Cualquier elemento entre 1
y los primeros 2
se captura como $$a
, y los elementos después de los primeros 2
se capturan como $$b
.
$$$<name>
) Estos marcadores de posición coinciden con el resto de los hermanos que no coincidieron con otra cosa. Por ejemplo, el patrón [1, $$$a, 2]
coincide con una expresión de matriz que tiene los elementos 1
y 2
en cualquier índice. Cualquier otro elemento (incluidas apariciones adicionales de 1
y 2
) se captura como $$$a
.
Puede utilizar un marcador de posición sin nombre para hacer coincidir los nodos sin capturarlos. $
coincidirá con cualquier nodo, $$
coincidirá con una lista contigua de nodos y $$$
coincidirá con todos los demás hermanos.
Si utiliza el mismo marcador de posición de captura más de una vez, las posiciones posteriores deberán coincidir con lo que se capturó en la primera aparición del marcador de posición.
Por ejemplo, el patrón foo($a, $a, $b, $b)
coincidirá solo con foo(1, 1, {foo: 1}, {foo: 1})
en lo siguiente:
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 : los marcadores de posición de captura de matriz ( $$a
) y los marcadores de posición de captura de resto ( $$$a
) actualmente no admiten referencias inversas.
Un patrón ObjectExpression
(también conocido como objeto literal) coincidirá con cualquier ObjectExpression
en su código con las mismas propiedades en cualquier orden. No coincidirá si faltan propiedades o hay propiedades adicionales. Por ejemplo, { foo: 1, bar: $bar }
coincidirá con { foo: 1, bar: 2 }
o { bar: 'hello', foo: 1 }
pero no con { foo: 1 }
o { foo: 1, bar: 2, baz: 3 }
.
Puede hacer coincidir propiedades adicionales usando ...$$captureName
, por ejemplo { foo: 1, ...$$rest }
coincidirá con { foo: 1 }
, { foo: 1, bar: 2 }
, { foo: 1, bar: 2, ...props }
etc. Las propiedades adicionales se capturarán en match.arrayCaptures
/ match.arrayPathCaptures
y se pueden distribuir en expresiones de reemplazo. Por ejemplo, astx.find`{ foo: 1, ...$$rest }`.replace`{ bar: 1, ...$$rest }`
transformará { foo: 1, qux: {}, ...props }
en { bar: 1, qux: {}, ...props }
.
Una propiedad de extensión que no tiene la forma /^$$[a-z0-9]+$/i
no es un marcador de posición de captura, por ejemplo { ...foo }
solo coincidirá con { ...foo }
y { ...$_$foo }
solo coincidirá con { ...$$foo }
(el $_
inicial es un escape para $
).
Actualmente no hay forma de hacer coincidir las propiedades en un orden específico, pero se podría agregar en el futuro.
En muchos casos en los que hay una lista de nodos en el AST, puede hacer coincidir varios elementos con un marcador de posición que comience con $$
. Por ejemplo, [$$before, 3, $$after]
coincidirá con cualquier expresión de matriz que contenga un elemento 3
; Los elementos anteriores a los 3
primeros se capturarán en $$before
y los elementos posteriores a los 3
primeros se capturarán en $$after
.
Esto funciona incluso con declaraciones en bloque. Por ejemplo, function foo() { $$before; throw new Error('test'); $$after; }
coincidirá con function foo()
que contiene un throw new Error('test')
, y las declaraciones antes y después de esa declaración throw se capturarán en $$before
y $$after
, respectivamente.
En algunos casos, la coincidencia de listas se ordenará de forma predeterminada y, en otros casos, estará desordenada. Por ejemplo, las coincidencias de propiedades ObjectExpression
están desordenadas de forma predeterminada, como se muestra en la siguiente tabla. El uso de un marcador de posición $$
o el marcador de posición especial $Ordered
forzará la coincidencia ordenada. El uso de un marcador de posición $$$
o el marcador de posición especial $Unordered
forzará una coincidencia desordenada.
Si utiliza un marcador de posición que comienza con $$$
, se trata como una captura "restante" y todos los demás elementos de la expresión coincidente coincidirán desordenados. Por ejemplo, import {a, b, $$$rest} from 'foo'
coincidiría con import {c, b, d, e, a} from 'foo'
, poniendo los especificadores c
, d
y e
en $$$rest
Marcador de posición $$$rest
.
Los marcadores de posición de descanso ( $$$
) no pueden ser hermanos de los marcadores de posición de lista ordenada ( $$
).
Algunos elementos marcados como TODO probablemente realmente funcionen, pero no se han probado.
Tipo | ¿Admite la coincidencia de listas? | ¿Desordenado por defecto? | Notas |
---|---|---|---|
ArrayExpression.elements | ✅ | ||
ArrayPattern.elements | ✅ | ||
BlockStatement.body | ✅ | ||
CallExpression.arguments | ✅ | ||
Class(Declaration/Expression).implements | ✅ | ✅ | |
ClassBody.body | ✅ | ✅ | |
ComprehensionExpression.blocks | HACER | ||
DeclareClass.body | HACER | ✅ | |
DeclareClass.implements | HACER | ✅ | |
DeclareExportDeclaration.specifiers | HACER | ✅ | |
DeclareInterface.body | HACER | ||
DeclareInterface.extends | HACER | ||
DoExpression.body | HACER | ||
ExportNamedDeclaration.specifiers | ✅ | ✅ | |
Function.decorators | HACER | ||
Function.params | ✅ | ||
FunctionTypeAnnotation/TSFunctionType.params | ✅ | ||
GeneratorExpression.blocks | HACER | ||
ImportDeclaration.specifiers | ✅ | ✅ | |
(TS)InterfaceDeclaration.body | HACER | ✅ | |
(TS)InterfaceDeclaration.extends | HACER | ✅ | |
IntersectionTypeAnnotation/TSIntersectionType.types | ✅ | ✅ | |
JSX(Element/Fragment).children | ✅ | ||
JSX(Opening)Element.attributes | ✅ | ✅ | |
MethodDefinition.decorators | HACER | ||
NewExpression.arguments | ✅ | ||
ObjectExpression.properties | ✅ | ✅ | |
ObjectPattern.decorators | HACER | ||
ObjectPattern.properties | ✅ | ✅ | |
(ObjectTypeAnnotation/TSTypeLiteral).properties | ✅ | ✅ | Utilice $a: $ para hacer coincidir una propiedad, $$a: $ o $$$a: $ para hacer coincidir varias |
Program.body | ✅ | ||
Property.decorators | HACER | ||
SequenceExpression | ✅ | ||
SwitchCase.consequent | ✅ | ||
SwitchStatement.cases | HACER | ||
TemplateLiteral.quasis/expressions | ❓ no estoy seguro de poder encontrar una sintaxis | ||
TryStatement.guardedHandlers | HACER | ||
TryStatement.handlers | HACER | ||
TSFunctionType.parameters | ✅ | ||
TSCallSignatureDeclaration.parameters | HACER | ||
TSConstructorType.parameters | HACER | ||
TSConstructSignatureDeclaration.parameters | HACER | ||
TSDeclareFunction.params | HACER | ||
TSDeclareMethod.params | HACER | ||
EnumDeclaration.body/TSEnumDeclaration.members | HACER | ✅ | |
TSIndexSignature.parameters | HACER | ||
TSMethodSignature.parameters | HACER | ||
TSModuleBlock.body | HACER | ||
TSTypeLiteral.members | ✅ | ✅ | |
TupleTypeAnnotation/TSTupleType.types | ✅ | ||
(TS)TypeParameterDeclaration.params | ✅ | ||
(TS)TypeParameterInstantiation.params | ✅ | ||
UnionTypeAnnotation/TSUnionType.types | ✅ | ✅ | |
VariableDeclaration.declarations | ✅ | ||
WithStatement.body | quien usa con declaraciones... |
Una cadena que es solo un marcador de posición como '$foo'
coincidirá con cualquier cadena y capturará su contenido en match.stringCaptures.$foo
. Se aplican las mismas reglas de escape que para los identificadores. Esto también funciona para literales de plantilla como `$foo`
y literales de plantilla etiquetados como doSomething`$foo`
.
Esto puede resultar útil para trabajar con declaraciones de importación. Por ejemplo, consulte Convertir declaraciones require en importaciones.
Un comentario vacío ( /**/
) en un patrón "extraerá" un nodo para hacer coincidir. Por ejemplo, el patrón const x = { /**/ $key: $value }
simplemente hará coincidir los nodos ObjectProperty
con $key: $value
.
El analizador no podría analizar $key: $value
por sí solo ni saber que te refieres a ObjectProperty
, a diferencia de algo diferente como x: number
en const x: number = 1
, por lo que usar /**/
te permite para solucionar este problema. Puede usar esto para hacer coincidir cualquier tipo de nodo que no sea una expresión o declaración válida en sí misma. Por ejemplo, type T = /**/ Array<number>
coincidiría con las anotaciones de tipo Array<number>
.
/**/
también funciona en patrones de reemplazo.
$Maybe(pattern)
Coincide con la expresión dada o con ningún nodo en su lugar. Por ejemplo, let $a = $Maybe(2)
coincidirá con let foo = 2
y let foo
(sin inicializador), pero no con let foo = 3
.
$Or(...)
Coincide con nodos que coinciden con al menos uno de los patrones dados. Por ejemplo $Or(foo($$args), {a: $value})
coincidirá con llamadas a foo
y objetos literales con solo una propiedad a
.
$And(...)
Coincide con nodos que coinciden con todos los patrones dados. Esto es especialmente útil para limitar los tipos de nodos que se pueden capturar en un marcador de posición determinado. Por ejemplo, let $a = $And($init, $a + $b)
coincidirá con las declaraciones let
donde el inicializador coincida con $a + $b
y capturará el inicializador como $init
.
$Maybe<pattern>
Coincide con la anotación de tipo dada o con ningún nodo en su lugar. Por ejemplo, let $a: $Maybe<number>
coincidirá con let foo: number
y let foo
(sin anotación de tipo), pero no let foo: string``let foo: string
.
$Or<...>
Coincide con nodos que coinciden con al menos una de las anotaciones de tipo dadas. Por ejemplo, let $x: $Or<number[], string[]>
coincidirá con declaraciones let
de tipo number[]
o string[]
.
$And<...>
Coincide con nodos que coinciden con todas las anotaciones de tipo dadas. Esto es especialmente útil para limitar los tipos de nodos que se pueden capturar en un marcador de posición determinado. Por ejemplo, let $a: $And<$type, $elem[]>
coincidirá con las declaraciones let
donde la anotación de tipo coincida con $elem[]
y capturará la anotación de tipo como $type
.
$Ordered
Obliga al patrón a coincidir con los nodos hermanos en el mismo orden.
$Unordered
Obliga al patrón a coincidir con los nodos hermanos en cualquier orden.
import { NodePath } from 'astx'
Esta es la misma interfaz NodePath
que ast-types
, con algunas mejoras en las definiciones de tipos de métodos. astx
utiliza ast-types
para recorrer el código, con la esperanza de admitir diferentes analizadores en el futuro.
import { Astx } from 'astx'
constructor(backend: Backend, paths: NodePath<any>[] | Match[], options?: { withCaptures?: Match[] })
backend
es la implementación del analizador/generador que se utiliza.
paths
especifica los NodePath
s o Match
es en los que desea que los métodos Astx
busquen/operen.
.find(...)
( Astx
) Encuentra coincidencias para el patrón dado dentro de las rutas iniciales de esta instancia y devuelve una instancia Astx
que contiene las coincidencias.
Si llama astx.find('foo($$args)')
en la instancia inicial pasada a su función de transformación, encontrará todas las llamadas a foo
dentro del archivo y devolverá esas coincidencias en una nueva instancia Astx
.
Los métodos de la instancia devuelta funcionarán solo en las rutas coincidentes.
Por ejemplo, si hace astx.find('foo($$args)').find('$a + $b')
, la segunda llamada find
solo buscará $a + $b
dentro de coincidencias con foo($$args)
, en lugar de en cualquier parte del archivo.
Puede llamar .find
como método o literal de plantilla etiquetada:
.find`pattern`
.find(pattern: string | string[] | Node | Node[] | NodePath | NodePath[] | ((wrapper: Astx) => boolean), options?: FindOptions)
Si proporciona el patrón como una cadena, debe ser una expresión o declaración válida. De lo contrario, deberían ser nodos AST válidos que ya haya analizado o construido. Puede interpolar cadenas, nodos AST, matrices de nodos AST e instancias Astx
en el literal de plantilla etiquetado.
Por ejemplo, podrías hacer astx.find`${t.identifier('foo')} + 3`
.
O podría hacer coincidir varias declaraciones haciendo
astx . find `
const $a = $b;
$$c;
const $d = $a + $e;
`
Esto coincidiría (por ejemplo) con las declaraciones const foo = 1; const bar = foo + 5;
, con cualquier número de declaraciones entre ellas.
.closest(...)
( Astx
) Como .find()
, pero busca los ancestros de AST en lugar de buscar los descendientes; encuentra el nodo circundante más cercano de cada ruta de entrada que coincide con el patrón dado.
.destruct(...)
( Astx
) Como .find()
, pero no prueba los descendientes de las rutas de entrada con el patrón; En el resultado sólo se incluirán las rutas de entrada que coincidan con el patrón.
FindOptions
Un objeto con las siguientes propiedades opcionales:
FindOptions.where
( { [captureName: string]: (path: Astx) => boolean }
) Donde se dan las condiciones para la captura de nodos. Por ejemplo, si su patrón de búsqueda es $a()
, podría tener { where: { $a: astx => /foo|bar/.test(astx.node.name) } }
, que solo coincidiría con llamadas sin argumentos a foo
o bar
.
.find(...).replace(...)
( void
) Busca y reemplaza coincidencias para el patrón dado dentro root
.
Hay varias formas diferentes de llamar a .replace
. Puede llamar a .find
de cualquier forma descrita anteriormente.
.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[])
Si proporciona el reemplazo como una cadena, debe ser una expresión o declaración válida. Puede proporcionar el reemplazo como nodo(s) AST que ya analizó o construyó. O puede proporcionar una función de reemplazo, que se llamará con cada coincidencia y debe devolver una cadena o Node | Node[]
(puede utilizar la función de cadena de plantilla etiquetada parse
proporcionada como segundo argumento para analizar el código en una cadena. Por ejemplo, puede poner en mayúsculas los nombres de las funciones en todas las llamadas a funciones sin argumentos ( foo(); bar()
se convierte FOO(); BAR()
) con esto:
astx
.find`$fn()`
.replace(({ captures: { $fn } }) => `${$fn.name.toUpperCase()}()`)
.findImports(...)
( Astx
) Una versión conveniente de .find()
para buscar importaciones que tolera especificadores adicionales, coincide con importaciones de valores del mismo nombre si se solicitaron importaciones de tipos, etc.
Por ejemplo, .findImports`import $a from 'a'`
coincidiría con import A, { b, c } from 'a'
o import { default as a } from 'a'
, capturando $a
, mientras que .find`import $a from 'a'`
no coincidiría con ninguno de estos casos.
El patrón debe contener sólo declaraciones de importación.
.addImports(...)
( Astx
) Como .findImports()
, pero agrega las importaciones que no se encontraron. Por ejemplo, dado el código fuente:
import { foo , type bar as qux } from 'foo'
import 'g'
y la operación
const { $bar } = astx . addImports `
import type { bar as $bar } from 'foo'
import FooDefault from 'foo'
import * as g from 'g'
`
La salida sería
import FooDefault , { foo , type bar as qux } from 'foo'
import * as g from 'g'
Con $bar
capturando el identificador qux
.
.removeImports(...)
( boolean
) Toma declaraciones de importación en el mismo formato que .findImports()
pero elimina todos los especificadores dados.
.replaceImport(...).with(...)
( boolean
)Reemplaza un único especificador de importación por otro. Por ejemplo, dada la entrada
import { Match , Route , Location } from 'react-router-dom'
import type { History } from 'history'
y operación
astx . replaceImport `
import { Location } from 'react-router-dom'
` . with `
import type { Location } from 'history'
`
La salida sería
import { Match , Route } from 'react-router-dom'
import type { History , Location } from 'history'
Los patrones de búsqueda y reemplazo deben contener una única declaración de importación con un único especificador.
.remove()
( void
) Elimina las coincidencias de .find()
o capturas enfocadas en esta instancia Astx
.
.matched
( this | null
) Devuelve esta instancia Astx
si tiene al menos una coincidencia; de lo contrario, devuelve null
.
Dado que .find()
, .closest()
y .destruct()
siempre devuelven una instancia Astx
, incluso si no hubo coincidencias, puede usar .find(...).matched
si solo desea un valor definido cuando hay Hubo al menos un partido.
.size()
( number
) Devuelve el número de coincidencias de la llamada .find()
o .closest()
que devolvió esta instancia.
[name: `$${string}` | `$$${string}` | `$$$${string}`]
( Astx
) Obtiene una instancia Astx
centrada en las capturas con el name
de pila.
Por ejemplo, puedes hacer:
for ( const { $v } of astx . find `process.env.$v` ) {
report ( $v . code )
}
.placeholder
( string | undefined
)El nombre del marcador de posición que representa esta instancia. Por ejemplo:
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
)Devuelve el primer nodo de la primera coincidencia. Lanza un error si no hay coincidencias.
.path
( NodePath
)Devuelve la primera ruta de la primera coincidencia. Lanza un error si no hay coincidencias.
.code
( string
)Genera código desde el primer nodo de la primera coincidencia. Lanza un error si no hay coincidencias.
.stringValue
( string
)Devuelve el valor de cadena del primer nodo si la captura enfocada es una captura de cadena. Lanza un error si no hay coincidencias.
[Symbol.iterator]
( Iterator<Astx>
) Itera cada coincidencia y devuelve una instancia Astx
para cada coincidencia.
.matches
( Match[]
) Obtiene las coincidencias de la llamada .find()
o .closest()
que devolvió esta instancia.
.match
( Match
) Obtiene la primera coincidencia de la llamada .find()
o .closest()
que devolvió esta instancia.
Lanza un error si no hubo coincidencias.
.paths
( NodePath[]
) Devuelve las rutas en las que .find()
y .closest()
buscarán. Si esta instancia fue devuelta por .find()
o .closest()
, estas son las rutas de los nodos que coincidieron con el patrón de búsqueda.
.nodes
( Node[]
) Devuelve los nodos en los que .find()
y .closest()
buscarán. Si esta instancia fue devuelta por .find()
o .closest()
, estos son los nodos que coincidieron con el patrón de búsqueda.
.some(predicate)
( boolean
) Devuelve false
a menos que predicate
devuelva verdadero para al menos una coincidencia.
iteratee
es una función que se llamará con match: Astx, index: number, parent: Astx
y devuelve true
o false
.
.every(predicate)
( boolean
) Devuelve true
a menos que predicate
devuelva falso para al menos una coincidencia.
iteratee
es una función que se llamará con match: Astx, index: number, parent: Astx
y devuelve true
o false
.
.filter(iteratee)
( Astx
)Filtra las coincidencias.
iteratee
es una función que se llamará con match: Astx, index: number, parent: Astx
y devuelve true
o false
. Solo se incluirán en el resultado las coincidencias para las que iteratee
devuelva true
.
.map<T>(iteratee)
( T[]
)Mapea los partidos.
iteratee
es una función que se llamará con match: Astx, index: number, parent: Astx
y devuelve el valor para incluir en la matriz de resultados.
.at(index)
( Astx
) Selecciona la coincidencia en el index
dado.
.withCaptures(...captures)
( Astx
) Devuelve una instancia Astx
que contiene capturas de las ...captures
dadas además de las capturas presentes en esta instancia.
Puede pasar los siguientes tipos de argumentos:
Astx
: se incluirán todas las capturas de la instancia.Astx[placeholder]
: se incluirán capturas para el placeholder
dado.{ $name: Astx[placeholder] }
- captura(s) para el placeholder
dado, renombrado a $name
.Match
objetos import { type Match } from 'astx'
.type
El tipo de coincidencia: 'node'
o 'nodes'
.
.path
El NodePath
del nodo coincidente. Si type
es 'nodes'
, serán paths[0]
.
.node
El Node
coincidente. Si type
es 'nodes'
, serán nodes[0]
.
.paths
Los NodePaths
de los nodos coincidentes.
.nodes
Los Node
coincidentes.
.captures
Los Node
se capturan de los marcadores de posición en el patrón de coincidencia. Por ejemplo, si el patrón era foo($bar)
, .captures.$bar
será el Node
del primer argumento.
.pathCaptures
Los NodePath
se capturan de los marcadores de posición en el patrón de coincidencia. Por ejemplo, si el patrón era foo($bar)
, .pathCaptures.$bar
será el NodePath
del primer argumento.
.arrayCaptures
Los Node[]
se capturan de los marcadores de posición de la matriz en el patrón de coincidencia. Por ejemplo, si el patrón era foo({ ...$bar })
, .arrayCaptures.$bar
serán los Node[]
s de las propiedades del objeto.
.arrayPathCaptures
Los NodePath[]
se capturan de los marcadores de posición de la matriz en el patrón de coincidencia. Por ejemplo, si el patrón era foo({ ...$bar })
, .pathArrayCaptures.$bar
serán los NodePath[]
de las propiedades del objeto.
.stringCaptures
Los valores de cadena capturados de los marcadores de posición de cadena en el patrón de coincidencia. Por ejemplo, si el patrón fue import foo from '$foo'
, stringCaptures.$foo
será la ruta de importación.
Al igual que jscodeshift
, puede colocar código para realizar una transformación en un archivo .ts
o .js
(el valor predeterminado es astx.ts
o astx.js
en el directorio de trabajo, a menos que especifique un archivo diferente con la opción -t
CLI).
Sin embargo, la API del archivo de transformación es un poco diferente de jscodeshift
. Puede tener las siguientes exportaciones:
exports.find
(opcional)Una cadena de código o nodo AST del patrón que se buscará en los archivos que se están transformando.
exports.where
(opcional) Donde condiciones para capturar marcadores de posición en exports.find
. Consulte FindOptions.where
( { [captureName: string]: (path: NodePath<any>) => boolean }
) para obtener más información.
exports.replace
(opcional) Una cadena de código, un nodo AST o una función de reemplazo para reemplazar las coincidencias de exports.find
.
Los argumentos de la función son los mismos que se describen en .find().replace()
.
exports.astx
(opcional) Una función para realizar una transformación arbitraria utilizando la API Astx
. Se llama con un objeto con las siguientes propiedades:
file
( string
): la ruta al archivo que se está transformandosource
( string
): el código fuente del archivo que se está transformando.astx
( Astx
): la instancia de API Astx
t
( AstTypes
): definiciones ast-types
para el analizador elegidoexpression
: literal de plantilla etiquetado para analizar el código como una expresiónstatement
: literal de plantilla etiquetada para analizar el código como una declaraciónstatements
: plantilla literal etiquetada para analizar el código como una matriz de declaracionesreport
( (message: unknown) => void
)mark
( (...matches: Astx[]) => void
): marca las coincidencias dadas para que se muestren en la lista de coincidencias de vscode-astx, etc. A diferencia de jscodeshift
, su función de transformación puede ser asíncrona y no tiene que devolver el código transformado, pero puede devolver una string
. También puedes devolver null
para omitir el archivo.
exports.onReport
(opcional) Si llama report(x)
desde una función exports.astx
, se llamará con onReport({ file, report: x })
.
Si está utilizando varios subprocesos de trabajo, se llamará onReport
en el proceso principal, por lo que el mensaje del informe debe ser un valor serializable. Esto permite que una transformación recopile informes de todos los trabajadores (y luego, potencialmente, hacer algo con ellos al finish
).
Si onReport
devuelve una Promise
, se esperará.
exports.finish
(opcional)Esto se llamará después de que la transformación se haya ejecutado en todos los archivos de entrada.
Si está utilizando varios subprocesos de trabajo, se llamará finish
en el proceso principal. Puede usar onReport
y finish
juntos para recopilar información de cada archivo de entrada y producir algún tipo de salida combinada al final.
Si finish
devuelve una Promise
se esperará.
astx
admite la configuración en los siguientes lugares (a través de cosmiconfig
):
astx
en package.json.astxrc
en formato JSON o YAML.astxrc.json
, .astxrc.yaml
, .astxrc.yml
, .astxrc.js
o .astxrc.cjs
astx.config.js
o astx.config.cjs
CommonJS que exporta un objetoSi su código base tiene un formato más bonito, le recomiendo probar esto primero:
{
"parser" : " babel/auto " ,
"parserOptions" : {
"preserveFormat" : " generatorHack "
}
}
(o como opciones CLI)
--parser babel/auto --parserOptions '{"preserveFormat": "generatorHack"}'
Si esto falla, puede probar parser: 'recast/babel/auto'
o los analizadores que no son /auto
.
Su millaje puede variar con recast
; simplemente no pueden mantenerlo actualizado con nuevas funciones de sintaxis en JS y TS con la suficiente rapidez, y he visto que genera una sintaxis no válida demasiadas veces.
De ahora en adelante, voy a trabajar en una solución confiable usando @babel/generator
o prettier
para imprimir el AST modificado, con un gancho para usar la fuente original palabra por palabra para los nodos no modificados.
parser
El analizador a utilizar. Opciones:
babel/auto
(predeterminado)babel
(más rápido que babel/auto
, pero utiliza opciones de análisis predeterminadas, es posible que tengas que configurar parserOptions
)recast/babel
recast/babel/auto
babel/auto
determina automáticamente las opciones de análisis de su configuración de babel, si están presentes. babel
usa opciones de análisis fijas en su lugar, por lo que es más rápido que babel/auto
, pero es posible que tengas que configurar parserOptions
. Las opciones recast/babel(/auto)
usan recast
para preservar el formato. He visto una sintaxis no válida en la salida recast
en algunos archivos, así que utilícela con precaución.
parserOptions
Opciones para pasar al analizador. En este momento, estas son solo las opciones @babel/parser
más las siguientes opciones adicionales:
preserveFormat
(se aplica a: babel
, babel/auto
) preserveFormat: 'generatorHack'
utiliza un truco experimental para preservar el formato de todos los nodos sin cambios secuestrando la API interna @babel/generator
.prettier
Si es false
, no intente utilizar prettier
para reformatear el código fuente transformado. El valor predeterminado es true
.
Astx incluye una CLI para realizar transformaciones. La CLI procesará los archivos proporcionados, luego imprimirá una diferencia de lo que se cambiará y le pedirá que confirme que desea escribir los cambios.
Analizará con babel de forma predeterminada utilizando la versión instalada en su proyecto y la configuración de babel de su proyecto, si corresponde. Puede pasar --parser recast/babel
si desea utilizar recast
para intentar conservar el formato en la salida, pero a veces veo errores de sintaxis en su salida.
A diferencia de jscodeshift
, si prettier
está instalado en su proyecto, formateará el código transformado con 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]