Super leistungsstarkes strukturelles Suchen und Ersetzen für JavaScript und TypeScript zur Automatisierung Ihres Refactorings
$<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
(optional)exports.where
(optional)exports.replace
(optional)exports.astx
(optional)exports.onReport
(optional)exports.finish
(optional)parser
parserOptions
prettier
Einfache Refaktoren können mühsam und repetitiv sein. Angenommen, Sie möchten in einer Codebasis die folgende Änderung vornehmen:
// before:
rmdir ( 'old/stuff' )
rmdir ( 'new/stuff' , true )
// after:
rmdir ( 'old/stuff' )
rmdir ( 'new/stuff' , { force : true } )
Eine Reihe von Aufrufen manuell an rmdir
zu ändern, wäre scheiße. Sie könnten versuchen, Regex-Replace zu verwenden, aber es ist umständlich und würde Leerzeichen und Zeilenumbrüche nicht gut tolerieren, es sei denn, Sie arbeiten wirklich hart an der Regex. Sie könnten sogar jscodeshift
verwenden, aber für einfache Fälle wie diesen dauert es zu lange und fühlt sich schwieriger an als nötig ...
Jetzt gibt es eine bessere Option ... Sie können mit astx
beruhigt umgestalten!
astx
--find ' rmdir($path, $force) '
--replace ' rmdir($path, { force: $force }) '
Dies ist ein einfaches Beispiel für astx
-Muster , bei denen es sich lediglich um JS- oder TS-Code handelt, der Platzhalter und andere spezielle passende Konstrukte enthalten kann; astx
sucht nach Code, der dem Muster entspricht, und akzeptiert jeden Ausdruck anstelle von $path
und $force
. Dann ersetzt astx
jede Übereinstimmung durch das Ersetzungsmuster und ersetzt die erfassten Ausdrücke für $path
( 'new/stuff'
) und $force
( true
).
Aber das ist erst der Anfang; astx
-Muster können viel komplexer und leistungsfähiger sein und für wirklich fortgeschrittene Anwendungsfälle verfügt es über eine intuitive API, die Sie verwenden können:
for ( const match of astx . find `rmdir($path, $force)` ) {
const { $path , $force } = match
// do stuff with $path.node, $path.code, etc...
}
Haben Sie viele Fehler Do not access Object.prototype method 'hasOwnProperty' from target object
?
// astx.js
exports . find = `$a.hasOwnProperty($b)`
exports . replace = `Object.hasOwn($a, $b)`
Kürzlich wollte ich aus beruflichen Gründen diese Änderung vornehmen:
// 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 } ) ,
]
Mit dem Listenabgleich geht das ganz einfach:
// 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`
In jscodeshift-add-imports
hatte ich eine Reihe von Testfällen, die diesem Muster folgten:
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 )
} )
Ich wollte sie trockener machen, so:
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' } ,
} )
} )
Hier war eine Transformation für das Obige. (Natürlich musste ich einige Variationen davon ausführen, wenn der erwartete Code anders war usw.)
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,
})
`
Nach einer Menge harter Arbeit habe ich gerade endlich Version 2 im Dezember 2022 veröffentlicht? Im Moment arbeite ich an der VSCode-Erweiterung. Danach möchte ich eine Dokumentationswebsite erstellen, die die Verwendung astx
besser veranschaulicht.
Die VSCode-Erweiterung befindet sich derzeit in der Betaphase, aber probieren Sie es aus!
Während ich darüber nachdachte, dies zu machen, entdeckte ich grip, ein ähnliches Tool, das die $
Capture-Syntax inspirierte. Es gibt mehrere Gründe, warum ich mich trotzdem dazu entschieden habe, astx
zu machen:
grasp -e 'setValue($k, $v, true)' -R 'setValueSilently({{k}}, {{v}})' file.js
jscodeshift
-ähnliche API, die ich in JS für erweiterte Anwendungsfälle verwenden kann, die in Grasp wahrscheinlich umständlich/unmöglich sind Die Philosophie von astx
ist also:
Fügen Sie Ihren Code in den AST Explorer ein, wenn Sie mehr über die Struktur des AST erfahren möchten.
Astx-Suchmuster sind lediglich JavaScript- oder TypeScript-Code, der Platzhalter oder andere spezielle Konstrukte wie $Or(A, B)
enthalten kann. Im Allgemeinen müssen Teile des Musters, die keine Platzhalter oder speziellen Konstrukte sind, genau übereinstimmen.
Das Suchmuster foo($a)
gleicht beispielsweise jeden Aufruf der Funktion foo
mit einem einzelnen Argument ab. Das Argument kann alles sein und wird als $a
erfasst .
Ersetzungsmuster sind fast identisch mit Suchmustern, außer dass Platzhalter durch das ersetzt werden, was durch das Suchmuster im Platzhalternamen erfasst wurde, und spezielle Suchkonstrukte wie $Or(A, B)
in Ersetzungsmustern keine besondere Bedeutung haben. (In Zukunft wird es möglicherweise spezielle Ersetzungskonstrukte geben, die eine Art Transformation auf erfassten Knoten durchführen.)
Wenn beispielsweise das Suchmuster foo($a)
mit foo(1 + 2)
übereinstimmt, generiert das Ersetzungsmuster foo({ value: $a })
den Code foo({ value: 1 + 2 })
.
Im Allgemeinen ist ein Bezeichner, der mit $
beginnt, ein Platzhalter , der wie ein Platzhalter funktioniert. Es gibt drei Arten von Platzhaltern:
$<name>
entspricht jedem einzelnen Knoten („Knotenplatzhalter“)$$<name>
entspricht einer zusammenhängenden Liste von Knoten („Array-Platzhalter“)$$$<name>
: entspricht allen anderen Geschwistern („Rest-Platzhalter“) Der <name>
(falls angegeben) muss mit einem Buchstaben oder einer Zahl beginnen; Andernfalls wird der Bezeichner nicht als Platzhalter behandelt.
Rest-Platzhalter ( $$$
) dürfen keine gleichgeordneten Elemente von Platzhaltern für geordnete Listen ( $$
) sein.
Sofern ein Platzhalter nicht anonym ist, „erfasst“ er die übereinstimmenden Knoten, was bedeutet, dass Sie denselben Platzhalter im Ersetzungsmuster verwenden können, um die übereinstimmenden Knoten in den generierten Ersatz zu interpolieren. In der Node API können Sie über den Platzhalternamen auch auf die erfassten AST-Pfade/Knoten zugreifen.
$<name>
) Diese Platzhalter entsprechen einem einzelnen Knoten. Beispielsweise entspricht das Muster [$a, $b]
einem Array-Ausdruck mit zwei Elementen, und diese Elemente werden als $a
und $b
erfasst.
$$<name>
) Diese Platzhalter entsprechen einer zusammenhängenden Liste von Knoten. Beispielsweise entspricht das Muster [1, $$a, 2, $$b]
einem Array-Ausdruck mit 1
als erstem Element und 2
als nachfolgendem Element. Alle Elemente zwischen 1
und den ersten 2
werden als $$a
erfasst, und Elemente nach den ersten 2
werden als $$b
erfasst.
$$$<name>
) Diese Platzhalter stimmen mit den übrigen Geschwistern überein, denen nichts anderes zugeordnet wurde. Beispielsweise entspricht das Muster [1, $$$a, 2]
einem Array-Ausdruck, der an einem beliebigen Index die Elemente 1
und 2
aufweist. Alle anderen Elemente (einschließlich zusätzlicher Vorkommen von 1
und 2
) werden als $$$a
erfasst.
Sie können einen Platzhalter ohne Namen verwenden, um Knoten abzugleichen, ohne sie zu erfassen. $
stimmt mit jedem einzelnen Knoten überein, $$
stimmt mit einer zusammenhängenden Liste von Knoten überein und $$$
stimmt mit allen anderen Geschwistern überein.
Wenn Sie denselben Erfassungsplatzhalter mehr als einmal verwenden, müssen die nachfolgenden Positionen mit denen übereinstimmen, die beim ersten Vorkommen des Platzhalters erfasst wurden.
Beispielsweise stimmt das Muster foo($a, $a, $b, $b)
im Folgenden nur mit foo(1, 1, {foo: 1}, {foo: 1})
überein:
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
Hinweis : Array-Capture-Platzhalter ( $$a
) und Rest-Capture-Platzhalter ( $$$a
) unterstützen derzeit keine Rückreferenzierung.
Ein ObjectExpression
Muster (auch bekannt als Objektliteral) entspricht jedem ObjectExpression
in Ihrem Code mit denselben Eigenschaften in beliebiger Reihenfolge. Bei fehlenden oder zusätzlichen Eigenschaften erfolgt keine Übereinstimmung. Beispielsweise stimmt { foo: 1, bar: $bar }
mit { foo: 1, bar: 2 }
oder { bar: 'hello', foo: 1 }
überein, aber nicht mit { foo: 1 }
oder { foo: 1, bar: 2, baz: 3 }
.
Sie können zusätzliche Eigenschaften abgleichen, indem Sie ...$$captureName
verwenden. Beispielsweise entspricht { foo: 1, ...$$rest }
{ foo: 1 }
, { foo: 1, bar: 2 }
, { foo: 1, bar: 2, ...props }
usw. Die zusätzlichen Eigenschaften werden in match.arrayCaptures
/ match.arrayPathCaptures
erfasst und können in Ersatzausdrücken verteilt werden. Zum Beispiel transformiert astx.find`{ foo: 1, ...$$rest }`.replace`{ bar: 1, ...$$rest }`
{ foo: 1, qux: {}, ...props }
in { bar: 1, qux: {}, ...props }
.
Eine Spread-Eigenschaft, die nicht die Form /^$$[a-z0-9]+$/i
hat, ist kein Capture-Platzhalter, zum Beispiel { ...foo }
stimmt nur mit { ...foo }
überein und { ...$_$foo }
stimmt nur mit { ...$$foo }
überein (führendes $_
ist ein Escapezeichen für $
).
Derzeit gibt es keine Möglichkeit, Eigenschaften in einer bestimmten Reihenfolge abzugleichen, diese könnte jedoch in Zukunft hinzugefügt werden.
In vielen Fällen, in denen es im AST eine Liste von Knoten gibt, können Sie mehrere Elemente mit einem Platzhalter abgleichen, der mit $$
beginnt. Beispielsweise stimmt [$$before, 3, $$after]
mit jedem Array-Ausdruck überein, der ein Element 3
enthält; Elemente vor den ersten 3
werden in $$before
erfasst und Elemente nach den ersten 3
werden in $$after
erfasst.
Dies funktioniert auch mit Blockanweisungen. Beispielsweise function foo() { $$before; throw new Error('test'); $$after; }
stimmt mit function foo()
überein, die einen throw new Error('test')
enthält, und die Anweisungen vor und nach dieser throw-Anweisung werden in $$before
bzw. $$after
erfasst.
In manchen Fällen erfolgt der Listenabgleich standardmäßig geordnet, in manchen Fällen ist er ungeordnet. Beispielsweise sind ObjectExpression
Eigenschaftsübereinstimmungen standardmäßig ungeordnet, wie in der folgenden Tabelle dargestellt. Durch die Verwendung eines $$
Platzhalters oder des speziellen $Ordered
-Platzhalters wird ein geordneter Abgleich erzwungen. Durch die Verwendung eines $$$
Platzhalters oder des speziellen $Unordered
-Platzhalters wird ein ungeordneter Abgleich erzwungen.
Wenn Sie einen Platzhalter verwenden, der mit $$$
beginnt, wird dieser als „Rest“-Erfassung behandelt und alle anderen Elemente des Übereinstimmungsausdrucks werden in der falschen Reihenfolge abgeglichen. Beispielsweise würde import {a, b, $$$rest} from 'foo'
mit import {c, b, d, e, a} from 'foo'
übereinstimmen und die Spezifizierer c
, d
und e
in $$$rest
einfügen $$$rest
Platzhalter.
Rest-Platzhalter ( $$$
) dürfen keine gleichgeordneten Elemente von Platzhaltern für geordnete Listen ( $$
) sein.
Einige mit TODO gekennzeichnete Elemente funktionieren wahrscheinlich tatsächlich, sind jedoch ungetestet.
Typ | Unterstützt Listenabgleich? | Standardmäßig ungeordnet? | Notizen |
---|---|---|---|
ArrayExpression.elements | ✅ | ||
ArrayPattern.elements | ✅ | ||
BlockStatement.body | ✅ | ||
CallExpression.arguments | ✅ | ||
Class(Declaration/Expression).implements | ✅ | ✅ | |
ClassBody.body | ✅ | ✅ | |
ComprehensionExpression.blocks | TODO | ||
DeclareClass.body | TODO | ✅ | |
DeclareClass.implements | TODO | ✅ | |
DeclareExportDeclaration.specifiers | TODO | ✅ | |
DeclareInterface.body | TODO | ||
DeclareInterface.extends | TODO | ||
DoExpression.body | TODO | ||
ExportNamedDeclaration.specifiers | ✅ | ✅ | |
Function.decorators | TODO | ||
Function.params | ✅ | ||
FunctionTypeAnnotation/TSFunctionType.params | ✅ | ||
GeneratorExpression.blocks | TODO | ||
ImportDeclaration.specifiers | ✅ | ✅ | |
(TS)InterfaceDeclaration.body | TODO | ✅ | |
(TS)InterfaceDeclaration.extends | TODO | ✅ | |
IntersectionTypeAnnotation/TSIntersectionType.types | ✅ | ✅ | |
JSX(Element/Fragment).children | ✅ | ||
JSX(Opening)Element.attributes | ✅ | ✅ | |
MethodDefinition.decorators | TODO | ||
NewExpression.arguments | ✅ | ||
ObjectExpression.properties | ✅ | ✅ | |
ObjectPattern.decorators | TODO | ||
ObjectPattern.properties | ✅ | ✅ | |
(ObjectTypeAnnotation/TSTypeLiteral).properties | ✅ | ✅ | Verwenden Sie $a: $ um eine Eigenschaft abzugleichen, $$a: $ oder $$$a: $ um mehrere abzugleichen |
Program.body | ✅ | ||
Property.decorators | TODO | ||
SequenceExpression | ✅ | ||
SwitchCase.consequent | ✅ | ||
SwitchStatement.cases | TODO | ||
TemplateLiteral.quasis/expressions | ❓ Ich bin mir nicht sicher, ob ich eine Syntax finden kann | ||
TryStatement.guardedHandlers | TODO | ||
TryStatement.handlers | TODO | ||
TSFunctionType.parameters | ✅ | ||
TSCallSignatureDeclaration.parameters | TODO | ||
TSConstructorType.parameters | TODO | ||
TSConstructSignatureDeclaration.parameters | TODO | ||
TSDeclareFunction.params | TODO | ||
TSDeclareMethod.params | TODO | ||
EnumDeclaration.body/TSEnumDeclaration.members | TODO | ✅ | |
TSIndexSignature.parameters | TODO | ||
TSMethodSignature.parameters | TODO | ||
TSModuleBlock.body | TODO | ||
TSTypeLiteral.members | ✅ | ✅ | |
TupleTypeAnnotation/TSTupleType.types | ✅ | ||
(TS)TypeParameterDeclaration.params | ✅ | ||
(TS)TypeParameterInstantiation.params | ✅ | ||
UnionTypeAnnotation/TSUnionType.types | ✅ | ✅ | |
VariableDeclaration.declarations | ✅ | ||
WithStatement.body | Wer verwendet with-Anweisungen... |
Eine Zeichenfolge, die nur ein Platzhalter wie '$foo'
ist, passt zu jeder Zeichenfolge und erfasst ihren Inhalt in match.stringCaptures.$foo
. Es gelten die gleichen Escape-Regeln wie für Bezeichner. Dies funktioniert auch für Vorlagenliterale wie `$foo`
und getaggte Vorlagenliterale wie doSomething`$foo`
.
Dies kann bei der Arbeit mit Importanweisungen hilfreich sein. Siehe beispielsweise „Require“-Anweisungen in „Importe“ umwandeln.
Ein leerer Kommentar ( /**/
) in einem Muster „extrahiert“ einen Knoten zum Abgleich. Beispielsweise gleicht das Muster const x = { /**/ $key: $value }
nur ObjectProperty
Knoten mit $key: $value
ab.
Der Parser wäre nicht in der Lage $key: $value
selbst zu analysieren oder zu wissen, dass Sie eine ObjectProperty
meinen, im Gegensatz zu etwas anderem wie der x: number
in const x: number = 1
, also ermöglicht Ihnen die Verwendung /**/
um dies zu umgehen. Sie können dies verwenden, um jeden Knotentyp abzugleichen, der für sich genommen kein gültiger Ausdruck oder keine gültige Anweisung ist. Beispielsweise würde type T = /**/ Array<number>
mit Anmerkungen vom Typ Array<number>
übereinstimmen.
/**/
funktioniert auch in Ersetzungsmustern.
$Maybe(pattern)
Entspricht entweder dem angegebenen Ausdruck oder keinem Knoten an seiner Stelle. Zum Beispiel passt let $a = $Maybe(2)
zu let foo = 2
und let foo
(ohne Initialisierer), aber nicht let foo = 3
.
$Or(...)
Entspricht Knoten, die mindestens einem der angegebenen Muster entsprechen. Beispielsweise gleicht $Or(foo($$args), {a: $value})
Aufrufe von foo
und Objektliteralen mit nur einer a
Eigenschaft ab.
$And(...)
Entspricht Knoten, die allen angegebenen Mustern entsprechen. Dies ist vor allem nützlich, um die Knotentypen einzugrenzen, die in einem bestimmten Platzhalter erfasst werden können. Beispielsweise stimmt let $a = $And($init, $a + $b)
mit let
-Deklarationen überein, bei denen der Initialisierer mit $a + $b
übereinstimmt, und erfasst den Initialisierer als $init
.
$Maybe<pattern>
Entspricht entweder der angegebenen Typanmerkung oder keinem Knoten an ihrer Stelle. Beispielsweise stimmt let $a: $Maybe<number>
mit let foo: number
und let foo
(ohne Typanmerkung) überein, aber nicht mit let foo: string``let foo: string
.
$Or<...>
Entspricht Knoten, die mit mindestens einer der angegebenen Typanmerkungen übereinstimmen. Beispielsweise stimmt let $x: $Or<number[], string[]>
mit let
Deklarationen vom Typ number[]
oder string[]
überein.
$And<...>
Entspricht Knoten, die allen angegebenen Typanmerkungen entsprechen. Dies ist vor allem nützlich, um die Knotentypen einzugrenzen, die in einem bestimmten Platzhalter erfasst werden können. Beispielsweise stimmt let $a: $And<$type, $elem[]>
mit let
Deklarationen überein, bei denen die Typanmerkung mit $elem[]
übereinstimmt, und erfasst die Typanmerkung als $type
.
$Ordered
Erzwingt, dass das Muster mit Geschwisterknoten in derselben Reihenfolge übereinstimmt.
$Unordered
Erzwingt, dass das Muster mit Geschwisterknoten in beliebiger Reihenfolge übereinstimmt.
import { NodePath } from 'astx'
Dies ist die gleiche NodePath
Schnittstelle wie ast-types
, mit einigen Verbesserungen an den Methodentypdefinitionen. astx
verwendet ast-types
zum Durchlaufen von Code, in der Hoffnung, in Zukunft verschiedene Parser unterstützen zu können.
import { Astx } from 'astx'
constructor(backend: Backend, paths: NodePath<any>[] | Match[], options?: { withCaptures?: Match[] })
backend
ist die verwendete Parser-/Generator-Implementierung.
paths
gibt die NodePath
s oder Match
es an, nach denen Astx
-Methoden suchen/arbeiten sollen.
.find(...)
( Astx
) Findet Übereinstimmungen für das angegebene Muster in den Startpfaden dieser Instanz und gibt eine Astx
Instanz zurück, die die Übereinstimmungen enthält.
Wenn Sie astx.find('foo($$args)')
für die erste Instanz aufrufen, die an Ihre Transformationsfunktion übergeben wurde, werden alle Aufrufe von foo
in der Datei gefunden und diese Übereinstimmungen in einer neuen Astx
-Instanz zurückgegeben.
Methoden für die zurückgegebene Instanz werden nur auf den übereinstimmenden Pfaden ausgeführt.
Wenn Sie beispielsweise astx.find('foo($$args)').find('$a + $b')
ausführen, sucht der zweite find
nur nach $a + $b
innerhalb von Übereinstimmungen mit foo($$args)
und nicht irgendwo in der Datei.
Sie können .find
als Methode oder getaggtes Vorlagenliteral aufrufen:
.find`pattern`
.find(pattern: string | string[] | Node | Node[] | NodePath | NodePath[] | ((wrapper: Astx) => boolean), options?: FindOptions)
Wenn Sie das Muster als Zeichenfolge angeben, muss es ein gültiger Ausdruck oder eine gültige Anweisung sein. Andernfalls sollten es gültige AST-Knoten sein, die Sie bereits analysiert oder erstellt haben. Sie können Zeichenfolgen, AST-Knoten, Arrays von AST-Knoten und Astx
Instanzen im getaggten Vorlagenliteral interpolieren.
Zum Beispiel könnten Sie astx.find`${t.identifier('foo')} + 3`
ausführen.
Oder Sie könnten auf diese Weise mehrere Aussagen zuordnen
astx . find `
const $a = $b;
$$c;
const $d = $a + $e;
`
Dies würde beispielsweise mit den Anweisungen const foo = 1; const bar = foo + 5;
, mit beliebig vielen Anweisungen dazwischen.
.closest(...)
( Astx
) Wie .find()
, durchsucht jedoch die AST-Vorfahren nach oben statt nach unten nach Nachkommen; findet den nächstgelegenen umschließenden Knoten jedes Eingabepfads, der dem angegebenen Muster entspricht.
.destruct(...)
( Astx
) Wie .find()
, testet jedoch nicht die Nachkommen der Eingabepfade anhand des Musters; Nur Eingabepfade, die mit dem Muster übereinstimmen, werden in das Ergebnis einbezogen.
FindOptions
Ein Objekt mit den folgenden optionalen Eigenschaften:
FindOptions.where
( { [captureName: string]: (path: Astx) => boolean }
) Wo Bedingungen für Knotenerfassungen. Wenn Ihr Suchmuster beispielsweise $a()
lautet, könnten Sie { where: { $a: astx => /foo|bar/.test(astx.node.name) } }
haben, was nur mit Aufrufen ohne Argumente übereinstimmen würde to foo
oder bar
.
.find(...).replace(...)
( void
) Sucht und ersetzt Übereinstimmungen für das angegebene Muster in root
.
Es gibt verschiedene Möglichkeiten, .replace
aufzurufen. Sie können .find
auf jede oben beschriebene Weise aufrufen.
.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[])
Wenn Sie den Ersatz als Zeichenfolge angeben, muss es sich um einen gültigen Ausdruck oder eine gültige Anweisung handeln. Sie können den Ersatz als AST-Knoten angeben, die Sie bereits analysiert oder erstellt haben. Oder Sie können eine Ersetzungsfunktion angeben, die bei jedem Treffer aufgerufen wird und einen String oder Node | Node[]
zurückgeben muss Node | Node[]
(Sie können die als zweites Argument bereitgestellte parse
Tagged-Template-String-Funktion verwenden, um Code in einen String zu parsen. Beispielsweise könnten Sie die Funktionsnamen in allen Funktionsaufrufen ohne Argumente in Großbuchstaben schreiben ( foo(); bar()
wird zu FOO(); BAR()
) mit diesem:
astx
.find`$fn()`
.replace(({ captures: { $fn } }) => `${$fn.name.toUpperCase()}()`)
.findImports(...)
( Astx
) Eine praktische Version von .find()
zum Suchen von Importen, die zusätzliche Spezifizierer toleriert, Wertimporte mit demselben Namen abgleicht, wenn Typimporte angefordert wurden usw.
Zum Beispiel würde .findImports`import $a from 'a'`
mit import A, { b, c } from 'a'
oder import { default as a } from 'a'
übereinstimmen und dabei $a
erfassen, während .find`import $a from 'a'`
würde keinem dieser Fälle entsprechen.
Das Muster darf nur Importanweisungen enthalten.
.addImports(...)
( Astx
) Wie .findImports()
, fügt jedoch alle Importe hinzu, die nicht gefunden wurden. Zum Beispiel anhand des Quellcodes:
import { foo , type bar as qux } from 'foo'
import 'g'
Und die Operation
const { $bar } = astx . addImports `
import type { bar as $bar } from 'foo'
import FooDefault from 'foo'
import * as g from 'g'
`
Die Ausgabe wäre
import FooDefault , { foo , type bar as qux } from 'foo'
import * as g from 'g'
Mit $bar
wird der Bezeichner qux
erfasst.
.removeImports(...)
( boolean
) Akzeptiert Importanweisungen im gleichen Format wie .findImports()
entfernt jedoch alle angegebenen Spezifizierer.
.replaceImport(...).with(...)
( boolean
)Ersetzt einen einzelnen Importspezifizierer durch einen anderen. Zum Beispiel die Eingabe gegeben
import { Match , Route , Location } from 'react-router-dom'
import type { History } from 'history'
Und Betrieb
astx . replaceImport `
import { Location } from 'react-router-dom'
` . with `
import type { Location } from 'history'
`
Die Ausgabe wäre
import { Match , Route } from 'react-router-dom'
import type { History , Location } from 'history'
Die Such- und Ersetzungsmuster müssen beide eine einzige Importanweisung mit einem einzigen Spezifizierer enthalten.
.remove()
( void
) Entfernt die Übereinstimmungen aus .find()
oder fokussierten Capture(s) in dieser Astx
Instanz.
.matched
( this | null
) Gibt diese Astx
-Instanz zurück, wenn sie mindestens eine Übereinstimmung hat, andernfalls wird null
zurückgegeben.
Da .find()
, .closest()
und .destruct()
immer eine Astx
Instanz zurückgeben, auch wenn es keine Übereinstimmungen gab, können Sie .find(...).matched
verwenden, wenn Sie nur dann einen definierten Wert wünschen, wenn dieser vorhanden ist war mindestens ein Match.
.size()
( number
) Gibt die Anzahl der Übereinstimmungen aus dem .find()
oder .closest()
Aufruf zurück, der diese Instanz zurückgegeben hat.
[name: `$${string}` | `$$${string}` | `$$$${string}`]
( Astx
) Ruft eine Astx
Instanz ab, die sich auf die Erfassung(en) mit dem angegebenen name
konzentriert.
Sie können zum Beispiel Folgendes tun:
for ( const { $v } of astx . find `process.env.$v` ) {
report ( $v . code )
}
.placeholder
( string | undefined
)Der Name des Platzhalters, den diese Instanz darstellt. Zum Beispiel:
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
)Gibt den ersten Knoten der ersten Übereinstimmung zurück. Gibt einen Fehler aus, wenn keine Übereinstimmungen vorhanden sind.
.path
( NodePath
)Gibt den ersten Pfad der ersten Übereinstimmung zurück. Gibt einen Fehler aus, wenn keine Übereinstimmungen vorhanden sind.
.code
( string
)Generiert Code aus dem ersten Knoten der ersten Übereinstimmung. Gibt einen Fehler aus, wenn keine Übereinstimmungen vorhanden sind.
.stringValue
( string
)Gibt den Zeichenfolgenwert des ersten Knotens zurück, wenn es sich bei der fokussierten Erfassung um eine Zeichenfolgenerfassung handelt. Gibt einen Fehler aus, wenn keine Übereinstimmungen vorhanden sind.
[Symbol.iterator]
( Iterator<Astx>
) Durchläuft jede Übereinstimmung und gibt für jede Übereinstimmung eine Astx
Instanz zurück.
.matches
( Match[]
) Ruft die Übereinstimmungen vom .find()
oder .closest()
-Aufruf ab, der diese Instanz zurückgegeben hat.
.match
( Match
) Ruft die erste Übereinstimmung vom .find()
oder .closest()
Aufruf ab, der diese Instanz zurückgegeben hat.
Gibt einen Fehler aus, wenn keine Übereinstimmungen vorhanden sind.
.paths
( NodePath[]
) Gibt die Pfade zurück, in denen .find()
und .closest()
suchen. Wenn diese Instanz von .find()
oder .closest()
zurückgegeben wurde, sind dies die Pfade von Knoten, die mit dem Suchmuster übereinstimmen.
.nodes
( Node[]
) Gibt die Knoten zurück, in denen .find()
und .closest()
suchen. Wenn diese Instanz von .find()
oder .closest()
zurückgegeben wurde, sind dies die Knoten, die mit dem Suchmuster übereinstimmten.
.some(predicate)
( boolean
) Gibt false
zurück, es sei denn, predicate
gibt für mindestens eine Übereinstimmung „truey“ zurück.
iteratee
ist eine Funktion, die mit match: Astx, index: number, parent: Astx
aufgerufen wird und true
oder false
zurückgibt.
.every(predicate)
( boolean
) Gibt true
zurück, andernfalls gibt predicate
„falsy“ für mindestens eine Übereinstimmung zurück.
iteratee
ist eine Funktion, die mit match: Astx, index: number, parent: Astx
aufgerufen wird und true
oder false
zurückgibt.
.filter(iteratee)
( Astx
)Filtert die Übereinstimmungen.
iteratee
ist eine Funktion, die mit match: Astx, index: number, parent: Astx
aufgerufen wird und true
oder false
zurückgibt. Nur Übereinstimmungen, für die iteratee
true
zurückgibt, werden in das Ergebnis einbezogen.
.map<T>(iteratee)
( T[]
)Ordnet die Übereinstimmungen zu.
iteratee
ist eine Funktion, die mit match: Astx, index: number, parent: Astx
aufgerufen wird und den Wert zurückgibt, der in das Ergebnisarray aufgenommen werden soll.
.at(index)
( Astx
) Wählt die Übereinstimmung am angegebenen index
aus.
.withCaptures(...captures)
( Astx
) Gibt eine Astx
-Instanz zurück, die zusätzlich zu den in dieser Instanz vorhandenen Captures auch Captures aus den angegebenen ...captures
enthält.
Sie können die folgenden Arten von Argumenten übergeben:
Astx
Instanzen – alle Erfassungen der Instanz werden einbezogen.Astx[placeholder]
-Instanzen – Erfassung(en) für den angegebenen placeholder
werden eingeschlossen.{ $name: Astx[placeholder] }
– Capture(s) für den angegebenen placeholder
, umbenannt in $name
.Match
Objekte zu import { type Match } from 'astx'
.type
Der Typ der Übereinstimmung: 'node'
oder 'nodes'
.
.path
Der NodePath
des übereinstimmenden Knotens. Wenn type
'nodes'
ist, ist dies paths[0]
.
.node
Der übereinstimmende Node
. Wenn type
'nodes'
ist, ist dies nodes[0]
.
.paths
Die NodePaths
der übereinstimmenden Knoten.
.nodes
Die passenden Node
s.
.captures
Der Node
wird aus Platzhaltern im Übereinstimmungsmuster erfasst. Wenn das Muster beispielsweise foo($bar)
war, ist .captures.$bar
der Node
des ersten Arguments.
.pathCaptures
Der NodePath
wird aus Platzhaltern im Übereinstimmungsmuster erfasst. Wenn das Muster beispielsweise foo($bar)
war, .pathCaptures.$bar
der NodePath
des ersten Arguments.
.arrayCaptures
Der Node[]
wird aus Array-Platzhaltern im Übereinstimmungsmuster erfasst. Wenn das Muster beispielsweise foo({ ...$bar })
lautete, .arrayCaptures.$bar
die Node[]
s der Objekteigenschaften.
.arrayPathCaptures
Der NodePath[]
wird aus Array-Platzhaltern im Übereinstimmungsmuster erfasst. Wenn das Muster beispielsweise foo({ ...$bar })
war, handelt es sich bei .pathArrayCaptures.$bar
um die NodePath[]
s der Objekteigenschaften.
.stringCaptures
Die aus Zeichenfolgenplatzhaltern im Übereinstimmungsmuster erfassten Zeichenfolgenwerte. Wenn das Muster beispielsweise import foo from '$foo'
lautete, stringCaptures.$foo
der Importpfad.
Wie bei jscodeshift
können Sie Code zum Durchführen einer Transformation in eine .ts
oder .js
Datei einfügen (standardmäßig astx.ts
oder astx.js
im Arbeitsverzeichnis, es sei denn, Sie geben mit der CLI-Option -t
eine andere Datei an).
Die Transformationsdatei-API unterscheidet sich jedoch ein wenig von jscodeshift
. Sie können folgende Exporte durchführen:
exports.find
(optional)Eine Codezeichenfolge oder ein AST-Knoten des Musters, das in den transformierten Dateien gefunden werden soll.
exports.where
(optional) Wo Bedingungen für die Erfassung von Platzhaltern in exports.find
gelten. Weitere Informationen finden Sie unter FindOptions.where
( { [captureName: string]: (path: NodePath<any>) => boolean }
).
exports.replace
(optional) Eine Codezeichenfolge, ein AST-Knoten oder eine Ersetzungsfunktion, um Übereinstimmungen von exports.find
durch zu ersetzen.
Die Funktionsargumente sind die gleichen wie in .find().replace()
beschrieben.
exports.astx
(optional) Eine Funktion zum Durchführen einer beliebigen Transformation mithilfe der Astx
-API. Es wird mit einem Objekt mit den folgenden Eigenschaften aufgerufen:
file
( string
) – Der Pfad zur Datei, die transformiert wirdsource
( string
) – Der Quellcode der Datei, die transformiert wirdastx
( Astx
) – die Astx
-API-Instanzt
( AstTypes
) – ast-types
-Definitionen für den ausgewählten Parserexpression
– getaggtes Vorlagenliteral zum Parsen von Code als Ausdruckstatement
– getaggtes Vorlagenliteral zum Parsen von Code als Anweisungstatements
– getaggtes Vorlagenliteral zum Parsen von Code als Array von Anweisungenreport
( (message: unknown) => void
)mark
( (...matches: Astx[]) => void
) – markiert die angegebenen Übereinstimmungen, die in der Übereinstimmungsliste von vscode-astx usw. angezeigt werden sollen Im Gegensatz zu jscodeshift
kann Ihre Transformationsfunktion asynchron sein und muss nicht den transformierten Code zurückgeben, Sie können jedoch einen string
zurückgeben. Sie können auch null
zurückgeben, um die Datei zu überspringen.
exports.onReport
(optional) Wenn Sie report(x)
von einer exports.astx
-Funktion aufrufen, wird diese mit onReport({ file, report: x })
aufgerufen.
Wenn Sie mehrere Arbeitsthreads verwenden, wird onReport
im übergeordneten Prozess aufgerufen, daher muss die Berichtsnachricht ein serialisierbarer Wert sein. Dies ermöglicht es einer Transformation, Berichte von allen Arbeitern zu sammeln (und dann möglicherweise im finish
etwas mit ihnen zu tun).
Wenn onReport
ein Promise
zurückgibt, wird darauf gewartet.
exports.finish
(optional)Dies wird aufgerufen, nachdem die Transformation für alle Eingabedateien ausgeführt wurde.
Wenn Sie mehrere Arbeitsthreads verwenden, wird finish
im übergeordneten Prozess aufgerufen. Sie können onReport
und finish
zusammen verwenden, um Informationen aus jeder Eingabedatei zu sammeln und am Ende eine Art kombinierte Ausgabe zu erstellen.
Wenn finish
ein Promise
zurückgibt, wird darauf gewartet.
astx
unterstützt die Konfiguration an den folgenden Stellen (über cosmiconfig
):
astx
Eigenschaft in package.json.astxrc
Datei im JSON- oder YAML-Format.astxrc.json
, .astxrc.yaml
, .astxrc.yml
, .astxrc.js
oder .astxrc.cjs
Dateiastx.config.js
oder astx.config.cjs
CommonJS-Modul, das ein Objekt exportiertWenn Ihre Codebasis mit Prettier formatiert ist, empfehle ich, zuerst Folgendes zu versuchen:
{
"parser" : " babel/auto " ,
"parserOptions" : {
"preserveFormat" : " generatorHack "
}
}
(oder als CLI-Optionen)
--parser babel/auto --parserOptions '{"preserveFormat": "generatorHack"}'
Wenn dies fehlschlägt, können Sie parser: 'recast/babel/auto'
oder die Nicht- /auto
-Parser ausprobieren.
Ihr Kilometerstand kann je nach recast
variieren; Sie sind einfach nicht in der Lage, es schnell genug mit neuen Syntaxfunktionen in JS und TS auf dem neuesten Stand zu halten, und ich habe gesehen, dass es zu oft ungültige Syntax ausgab.
Von nun an werde ich an einer zuverlässigen Lösung arbeiten, indem ich @babel/generator
oder prettier
verwende, um den geänderten AST zu drucken, mit einem Haken, um die ursprüngliche Quelle wörtlich für unveränderte Knoten zu verwenden.
parser
Der zu verwendende Parser. Optionen:
babel/auto
(Standard)babel
(schneller als babel/auto
, verwendet aber stattdessen Standard-Parse-Optionen; möglicherweise müssen Sie parserOptions
konfigurieren)recast/babel
recast/babel/auto
babel/auto
bestimmt automatisch Parse-Optionen aus Ihrer Babel-Konfiguration, falls vorhanden. babel
verwendet stattdessen feste Parse-Optionen, ist also schneller als babel/auto
, aber Sie müssen möglicherweise parserOptions
konfigurieren. Die Optionen recast/babel(/auto)
verwenden recast
um die Formatierung beizubehalten. Ich habe festgestellt, dass die Syntax recast
bei einigen Dateien ungültig ist. Verwenden Sie sie daher mit Vorsicht.
parserOptions
Optionen zur Übergabe an den Parser. Im Moment sind dies nur die @babel/parser
-Optionen plus die folgenden zusätzlichen Optionen:
preserveFormat
(gilt für: babel
, babel/auto
) preserveFormat: 'generatorHack'
verwendet einen experimentellen Hack, um das Format aller unveränderten Knoten beizubehalten, indem die interne @babel/generator
-API gekapert wird.prettier
Wenn false
, versuchen Sie nicht, den transformierten Quellcode mithilfe von prettier
neu zu formatieren. Der Standardwert ist true
.
Astx enthält eine CLI zum Durchführen von Transformationen. Die CLI verarbeitet die angegebenen Dateien, druckt dann ein Diff der Änderungen aus und fordert Sie auf, zu bestätigen, dass Sie die Änderungen schreiben möchten.
Die Analyse erfolgt standardmäßig mit Babel unter Verwendung der in Ihrem Projekt installierten Version und der Babel-Konfiguration Ihres Projekts, falls vorhanden. Sie können --parser recast/babel
übergeben, wenn Sie recast
verwenden möchten, um zu versuchen, die Formatierung in der Ausgabe beizubehalten, aber ich sehe manchmal Syntaxfehler in der Ausgabe.
Im Gegensatz zu jscodeshift
formatiert es den transformierten Code mit prettier
, wenn prettier
in Ihrem Projekt installiert ist.
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]