Recherche et remplacement structurels super puissants pour JavaScript et TypeScript pour automatiser votre refactoring
$<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
(facultatif)exports.where
(facultatif)exports.replace
(facultatif)exports.astx
(facultatif)exports.onReport
(facultatif)exports.finish
(facultatif)parser
parserOptions
prettier
Les refactors simples peuvent être fastidieux et répétitifs. Par exemple, supposons que vous souhaitiez apporter la modification suivante dans une base de code :
// before:
rmdir ( 'old/stuff' )
rmdir ( 'new/stuff' , true )
// after:
rmdir ( 'old/stuff' )
rmdir ( 'new/stuff' , { force : true } )
Changer manuellement un tas d'appels vers rmdir
serait nul. Vous pouvez essayer d'utiliser le remplacement de l'expression régulière, mais c'est délicat et ne tolérerait pas bien les espaces et les sauts de ligne à moins que vous ne travailliez très dur sur l'expression régulière. Vous pouvez même utiliser jscodeshift
, mais cela prend trop de temps pour des cas simples comme celui-ci et commence à sembler plus difficile que nécessaire...
Il existe désormais une meilleure option... vous pouvez refactoriser en toute confiance en utilisant astx
!
astx
--find ' rmdir($path, $force) '
--replace ' rmdir($path, { force: $force }) '
Il s'agit d'un exemple de base de modèles astx
, qui ne sont que du code JS ou TS pouvant contenir des espaces réservés et d'autres constructions de correspondance spéciales ; astx
recherche le code correspondant au modèle, acceptant n'importe quelle expression à la place de $path
et $force
. Ensuite, astx
remplace chaque correspondance par le modèle de remplacement, en remplaçant les expressions capturées par $path
( 'new/stuff'
) et $force
( true
).
Mais ce n'est que le début ; Les modèles astx
peuvent être beaucoup plus complexes et puissants que cela, et pour les cas d'utilisation vraiment avancés, ils disposent d'une API intuitive que vous pouvez utiliser :
for ( const match of astx . find `rmdir($path, $force)` ) {
const { $path , $force } = match
// do stuff with $path.node, $path.code, etc...
}
Vous avez beaucoup d'erreurs Do not access Object.prototype method 'hasOwnProperty' from target object
?
// astx.js
exports . find = `$a.hasOwnProperty($b)`
exports . replace = `Object.hasOwn($a, $b)`
Récemment, pour le travail, j'ai voulu faire ce changement :
// 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 } ) ,
]
C'est simple à faire avec la correspondance de liste :
// 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`
Dans jscodeshift-add-imports
j'ai eu un tas de cas de test suivant ce modèle :
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 )
} )
Je voulais les rendre plus SECS, comme ceci :
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' } ,
} )
} )
Voici une transformation pour ce qui précède. (Bien sûr, j'ai dû exécuter quelques variantes pour les cas où le code attendu était différent, 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,
})
`
Je viens enfin de sortir la version 2 en décembre 2022 après des tonnes de travail acharné ? En ce moment, je travaille sur l'extension VSCode. Après cela, je souhaite créer un site Web de documentation qui illustre mieux comment utiliser astx
.
L'extension VSCode est actuellement en version bêta, mais essayez-la !
Pendant que je réfléchissais à cela, j'ai découvert grip, un outil similaire qui a inspiré la syntaxe $
capture. Il y a quand même plusieurs raisons pour lesquelles j'ai décidé de créer astx
:
grasp -e 'setValue($k, $v, true)' -R 'setValueSilently({{k}}, {{v}})' file.js
jscodeshift
que je pourrais utiliser en JS pour des cas d'utilisation avancés qui sont probablement gênants/impossibles dans Grasp La philosophie d’ astx
est donc la suivante :
Collez votre code dans AST Explorer si vous avez besoin d'en savoir plus sur la structure de l'AST.
Les modèles de recherche Astx ne sont que du code JavaScript ou TypeScript qui peut contenir des caractères génériques ou d'autres constructions spéciales comme $Or(A, B)
. De manière générale, les parties du modèle qui ne sont pas des caractères génériques ou des constructions spéciales doivent correspondre exactement.
Par exemple, le modèle de recherche foo($a)
correspond à tout appel à la fonction foo
avec un seul argument. L'argument peut n'importe quoi et est capturé sous la forme $a
.
Les modèles de remplacement sont presque identiques aux modèles de recherche, sauf que les espaces réservés sont remplacés par tout ce qui a été capturé dans le nom de l'espace réservé par le modèle de recherche, et les constructions de recherche spéciales comme $Or(A, B)
n'ont aucune signification particulière dans les modèles de remplacement. (À l'avenir, il pourrait y avoir des constructions de remplacement spéciales qui effectuent une sorte de transformation sur les nœuds capturés.)
Par exemple, le modèle de recherche foo($a)
correspond foo(1 + 2)
, puis le modèle de remplacement foo({ value: $a })
générera le code foo({ value: 1 + 2 })
.
De manière générale, un identifiant commençant par $
est un espace réservé qui fonctionne comme un caractère générique. Il existe trois types d'espaces réservés :
$<name>
correspond à n'importe quel nœud ("espace réservé au nœud")$$<name>
correspond à une liste contiguë de nœuds ("espace réservé au tableau")$$$<name>
: correspond à tous les autres frères et sœurs ("rest placeholder") Le <name>
(s'il est donné) doit commencer par une lettre ou un chiffre ; sinon, l'identifiant ne sera pas traité comme un espace réservé.
Les espaces réservés de repos ( $$$
) ne peuvent pas être des frères et sœurs d'espaces réservés de liste ordonnée ( $$
).
À moins qu'un espace réservé ne soit anonyme, il « capturera » le(s) nœud(s) correspondant(s), ce qui signifie que vous pouvez utiliser le même espace réservé dans le modèle de remplacement pour interpoler le(s) nœud(s) correspondant(s) dans le remplacement généré. Dans l'API Node, vous pouvez également accéder aux chemins/nœuds AST capturés via le nom de l'espace réservé.
$<name>
) Ces espaces réservés correspondent à un seul nœud. Par exemple, le modèle [$a, $b]
correspond à une expression de tableau avec deux éléments, et ces éléments sont capturés sous la forme $a
et $b
.
$$<name>
) Ces espaces réservés correspondent à une liste contiguë de nœuds. Par exemple, le modèle [1, $$a, 2, $$b]
correspond à une expression de tableau avec 1
comme premier élément et 2
comme élément suivant. Tous les éléments entre 1
et les 2
premiers sont capturés sous la forme $$a
, et les éléments après les 2
premiers sont capturés sous la forme $$b
.
$$$<name>
) Ces espaces réservés correspondent au reste des frères et sœurs qui n'ont pas été mis en correspondance par autre chose. Par exemple, le modèle [1, $$$a, 2]
correspond à une expression de tableau contenant les éléments 1
et 2
à n'importe quel index. Tous les autres éléments (y compris les occurrences supplémentaires de 1
et 2
) sont capturés sous la forme $$$a
.
Vous pouvez utiliser un espace réservé sans nom pour faire correspondre les nœuds sans les capturer. $
correspondra à n'importe quel nœud unique, $$
correspondra à une liste contiguë de nœuds et $$$
correspondra à tous les autres frères et sœurs.
Si vous utilisez le même espace réservé de capture plus d'une fois, les positions suivantes devront correspondre à ce qui a été capturé pour la première occurrence de l'espace réservé.
Par exemple, le modèle foo($a, $a, $b, $b)
correspondra uniquement foo(1, 1, {foo: 1}, {foo: 1})
dans ce qui suit :
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
Remarque : les espaces réservés de capture de tableau ( $$a
) et les espaces réservés de capture de repos ( $$$a
) ne prennent actuellement pas en charge le référencement arrière.
Un modèle ObjectExpression
(alias objet littéral) correspondra à n’importe quelle ObjectExpression
de votre code avec les mêmes propriétés dans n’importe quel ordre. Il ne correspondra pas s’il existe des propriétés manquantes ou supplémentaires. Par exemple, { foo: 1, bar: $bar }
correspondra à { foo: 1, bar: 2 }
ou { bar: 'hello', foo: 1 }
mais pas { foo: 1 }
ou { foo: 1, bar: 2, baz: 3 }
.
Vous pouvez faire correspondre des propriétés supplémentaires en utilisant ...$$captureName
, par exemple { foo: 1, ...$$rest }
correspondra à { foo: 1 }
, { foo: 1, bar: 2 }
, { foo: 1, bar: 2, ...props }
etc. Les propriétés supplémentaires seront capturées dans match.arrayCaptures
/ match.arrayPathCaptures
et pourront être réparties dans des expressions de remplacement. Par exemple, astx.find`{ foo: 1, ...$$rest }`.replace`{ bar: 1, ...$$rest }`
transformera { foo: 1, qux: {}, ...props }
dans { bar: 1, qux: {}, ...props }
.
Une propriété spread qui n'est pas de la forme /^$$[a-z0-9]+$/i
n'est pas un espace réservé de capture, par exemple { ...foo }
ne correspondra qu'à { ...foo }
et { ...$_$foo }
ne correspondra qu'à { ...$$foo }
(le début de $_
est une évasion pour $
).
Il n'existe actuellement aucun moyen de faire correspondre les propriétés dans un ordre spécifique, mais cela pourrait être ajouté à l'avenir.
Dans de nombreux cas où il existe une liste de nœuds dans l'AST, vous pouvez faire correspondre plusieurs éléments avec un espace réservé commençant par $$
. Par exemple, [$$before, 3, $$after]
correspondra à toute expression de tableau contenant un élément 3
; les éléments avant les 3
premiers seront capturés dans $$before
et les éléments après les 3
premiers seront capturés dans $$after
.
Cela fonctionne même avec les instructions de bloc. Par exemple, function foo() { $$before; throw new Error('test'); $$after; }
correspondra à function foo()
qui contient un throw new Error('test')
, et les instructions avant et après cette instruction throw seront capturées respectivement dans $$before
et $$after
.
Dans certains cas, la correspondance de liste sera ordonnée par défaut, et dans d'autres cas, elle ne sera pas ordonnée. Par exemple, les correspondances de propriétés ObjectExpression
ne sont pas ordonnées par défaut, comme indiqué dans le tableau ci-dessous. L’utilisation d’un espace réservé $$
ou de l’espace réservé spécial $Ordered
forcera la correspondance ordonnée. L'utilisation d'un espace réservé $$$
ou de l'espace réservé spécial $Unordered
forcera une correspondance non ordonnée.
Si vous utilisez un espace réservé commençant par $$$
, il est traité comme une capture "reste" et tous les autres éléments de l'expression de correspondance seront mis en correspondance dans le désordre. Par exemple, import {a, b, $$$rest} from 'foo'
correspondrait à import {c, b, d, e, a} from 'foo'
, en plaçant les spécificateurs c
, d
et e
dans le $$$rest
Espace réservé $$$rest
.
Les espaces réservés de repos ( $$$
) ne peuvent pas être des frères et sœurs d'espaces réservés de liste ordonnée ( $$
).
Certains éléments marqués TODO fonctionnent probablement réellement, mais n'ont pas été testés.
Taper | Prend en charge la correspondance de liste ? | Non ordonné par défaut ? | Remarques |
---|---|---|---|
ArrayExpression.elements | ✅ | ||
ArrayPattern.elements | ✅ | ||
BlockStatement.body | ✅ | ||
CallExpression.arguments | ✅ | ||
Class(Declaration/Expression).implements | ✅ | ✅ | |
ClassBody.body | ✅ | ✅ | |
ComprehensionExpression.blocks | FAIRE | ||
DeclareClass.body | FAIRE | ✅ | |
DeclareClass.implements | FAIRE | ✅ | |
DeclareExportDeclaration.specifiers | FAIRE | ✅ | |
DeclareInterface.body | FAIRE | ||
DeclareInterface.extends | FAIRE | ||
DoExpression.body | FAIRE | ||
ExportNamedDeclaration.specifiers | ✅ | ✅ | |
Function.decorators | FAIRE | ||
Function.params | ✅ | ||
FunctionTypeAnnotation/TSFunctionType.params | ✅ | ||
GeneratorExpression.blocks | FAIRE | ||
ImportDeclaration.specifiers | ✅ | ✅ | |
(TS)InterfaceDeclaration.body | FAIRE | ✅ | |
(TS)InterfaceDeclaration.extends | FAIRE | ✅ | |
IntersectionTypeAnnotation/TSIntersectionType.types | ✅ | ✅ | |
JSX(Element/Fragment).children | ✅ | ||
JSX(Opening)Element.attributes | ✅ | ✅ | |
MethodDefinition.decorators | FAIRE | ||
NewExpression.arguments | ✅ | ||
ObjectExpression.properties | ✅ | ✅ | |
ObjectPattern.decorators | FAIRE | ||
ObjectPattern.properties | ✅ | ✅ | |
(ObjectTypeAnnotation/TSTypeLiteral).properties | ✅ | ✅ | Utilisez $a: $ pour faire correspondre une propriété, $$a: $ ou $$$a: $ pour faire correspondre plusieurs |
Program.body | ✅ | ||
Property.decorators | FAIRE | ||
SequenceExpression | ✅ | ||
SwitchCase.consequent | ✅ | ||
SwitchStatement.cases | FAIRE | ||
TemplateLiteral.quasis/expressions | ❓ je ne sais pas si je peux trouver une syntaxe | ||
TryStatement.guardedHandlers | FAIRE | ||
TryStatement.handlers | FAIRE | ||
TSFunctionType.parameters | ✅ | ||
TSCallSignatureDeclaration.parameters | FAIRE | ||
TSConstructorType.parameters | FAIRE | ||
TSConstructSignatureDeclaration.parameters | FAIRE | ||
TSDeclareFunction.params | FAIRE | ||
TSDeclareMethod.params | FAIRE | ||
EnumDeclaration.body/TSEnumDeclaration.members | FAIRE | ✅ | |
TSIndexSignature.parameters | FAIRE | ||
TSMethodSignature.parameters | FAIRE | ||
TSModuleBlock.body | FAIRE | ||
TSTypeLiteral.members | ✅ | ✅ | |
TupleTypeAnnotation/TSTupleType.types | ✅ | ||
(TS)TypeParameterDeclaration.params | ✅ | ||
(TS)TypeParameterInstantiation.params | ✅ | ||
UnionTypeAnnotation/TSUnionType.types | ✅ | ✅ | |
VariableDeclaration.declarations | ✅ | ||
WithStatement.body | qui utilise des déclarations with... |
Une chaîne qui n'est qu'un espace réservé comme '$foo'
correspondra à n'importe quelle chaîne et capturera son contenu dans match.stringCaptures.$foo
. Les mêmes règles d'échappement s'appliquent que pour les identifiants. Cela fonctionne également pour les littéraux de modèle comme `$foo`
et les littéraux de modèle balisés comme doSomething`$foo`
.
Cela peut être utile pour travailler avec des instructions d'importation. Par exemple, consultez Conversion des instructions require en importations.
Un commentaire vide ( /**/
) dans un modèle « extraira » un nœud pour la correspondance. Par exemple, le modèle const x = { /**/ $key: $value }
fera simplement correspondre les nœuds ObjectProperty
avec $key: $value
.
L'analyseur ne serait pas capable d'analyser $key: $value
par lui-même ou de savoir que vous voulez dire un ObjectProperty
, par opposition à quelque chose de différent comme le x: number
dans const x: number = 1
, donc utiliser /**/
vous permet pour contourner ce problème. Vous pouvez l'utiliser pour faire correspondre n'importe quel type de nœud qui n'est pas une expression ou une instruction valide en soi. Par exemple, type T = /**/ Array<number>
correspondrait aux annotations de type Array<number>
.
/**/
fonctionne également dans les modèles de remplacement.
$Maybe(pattern)
Correspond soit à l'expression donnée, soit à aucun nœud à sa place. Par exemple, let $a = $Maybe(2)
correspondra à let foo = 2
et let foo
(sans initialiseur), mais pas let foo = 3
.
$Or(...)
Correspond aux nœuds qui correspondent à au moins un des modèles donnés. Par exemple, $Or(foo($$args), {a: $value})
fera correspondre les appels aux littéraux foo
et objet avec uniquement une propriété a
.
$And(...)
Correspond aux nœuds qui correspondent à tous les modèles donnés. Ceci est surtout utile pour affiner les types de nœuds pouvant être capturés dans un espace réservé donné. Par exemple, let $a = $And($init, $a + $b)
correspondra aux déclarations let
où l'initialiseur correspond à $a + $b
, et capturera l'initialiseur sous la forme $init
.
$Maybe<pattern>
Correspond soit à l'annotation de type donnée, soit à aucun nœud à sa place. Par exemple, let $a: $Maybe<number>
correspondra à let foo: number
et let foo
(sans annotation de type), mais pas let foo: string``let foo: string
.
$Or<...>
Correspond aux nœuds qui correspondent à au moins une des annotations de type données. Par exemple, let $x: $Or<number[], string[]>
correspondra aux déclarations let
de type number[]
ou string[]
.
$And<...>
Correspond aux nœuds qui correspondent à toutes les annotations de type données. Ceci est surtout utile pour affiner les types de nœuds pouvant être capturés dans un espace réservé donné. Par exemple, let $a: $And<$type, $elem[]>
correspondra aux déclarations let
où l'annotation de type correspond à $elem[]
et capturera l'annotation de type sous la forme $type
.
$Ordered
Force le modèle à faire correspondre les nœuds frères dans le même ordre.
$Unordered
Force le modèle à correspondre aux nœuds frères dans n'importe quel ordre.
import { NodePath } from 'astx'
Il s'agit de la même interface NodePath
que ast-types
, avec quelques améliorations des définitions de types de méthodes. astx
utilise ast-types
pour parcourir le code, dans l'espoir de prendre en charge différents analyseurs à l'avenir.
import { Astx } from 'astx'
constructor(backend: Backend, paths: NodePath<any>[] | Match[], options?: { withCaptures?: Match[] })
backend
est l’implémentation de l’analyseur/générateur utilisée.
paths
spécifie les NodePath
s ou Match
es sur lesquels vous souhaitez que les méthodes Astx
recherchent/opèrent.
.find(...)
( Astx
) Recherche des correspondances pour le modèle donné dans les chemins de départ de cette instance et renvoie une instance Astx
contenant les correspondances.
Si vous appelez astx.find('foo($$args)')
sur l'instance initiale transmise à votre fonction de transformation, elle trouvera tous les appels à foo
dans le fichier et renverra ces correspondances dans une nouvelle instance Astx
.
Les méthodes sur l'instance renvoyée fonctionneront uniquement sur les chemins correspondants.
Par exemple, si vous effectuez astx.find('foo($$args)').find('$a + $b')
, le deuxième appel find
recherchera uniquement $a + $b
dans les correspondances avec foo($$args)
, plutôt que n'importe où dans le fichier.
Vous pouvez appeler .find
en tant que méthode ou littéral de modèle balisé :
.find`pattern`
.find(pattern: string | string[] | Node | Node[] | NodePath | NodePath[] | ((wrapper: Astx) => boolean), options?: FindOptions)
Si vous donnez le modèle sous forme de chaîne, il doit s'agir d'une expression ou d'une ou plusieurs instructions valides. Sinon, il doit s'agir de nœuds AST valides que vous avez déjà analysés ou construits. Vous pouvez interpoler des chaînes, des nœuds AST, des tableaux de nœuds AST et des instances Astx
dans le littéral de modèle balisé.
Par exemple, vous pourriez faire astx.find`${t.identifier('foo')} + 3`
.
Ou vous pouvez faire correspondre plusieurs déclarations en faisant
astx . find `
const $a = $b;
$$c;
const $d = $a + $e;
`
Cela correspondrait (par exemple) aux instructions const foo = 1; const bar = foo + 5;
, avec un certain nombre de déclarations entre eux.
.closest(...)
( Astx
) Comme .find()
, mais recherche les ancêtres AST au lieu de descendre dans les descendants ; trouve le nœud englobant le plus proche de chaque chemin d'entrée qui correspond au modèle donné.
.destruct(...)
( Astx
) Comme .find()
, mais ne teste pas les descendants du ou des chemins d'entrée par rapport au modèle ; seuls les chemins d'entrée correspondant au modèle seront inclus dans le résultat.
FindOptions
Un objet avec les propriétés facultatives suivantes :
FindOptions.where
( { [captureName: string]: (path: Astx) => boolean }
) Où les conditions pour les captures de nœuds. Par exemple, si votre modèle de recherche est $a()
, vous pourriez avoir { where: { $a: astx => /foo|bar/.test(astx.node.name) } }
, qui ne correspondrait qu'aux appels sans argument à foo
ou au bar
.
.find(...).replace(...)
( void
) Recherche et remplace les correspondances pour le modèle donné dans root
.
Il existe plusieurs manières différentes d’appeler .replace
. Vous pouvez appeler .find
de la manière décrite ci-dessus.
.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 vous donnez le remplacement sous forme de chaîne, il doit s'agir d'une expression ou d'une instruction valide. Vous pouvez donner le remplacement en tant que nœud(s) AST que vous avez déjà analysé ou construit. Ou vous pouvez donner une fonction de remplacement, qui sera appelée à chaque correspondance et devra renvoyer une chaîne ou Node | Node[]
(vous pouvez utiliser la fonction de chaîne de modèle balisée parse
fournie comme deuxième argument pour analyser le code dans une chaîne. Par exemple, vous pouvez mettre en majuscules les noms de fonction dans tous les appels de fonction sans argument ( foo(); bar()
devient FOO(); BAR()
) avec ceci :
astx
.find`$fn()`
.replace(({ captures: { $fn } }) => `${$fn.name.toUpperCase()}()`)
.findImports(...)
( Astx
) Une version pratique de .find()
pour rechercher des importations qui tolèrent des spécificateurs supplémentaires, correspondent aux importations de valeur du même nom si des importations de type ont été demandées, etc.
Par exemple, .findImports`import $a from 'a'`
correspondrait à import A, { b, c } from 'a'
ou import { default as a } from 'a'
, capturant $a
, alors que .find`import $a from 'a'`
ne correspondrait à aucun de ces cas.
Le modèle doit contenir uniquement des instructions d'importation.
.addImports(...)
( Astx
) Comme .findImports()
, mais ajoute toutes les importations introuvables. Par exemple étant donné le code source :
import { foo , type bar as qux } from 'foo'
import 'g'
Et l'opération
const { $bar } = astx . addImports `
import type { bar as $bar } from 'foo'
import FooDefault from 'foo'
import * as g from 'g'
`
La sortie serait
import FooDefault , { foo , type bar as qux } from 'foo'
import * as g from 'g'
Avec $bar
capturant l'identifiant qux
.
.removeImports(...)
( boolean
) Prend les instructions d'importation dans le même format que .findImports()
mais supprime tous les spécificateurs donnés.
.replaceImport(...).with(...)
( boolean
)Remplace un seul spécificateur d'importation par un autre. Par exemple étant donné l'entrée
import { Match , Route , Location } from 'react-router-dom'
import type { History } from 'history'
Et le fonctionnement
astx . replaceImport `
import { Location } from 'react-router-dom'
` . with `
import type { Location } from 'history'
`
La sortie serait
import { Match , Route } from 'react-router-dom'
import type { History , Location } from 'history'
Les modèles de recherche et de remplacement doivent tous deux contenir une seule instruction d'importation avec un seul spécificateur.
.remove()
( void
) Supprime les correspondances de .find()
ou des captures ciblées dans cette instance Astx
.
.matched
( this | null
) Renvoie cette instance Astx
si elle a au moins une correspondance, sinon renvoie null
.
Puisque .find()
, .closest()
et .destruct()
renvoient toujours une instance Astx
, même s'il n'y a pas de correspondance, vous pouvez utiliser .find(...).matched
si vous souhaitez uniquement une valeur définie lorsqu'il y a il y avait au moins un match.
.size()
( number
) Renvoie le nombre de correspondances de l'appel .find()
ou .closest()
qui a renvoyé cette instance.
[name: `$${string}` | `$$${string}` | `$$$${string}`]
( Astx
) Obtient une instance Astx
axée sur la ou les captures portant le name
donné.
Par exemple, vous pouvez faire :
for ( const { $v } of astx . find `process.env.$v` ) {
report ( $v . code )
}
.placeholder
( string | undefined
)Le nom de l'espace réservé que cette instance représente. Par exemple:
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
)Renvoie le premier nœud de la première correspondance. Génère une erreur s'il n'y a aucune correspondance.
.path
( NodePath
)Renvoie le premier chemin de la première correspondance. Génère une erreur s'il n'y a aucune correspondance.
.code
( string
)Génère du code à partir du premier nœud de la première correspondance. Génère une erreur s'il n'y a aucune correspondance.
.stringValue
( string
)Renvoie la valeur de chaîne du premier nœud si la capture ciblée est une capture de chaîne. Génère une erreur s'il n'y a aucune correspondance.
[Symbol.iterator]
( Iterator<Astx>
) Parcourt chaque correspondance, renvoyant une instance Astx
pour chaque correspondance.
.matches
( Match[]
) Obtient les correspondances de l'appel .find()
ou .closest()
qui a renvoyé cette instance.
.match
( Match
) Obtient la première correspondance de l'appel .find()
ou .closest()
qui a renvoyé cette instance.
Génère une erreur s’il n’y a pas de correspondance.
.paths
( NodePath[]
) Renvoie les chemins dans lesquels .find()
et .closest()
rechercheront. Si cette instance a été renvoyée par .find()
ou .closest()
, ce sont les chemins des nœuds qui correspondent au modèle de recherche.
.nodes
( Node[]
) Renvoie les nœuds dans lesquels .find()
et .closest()
rechercheront. Si cette instance a été renvoyée par .find()
ou .closest()
, ce sont les nœuds qui correspondent au modèle de recherche.
.some(predicate)
( boolean
) Renvoie false
sauf si predicate
renvoie true pour au moins une correspondance.
iteratee
est une fonction qui sera appelée avec match: Astx, index: number, parent: Astx
et renvoie true
ou false
.
.every(predicate)
( boolean
) Renvoie true
sauf si predicate
renvoie faux pour au moins une correspondance.
iteratee
est une fonction qui sera appelée avec match: Astx, index: number, parent: Astx
et renvoie true
ou false
.
.filter(iteratee)
( Astx
)Filtre les correspondances.
iteratee
est une fonction qui sera appelée avec match: Astx, index: number, parent: Astx
et renvoie true
ou false
. Seules les correspondances pour lesquelles iteratee
renvoie true
seront incluses dans le résultat.
.map<T>(iteratee)
( T[]
)Cartographie les correspondances.
iteratee
est une fonction qui sera appelée avec match: Astx, index: number, parent: Astx
et renvoie la valeur à inclure dans le tableau de résultats.
.at(index)
( Astx
) Sélectionne la correspondance à l' index
donné.
.withCaptures(...captures)
( Astx
) Renvoie une instance Astx
qui contient des captures des ...captures
en plus des captures présentes dans cette instance.
Vous pouvez transmettre les types d'arguments suivants :
Astx
: toutes les captures de l'instance seront incluses.Astx[placeholder]
- les captures pour l' placeholder
donné seront incluses.{ $name: Astx[placeholder] }
- capture(s) pour l' placeholder
réservé donné, renommé en $name
.Match
des objets import { type Match } from 'astx'
.type
Le type de correspondance : 'node'
ou 'nodes'
.
.path
Le NodePath
du nœud correspondant. Si type
est 'nodes'
, ce sera paths[0]
.
.node
Le Node
correspondant. Si type
est 'nodes'
, ce sera nodes[0]
.
.paths
Les NodePaths
des nœuds correspondants.
.nodes
Node
s correspondants.
.captures
Les Node
sont capturés à partir d'espaces réservés dans le modèle de correspondance. Par exemple, si le modèle était foo($bar)
, .captures.$bar
sera le Node
du premier argument.
.pathCaptures
Les NodePath
sont capturés à partir d'espaces réservés dans le modèle de correspondance. Par exemple, si le modèle était foo($bar)
, .pathCaptures.$bar
sera le NodePath
du premier argument.
.arrayCaptures
Les Node[]
sont capturés à partir des espaces réservés du tableau dans le modèle de correspondance. Par exemple, si le modèle était foo({ ...$bar })
, .arrayCaptures.$bar
sera le Node[]
s des propriétés de l'objet.
.arrayPathCaptures
Les NodePath[]
sont capturés à partir des espaces réservés du tableau dans le modèle de correspondance. Par exemple, si le modèle était foo({ ...$bar })
, .pathArrayCaptures.$bar
sera le NodePath[]
s des propriétés de l'objet.
.stringCaptures
Valeurs de chaîne capturées à partir des espaces réservés de chaîne dans le modèle de correspondance. Par exemple, si le modèle était import foo from '$foo'
, stringCaptures.$foo
sera le chemin d'importation.
Comme jscodeshift
, vous pouvez mettre du code pour effectuer une transformation dans un fichier .ts
ou .js
(la valeur par défaut est astx.ts
ou astx.js
dans le répertoire de travail, sauf si vous spécifiez un fichier différent avec l'option -t
CLI).
L'API du fichier de transformation est cependant un peu différente de jscodeshift
. Vous pouvez avoir les exports suivants :
exports.find
(facultatif)Une chaîne de code ou nœud AST du motif à retrouver dans les fichiers en cours de transformation.
exports.where
(facultatif) Où les conditions de capture des espaces réservés dans exports.find
. Voir FindOptions.where
( { [captureName: string]: (path: NodePath<any>) => boolean }
) pour plus d'informations.
exports.replace
(facultatif) Une chaîne de code, un nœud AST ou une fonction de remplacement pour remplacer les correspondances de exports.find
par.
Les arguments de la fonction sont les mêmes que ceux décrits dans .find().replace()
.
exports.astx
(facultatif) Une fonction pour effectuer une transformation arbitraire à l'aide de l'API Astx
. Il est appelé avec un objet avec les propriétés suivantes :
file
( string
) - Le chemin d'accès au fichier en cours de transformationsource
( string
) - Le code source du fichier en cours de transformationastx
( Astx
) - l'instance de l'API Astx
t
( AstTypes
) - définitions ast-types
pour l'analyseur choisiexpression
- modèle littéral balisé pour analyser le code en tant qu'expressionstatement
- modèle littéral balisé pour analyser le code en tant qu'instructionstatements
- modèle littéral balisé pour analyser le code sous la forme d'un tableau d'instructionsreport
( (message: unknown) => void
)mark
( (...matches: Astx[]) => void
) - marque les correspondances données à afficher dans la liste des correspondances de vscode-astx, etc. Contrairement à jscodeshift
, votre fonction de transformation peut être asynchrone et elle n'a pas besoin de renvoyer le code transformé, mais vous pouvez renvoyer une string
. Vous pouvez également renvoyer null
pour ignorer le fichier.
exports.onReport
(facultatif) Si vous appelez report(x)
à partir d'une fonction exports.astx
, celle-ci sera appelée avec onReport({ file, report: x })
.
Si vous utilisez plusieurs threads de travail, onReport
sera appelé dans le processus parent, le message de rapport doit donc être une valeur sérialisable. Cela permet à une transformation de collecter les rapports de tous les travailleurs (et ensuite potentiellement d'en faire quelque chose dans finish
).
Si onReport
renvoie une Promise
elle sera attendue.
exports.finish
(facultatif)Ceci sera appelé une fois la transformation exécutée sur tous les fichiers d’entrée.
Si vous utilisez plusieurs threads de travail, finish
sera appelé dans le processus parent. Vous pouvez utiliser onReport
et finish
ensemble pour collecter des informations sur chaque fichier d'entrée et produire une sorte de sortie combinée à la fin.
Si finish
renvoie une Promise
elle sera attendue.
astx
prend en charge la configuration aux endroits suivants (via cosmiconfig
) :
astx
dans package.json.astxrc
au format JSON ou YAML.astxrc.json
, .astxrc.yaml
, .astxrc.yml
, .astxrc.js
ou .astxrc.cjs
astx.config.js
ou astx.config.cjs
CommonJS exportant un objetSi votre base de code est formatée avec plus joli, je vous recommande d'essayer ceci en premier :
{
"parser" : " babel/auto " ,
"parserOptions" : {
"preserveFormat" : " generatorHack "
}
}
(ou comme options CLI)
--parser babel/auto --parserOptions '{"preserveFormat": "generatorHack"}'
Si cela échoue, vous pouvez essayer parser: 'recast/babel/auto'
ou les analyseurs non /auto
.
Votre kilométrage peut varier selon recast
; ils ne sont tout simplement pas en mesure de le maintenir à jour assez rapidement avec les nouvelles fonctionnalités de syntaxe de JS et TS, et je l'ai vu trop souvent produire une syntaxe invalide.
A partir de maintenant, je vais travailler sur une solution fiable utilisant @babel/generator
ou prettier
pour imprimer l'AST modifié, avec un hook pour utiliser textuellement la source d'origine pour les nœuds non modifiés.
parser
L'analyseur à utiliser. Possibilités :
babel/auto
(par défaut,)babel
(plus rapide que babel/auto
, mais utilise les options d'analyse par défaut à la place, vous devrez peut-être configurer parserOptions
)recast/babel
recast/babel/auto
babel/auto
détermine automatiquement les options d'analyse à partir de votre configuration babel si elle est présente. babel
utilise à la place des options d'analyse fixes, c'est donc plus rapide que babel/auto
, mais vous devrez peut-être configurer parserOptions
. Les options recast/babel(/auto)
utilisent recast
pour préserver le formatage. J'ai vu une syntaxe de sortie invalide recast
sur certains fichiers, alors utilisez-la avec prudence.
parserOptions
Options à transmettre à l'analyseur. Pour le moment, il ne s'agit que des options @babel/parser
plus les options supplémentaires suivantes :
preserveFormat
(s'applique à : babel
, babel/auto
) preserveFormat: 'generatorHack'
utilise un hack expérimental pour préserver le format de tous les nœuds inchangés en détournant l'API interne @babel/generator
.prettier
Si false
, n'essayez pas d'utiliser prettier
pour reformater le code source transformé. La valeur par défaut est true
.
Astx inclut une CLI pour effectuer des transformations. La CLI traitera les fichiers donnés, puis imprimera un différentiel de ce qui sera modifié et vous demandera de confirmer que vous souhaitez écrire les modifications.
Il analysera avec babel par défaut en utilisant la version installée dans votre projet et la configuration babel de votre projet, le cas échéant. Vous pouvez passer --parser recast/babel
si vous souhaitez utiliser recast
pour essayer de conserver le formatage dans la sortie, mais je vois parfois des erreurs de syntaxe dans sa sortie.
Contrairement à jscodeshift
, si prettier
est installé dans votre projet, il formatera le code transformé avec 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]