
O Dyfactor é a plataforma para escrever e executar codemodos guiados por perfil. Ao combinar informações de tempo de execução e análise estática, os aplicativos são capazes de migrar bases de código para novas APIs em um ritmo muito mais rápido.
Veja Dyfactor em ação
$ yarn global add dyfactor$ yarn dyfactor --help
Usage: dyfactor [options] [command]
Options:
-h, --help output usage information
Commands:
init Initializes dyfactor in your project
run Runs a plugin against code
list-plugins Lists the available plugins
help [cmd] display help for [cmd]Ferramentas como JScodeshift e Babel são excelentes ferramentas para migrar o código de uma API para outra, devido ao fato de que eles dependem da análise estática. No entanto, é completamente possível que partes de um aplicativo confiem nas informações de tempo de execução para determinar a execução. Em outros casos, você pode ter DSLs personalizados, como idiomas de modelo que não têm a mesma análise estática que o JavaScript. Migrar esse tipo de código normalmente requer uma grande intervenção do desenvolvedor para resolver o que está realmente acontecendo. O objetivo do Dyfactor é reduzir essa fenda de entendimento.
Nos desenvolvedores de camadas de modelo da Ember, podem invocar componentes com argumentos. Eles também podem olhar para os valores locais na classe de apoio. Um modelo para um componente pode parecer o seguinte.
Embora esse modelo seja declarativo, não é óbvio se alguma das MustacheExpressions (cachos) foram argumentos ao componente ou se são valores locais na classe de componentes. A única maneira de saber é olhar para a invocação do componente. Ao fazer isso, descobrimos que firstName e lastName são transmitidos como argumentos.
O núcleo do Ember reconheceu que isso é extremamente problemático à medida que um aplicativo cresce, portanto, eles permitiram que os argumentos fossem pré-fixados com @ e moradores locais prefixados com this. . A questão é que a migração de todos os modelos em um projeto levaria muito tempo porque exige que os desenvolvedores separem essas coisas.
É aqui que entra o Dyfactor. Ao escrever um plug -in dinâmico, podemos instrumentar o aplicativo de tal maneira que nos permite saber como esses símbolos estão sendo resolvidos em tempo de execução. A partir daí, podemos usar essas informações para migrar manualmente o código ou deixar o Dyfactor tentar fazer a migração para nós.
Em um nível alto, o Dyfactor é um sistema de corredor e plug -in. Atualmente, ele suporta dois tipos de plugins: plugins estáticos e plugins dinâmicos.
Um plug -in estático deve ser usado para realizar codemodos que exigem apenas uma análise estática e são apenas em fases únicas. Eles devem ser considerados como um invólucro leve em torno de um codemod que você escreveria com o JScodeshift ou Babel.
interface StaticPlugin {
/**
* An array containing all recursive files and directories
* under a given directory
**/
inputs : string [ ] ;
/**
* Hook called by the runner where codemod is implimented
*/
modify ( ) : void ;
} import { StaticPlugin } from 'dyfactor' ;
import * as fs from 'fs' ;
import * as recast from 'recast' ;
function filesOnly ( path ) {
return path . charAt ( path . length - 1 ) !== '/' ;
}
export default class extends StaticPlugin {
modify ( ) {
this . inputs . filter ( filesOnly ) . forEach ( input => {
let content = fs . readFileSync ( input , 'utf8' ) ;
let ast = recast . parse ( content ) ;
let add = ast . program . body [ 0 ] ;
let { builders : b } = recast . types ;
ast . program . body [ 0 ] = b . variableDeclaration ( 'var' , [
b . variableDeclarator (
add . id ,
b . functionExpression (
null , // Anonymize the function expression.
add . params ,
add . body
)
)
] ) ;
add . params . push ( add . params . shift ( ) ) ;
let output = recast . prettyPrint ( ast , { tabWidth : 2 } ) . code ;
fs . writeFileSync ( input , output ) ;
} ) ;
}
} Um plugin dinâmico é duas fases. A primeira fase permite que você instrumente um aplicativo com segurança para coletar os dados de telemetria de tempo de execução. A aplicação instrumentada é então inicializada com marionetistas para coletar os dados. A segunda fase é responsável por introspectar os dados de telemetria de tempo de execução e aplicar os modelos de codos com base nesses dados. É importante observar que os plugins dinâmicos podem ser executados como plugins em fases únicos apenas para produzir a telemetria de tempo de execução e escrevê -lo no disco.
interface DynamicPlugin {
/**
* An array containing all recursive files and directories
* under a given directory
**/
inputs : string [ ] ;
/**
* Hook called by the runner to instrument the application
*/
instrument ( ) : void ;
/**
* Hook called by the runner to apply codemods based on the telemtry
*/
modify ( telemetry : Telemetry ) : void ;
}
interface Telemetry {
data : any ;
} import { DynamicPlugin , TelemetryBuilder } from 'dyfactor' ;
import * as fs from 'fs' ;
import { preprocess , print } from '@glimmer/syntax' ;
import { transform } from 'babel-core' ;
function filesOnly ( path ) {
return path . charAt ( path . length - 1 ) !== '/' ;
}
function instrumentCreate ( babel ) {
const { types : t } = babel ;
let ident ;
let t = new TelemetryBuilder ( ) ;
let template = babel . template ( `
IDENT.reopenClass({
create(injections) {
let instance = this._super(injections);
${ t . preamble ( ) }
${ t . conditionallyAdd (
'instance._debugContainerKey' ,
( ) => {
return `Object.keys(injections.attrs).forEach((arg) => {
if (! ${ t . path ( 'instance._debugContainerKey' ) } .contains(arg)) {
${ t . path ( 'instance._debugContainerKey' ) } .push(arg);
}
});` ;
} ,
( ) => {
return ` ${ t . path ( 'instance._debugContainerKey' ) } = Object.keys(injections.attrs);` ;
}
) }
return instance;
}
});
` ) ;
return {
name : 'instrument-create' ,
visitor : {
Program : {
enter ( p ) {
ident = p . scope . generateUidIdentifier ( 'refactor' ) ;
} ,
exit ( p ) {
let body = p . node . body ;
let ast = template ( { IDENT : ident } ) ;
body . push ( ast , t . exportDefaultDeclaration ( ident ) ) ;
}
} ,
ExportDefaultDeclaration ( p ) {
let declaration = p . node . declaration ;
let declarator = t . variableDeclarator ( ident , declaration ) ;
let varDecl = t . variableDeclaration ( 'const' , [ declarator ] ) ;
p . replaceWith ( varDecl ) ;
}
}
} ;
}
export default class extends DynamicPlugin {
instrument ( ) {
this . inputs . filter ( filesOnly ) . forEach ( input => {
let code = fs . readFileSync ( input , 'utf8' ) ;
let content = transform ( code , {
plugins : [ instrumentCreate ]
} ) ;
fs . writeFileSync ( input , content . code ) ;
} ) ;
}
modify ( telemetry ) {
telemetry . data . forEach ( components => {
Object . keys ( components ) . forEach ( component => {
let templatePath = this . templateFor ( component ) ;
let template = fs . readFileSync ( templatePath , 'utf8' ) ;
let ast = preprocess ( template , {
plugins : {
ast : [ toArgs ( components [ component ] ) ]
}
} ) ;
fs . writeFileSync ( templatePath , print ( ast ) ) ;
} ) ;
} ) ;
}
templateFor ( fullName : string ) {
let [ , name ] = fullName . split ( ':' ) ;
return this . inputs . find ( input => input . includes ( `templates/components/ ${ name } ` ) ) ;
}
}