SlideShare a Scribd company logo
Crossfile Codemodding
with Joshua Lawrence
Joshua Lawrence
Sr. Software Engineer @LinkedIn
Developer Experience Team


Centralized Release Tool “CRT” - Internal CI/CD UI


Working with Ember for six years


Codemod n00b
Native


Classes
Engines
Angle


Brackets
Qunit
ES6


Modules
PODs
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
CHECKING
TYPE
ARGUMENT
Life Without Component Argument Type Checking
// ember-mascot usage


<EmberMascot @name={{this.name}} @likes={{this.likes}} />
Are there any other args
that I can/should provide?
Is this a number? Boolean?
An array of strings?
Probably a string
// ember-mascot.hbs


<p>{{this.name}}</p>


<p>{{this.likeCount}}</p>


<ul>


{{#each this.likes as |like|}}


<MascotLike @like={{like}} />


{{/each}}


</ul>
Life Without Component Argument Type Checking
Is this an argument with a default value


or a computed prop?
What is this an array of?
JSDoc Basic information for devs, but became
inaccurate over time
Verbose and lacks some features of argument
decorators
Uses modern patterns, is powerful and flexible,
doesn’t distract from component logic
ember-prop-types
ember-decorators/argument
Type Checking via “ember-decorators/argument”
// ember-mascot.hbs


<p>{{this.name}}</p>


<p>{{this.likeCount}}</p>


<ul>


{{#each this.likes as |like|}}


<MascotLike @like={{like}} />


{{/each}}


</ul>
// ember-mascot.js


export default class EmberMascot extends Component {


@argument(‘string’)


name = ‘’;


@argument(‘number’)


likeCount = 0;


@argument(shapeOf({


user: ‘string’,


date: Date


}))


likes;


}
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
Ditch type checking?


Brute force update?


Regex find and replace?


Stay on Ember 3.12 forever?
?
Ember.js Skill
GitHub Followers
Commits
Robert Jackson
@rwjblue
Chris Garrett
@pzuraq
Disclaimer: These stats are completely made up, but these guys are legit
ember-
argument-types
github.com/pzuraq/ember-
argument-types
// ember-mascot.hbs


{{arg-type @name (optional “string”)}}


{{arg-type @likeCount (optional “number”)}}


{{arg-type @likes (array-of


(shape-of


user=“string”


date=“date”


))


}}


<p>{{this.name}}</p>


<p>{{this.likeCount}}</p>


<ul>


{{#each this.likes as |like|}}


<MascotLike @like={{like}} />


{{/each}}


</ul>
ember-codemods
github.com/ember-codemods
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
jscodeshift
github.com/facebook/
jscodeshift
jscodeshift is a toolkit for running
codemods over multiple JavaScript or
TypeScript files.
…bring your own docs.
Input


source code
Mutate AST
Parse into


an AST
Convert to
source


and save
Lifecycle of a codemod
HTML DOM
JS AST
Can you codemod from


one file to another?
JS JS
HBS
codemod-cli project
Three transforms are better than one
Map
Decorators


to


JSON
Use JSON


to build


Template
Helpers
Remove


Argument


Decorators
JSON HBS JS
// argument-decorators.input.js


class Foo extends Component {


@argument(‘string’)


stringArg;


@argument(‘number’)


numberArg;


}
Transform 1 - Input/Output
// argument-decorators.map.json


{


“argument-decorators.input”: {


“path”: “path/to/file”,


“args”: {


“stringArg”: {


“type”: “StringLiteral”,


“value”: “string”


},


“numberArg”: {


“type”: “StringLiteral”,


“value”: “number”


},


}


}


}
// argument-types.input.hbs


<p>{{@stringArg}}</p>


<p>{{@numberArg}}</p>
Transform 2 - Input/Output
// argument-types.output.js


{{arg-type @stringArg “string”}}


{{arg-type @numberArg “number”}}


<p>{{@stringArg}}</p>


<p>{{@numberArg}}</p>
// argument-decorators.input.js


class Foo extends Component {


// This is a string arg


@argument(‘string’)


stringArg;


// This is a number arg


@argument(‘number’)


numberArg;


}
Transform 3 - Input/Output
// argument-decorators.output.js


class Foo extends Component {


// This is a string arg


stringArg;


// This is a number arg


numberArg;


}
Convert
Decorators


to


JSON
Use JSON
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
> codemod-cli new <project-name>


> codemod-cli generate codemod <codemod-name>


> codemod-cli generate fixture <codemod-name> <fixture-name>
// codemod-cli project transform


input.find(j.ClassBody).forEach((classBody) => {


classBody.value.body.filter((classProp) => {});B


});A


Transform 1: Decorators to JSON
// AST Explorer: Input


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// AST Explorer: Parsed AST


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// codemod-cli project transform


input.find(j.ClassBody).forEach((classBody) => {


classBody.value.body.filter((classProp) => {});B


});A


Transform 1: Decorators to JSON
// AST Explorer: Input


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// AST Explorer: Parsed AST


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// codemod-cli project transform


input.find(j.ClassBody).forEach((classBody) => {


classBody.value.body.filter((classProp) => {});B


});A


Transform 1: Decorators to JSON
// AST Explorer: Input


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// AST Explorer: Parsed AST


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// codemod-cli project transform


input.find(j.ClassBody).forEach((classBody) => {


classBody.value.body.filter((classProp) => {});B


});A


Transform 1: Decorators to JSON
// AST Explorer: Input


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// AST Explorer: Parsed AST


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// codemod-cli project transform


input.find(j.ClassBody).forEach((classBody) => {


classBody.value.body.filter((classProp) => {});B


});A


Transform 1: Decorators to JSON
// AST Explorer: Input


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// AST Explorer: Parsed AST


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// codemod-cli project transform


input.find(j.ClassBody).forEach((classBody) => {


classBody.value.body.filter((classProp) => {


return classProp.decorators.some((decorator) => {});C


});B


});A


Transform 1: Decorators to JSON
// AST Explorer: Input


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// AST Explorer: Parsed AST


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// codemod-cli project transform


input.find(j.ClassBody).forEach((classBody) => {


classBody.value.body.filter((classProp) => {


return classProp.decorators.some((decorator) => {


return (


decorator.expression.type === 'CallExpression' &&


(decorator.expression.callee.name === 'type' ||


decorator.expression.callee.name === 'argument')


);


});C


});B


});A


Transform 1: Decorators to JSON
// AST Explorer: Input


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// AST Explorer: Parsed AST


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// codemod-cli project transform


input.find(j.ClassBody).forEach((classBody) => {


classBody.value.body.filter((classProp) => {


return classProp.decorators.some((decorator) => {


return (


decorator.expression.type === 'CallExpression' &&


(decorator.expression.callee.name === 'type' ||


decorator.expression.callee.name === 'argument')


);


});C


}).forEach(parseDecoratorArgs);B


});A


Transform 1: Decorators to JSON
// AST Explorer: Input


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// AST Explorer: Parsed AST


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// codemod-cli project transform


input.find(j.ClassBody).forEach((classBody) => {


classBody.value.body.filter((classProp) => {


return classProp.decorators.some((decorator) => {


return (


decorator.expression.type === 'CallExpression' &&


(decorator.expression.callee.name === 'type' ||


decorator.expression.callee.name === 'argument')


);


});C


}).forEach(parseDecoratorArgs);B


});A


fs.writeFileSync(


‘map.json’,


JSON.stringify(parsedDecorators, null, 2),


{ encoding: ‘utf8' }


);


Transform 1: Decorators to JSON
// AST Explorer: Input


class Foo extends Component {


@argument(‘string’)


stringArg;


}
// AST Explorer: Parsed AST


class Foo extends Component {


@argument(‘string’)


stringArg;


}
Multiple codemod passes with increasing scope
Test Fixtures
One Component
Directory with a
few components
All components
Convert
Decorators


to build


Template
Helpers
Remove
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence
Argument


Decorators
JSON HBS JS
import { argument } from '@ember-
decorators/argument';


class Foo extends Component {


// This is a string arg


@someUnrelatedDecorator


@argument(‘string’)


stringArg;


}
Remove Argument Decorators
class Foo extends Component {


// This is a string arg


@someUnrelatedDecorator


stringArg;


}
Putting them all together
Map
Decorators


to


JSON
Use JSON


to build


Template
Helpers
Remove


Argument


Decorators
JSON HBS JS
github.com/ember-decorators/
argument-codemod
ember-decorators/
argument-codemod
> npx @ember-decorators/argument-codemod decorators-to-json ./app/components


> npx @ember-decorators/argument-codemod json-to-template ./app/templates/components


> npx @ember-decorators/argument-codemod decorators-cleanup ./app/components
Almost there…
Ember 3.20 Octane
Takeaways
Your code(mod)
doesn’t need to
be perfect
Sometimes you
have to be the
one to fix it
Don’t stop


at one
Opportunities to
improve tooling
THANK YOU!
Resources


https://github.com/ember-decorators/argument-codemod


https://github.com/pzuraq/ember-argument-types


https://github.com/rwjblue/codemod-cli


https://github.com/facebook/jscodeshift


https://github.com/ember-codemods
jwlawrence joshlawrence jlaw

More Related Content

PPT
Ruby For Java Programmers
PPT
Groovy presentation
PPT
Javascript
PDF
Scala parsers Error Recovery in Production
ODP
What I Love About Ruby
PDF
Clean coding-practices
PPTX
The Sincerest Form of Flattery
PPTX
Kotlin as a Better Java
Ruby For Java Programmers
Groovy presentation
Javascript
Scala parsers Error Recovery in Production
What I Love About Ruby
Clean coding-practices
The Sincerest Form of Flattery
Kotlin as a Better Java

What's hot (19)

PDF
Java Full Throttle
PDF
Denis Lebedev, Swift
PPT
X Path
PDF
Code generating beans in Java
PDF
The Swift Compiler and Standard Library
PDF
Scala + WattzOn, sitting in a tree....
PDF
The Sincerest Form of Flattery
PDF
Java 8, Streams & Collectors, patterns, performances and parallelization
PDF
Swift Introduction
PDF
Getting rid of backtracking
PDF
CS4200 2019 | Lecture 3 | Parsing
PPTX
10 Sets of Best Practices for Java 8
PDF
Cocoa Design Patterns in Swift
PDF
Java 8 ​and ​Best Practices
PPT
Linq intro
PPTX
Clean code slide
PDF
Free your lambdas
PDF
Zend Certification Preparation Tutorial
Java Full Throttle
Denis Lebedev, Swift
X Path
Code generating beans in Java
The Swift Compiler and Standard Library
Scala + WattzOn, sitting in a tree....
The Sincerest Form of Flattery
Java 8, Streams & Collectors, patterns, performances and parallelization
Swift Introduction
Getting rid of backtracking
CS4200 2019 | Lecture 3 | Parsing
10 Sets of Best Practices for Java 8
Cocoa Design Patterns in Swift
Java 8 ​and ​Best Practices
Linq intro
Clean code slide
Free your lambdas
Zend Certification Preparation Tutorial
Ad

Recently uploaded (20)

PDF
KodekX | Application Modernization Development
PPTX
Cloud computing and distributed systems.
PPTX
Spectroscopy.pptx food analysis technology
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PDF
Reach Out and Touch Someone: Haptics and Empathic Computing
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PDF
Diabetes mellitus diagnosis method based random forest with bat algorithm
PDF
Mobile App Security Testing_ A Comprehensive Guide.pdf
PDF
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
PDF
Building Integrated photovoltaic BIPV_UPV.pdf
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
DOCX
The AUB Centre for AI in Media Proposal.docx
PPTX
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
Approach and Philosophy of On baking technology
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
KodekX | Application Modernization Development
Cloud computing and distributed systems.
Spectroscopy.pptx food analysis technology
Advanced methodologies resolving dimensionality complications for autism neur...
Reach Out and Touch Someone: Haptics and Empathic Computing
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
Understanding_Digital_Forensics_Presentation.pptx
Diabetes mellitus diagnosis method based random forest with bat algorithm
Mobile App Security Testing_ A Comprehensive Guide.pdf
Profit Center Accounting in SAP S/4HANA, S4F28 Col11
Building Integrated photovoltaic BIPV_UPV.pdf
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
The AUB Centre for AI in Media Proposal.docx
KOM of Painting work and Equipment Insulation REV00 update 25-dec.pptx
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Approach and Philosophy of On baking technology
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Ad

EmberConf 2021 - Crossfile Codemodding with Joshua Lawrence

  • 2. Joshua Lawrence Sr. Software Engineer @LinkedIn Developer Experience Team Centralized Release Tool “CRT” - Internal CI/CD UI Working with Ember for six years Codemod n00b
  • 6. Life Without Component Argument Type Checking // ember-mascot usage <EmberMascot @name={{this.name}} @likes={{this.likes}} /> Are there any other args that I can/should provide? Is this a number? Boolean? An array of strings? Probably a string
  • 7. // ember-mascot.hbs <p>{{this.name}}</p> <p>{{this.likeCount}}</p> <ul> {{#each this.likes as |like|}} <MascotLike @like={{like}} /> {{/each}} </ul> Life Without Component Argument Type Checking Is this an argument with a default value or a computed prop? What is this an array of?
  • 8. JSDoc Basic information for devs, but became inaccurate over time Verbose and lacks some features of argument decorators Uses modern patterns, is powerful and flexible, doesn’t distract from component logic ember-prop-types ember-decorators/argument
  • 9. Type Checking via “ember-decorators/argument” // ember-mascot.hbs <p>{{this.name}}</p> <p>{{this.likeCount}}</p> <ul> {{#each this.likes as |like|}} <MascotLike @like={{like}} /> {{/each}} </ul> // ember-mascot.js export default class EmberMascot extends Component { @argument(‘string’) name = ‘’; @argument(‘number’) likeCount = 0; @argument(shapeOf({ user: ‘string’, date: Date })) likes; }
  • 11. Ditch type checking? Brute force update? Regex find and replace? Stay on Ember 3.12 forever? ?
  • 12. Ember.js Skill GitHub Followers Commits Robert Jackson @rwjblue Chris Garrett @pzuraq Disclaimer: These stats are completely made up, but these guys are legit
  • 13. ember- argument-types github.com/pzuraq/ember- argument-types // ember-mascot.hbs {{arg-type @name (optional “string”)}} {{arg-type @likeCount (optional “number”)}} {{arg-type @likes (array-of (shape-of user=“string” date=“date” )) }} <p>{{this.name}}</p> <p>{{this.likeCount}}</p> <ul> {{#each this.likes as |like|}} <MascotLike @like={{like}} /> {{/each}} </ul>
  • 16. jscodeshift github.com/facebook/ jscodeshift jscodeshift is a toolkit for running codemods over multiple JavaScript or TypeScript files. …bring your own docs.
  • 17. Input source code Mutate AST Parse into an AST Convert to source and save Lifecycle of a codemod
  • 19. Can you codemod from one file to another? JS JS HBS
  • 20. codemod-cli project Three transforms are better than one Map Decorators to JSON Use JSON to build Template Helpers Remove Argument Decorators JSON HBS JS
  • 21. // argument-decorators.input.js class Foo extends Component { @argument(‘string’) stringArg; @argument(‘number’) numberArg; } Transform 1 - Input/Output // argument-decorators.map.json { “argument-decorators.input”: { “path”: “path/to/file”, “args”: { “stringArg”: { “type”: “StringLiteral”, “value”: “string” }, “numberArg”: { “type”: “StringLiteral”, “value”: “number” }, } } }
  • 22. // argument-types.input.hbs <p>{{@stringArg}}</p> <p>{{@numberArg}}</p> Transform 2 - Input/Output // argument-types.output.js {{arg-type @stringArg “string”}} {{arg-type @numberArg “number”}} <p>{{@stringArg}}</p> <p>{{@numberArg}}</p>
  • 23. // argument-decorators.input.js class Foo extends Component { // This is a string arg @argument(‘string’) stringArg; // This is a number arg @argument(‘number’) numberArg; } Transform 3 - Input/Output // argument-decorators.output.js class Foo extends Component { // This is a string arg stringArg; // This is a number arg numberArg; }
  • 31. > codemod-cli new <project-name> > codemod-cli generate codemod <codemod-name> > codemod-cli generate fixture <codemod-name> <fixture-name>
  • 32. // codemod-cli project transform input.find(j.ClassBody).forEach((classBody) => { classBody.value.body.filter((classProp) => {});B });A Transform 1: Decorators to JSON // AST Explorer: Input class Foo extends Component { @argument(‘string’) stringArg; } // AST Explorer: Parsed AST class Foo extends Component { @argument(‘string’) stringArg; }
  • 33. // codemod-cli project transform input.find(j.ClassBody).forEach((classBody) => { classBody.value.body.filter((classProp) => {});B });A Transform 1: Decorators to JSON // AST Explorer: Input class Foo extends Component { @argument(‘string’) stringArg; } // AST Explorer: Parsed AST class Foo extends Component { @argument(‘string’) stringArg; }
  • 34. // codemod-cli project transform input.find(j.ClassBody).forEach((classBody) => { classBody.value.body.filter((classProp) => {});B });A Transform 1: Decorators to JSON // AST Explorer: Input class Foo extends Component { @argument(‘string’) stringArg; } // AST Explorer: Parsed AST class Foo extends Component { @argument(‘string’) stringArg; }
  • 35. // codemod-cli project transform input.find(j.ClassBody).forEach((classBody) => { classBody.value.body.filter((classProp) => {});B });A Transform 1: Decorators to JSON // AST Explorer: Input class Foo extends Component { @argument(‘string’) stringArg; } // AST Explorer: Parsed AST class Foo extends Component { @argument(‘string’) stringArg; }
  • 36. // codemod-cli project transform input.find(j.ClassBody).forEach((classBody) => { classBody.value.body.filter((classProp) => {});B });A Transform 1: Decorators to JSON // AST Explorer: Input class Foo extends Component { @argument(‘string’) stringArg; } // AST Explorer: Parsed AST class Foo extends Component { @argument(‘string’) stringArg; }
  • 37. // codemod-cli project transform input.find(j.ClassBody).forEach((classBody) => { classBody.value.body.filter((classProp) => { return classProp.decorators.some((decorator) => {});C });B });A Transform 1: Decorators to JSON // AST Explorer: Input class Foo extends Component { @argument(‘string’) stringArg; } // AST Explorer: Parsed AST class Foo extends Component { @argument(‘string’) stringArg; }
  • 38. // codemod-cli project transform input.find(j.ClassBody).forEach((classBody) => { classBody.value.body.filter((classProp) => { return classProp.decorators.some((decorator) => { return ( decorator.expression.type === 'CallExpression' && (decorator.expression.callee.name === 'type' || decorator.expression.callee.name === 'argument') ); });C });B });A Transform 1: Decorators to JSON // AST Explorer: Input class Foo extends Component { @argument(‘string’) stringArg; } // AST Explorer: Parsed AST class Foo extends Component { @argument(‘string’) stringArg; }
  • 39. // codemod-cli project transform input.find(j.ClassBody).forEach((classBody) => { classBody.value.body.filter((classProp) => { return classProp.decorators.some((decorator) => { return ( decorator.expression.type === 'CallExpression' && (decorator.expression.callee.name === 'type' || decorator.expression.callee.name === 'argument') ); });C }).forEach(parseDecoratorArgs);B });A Transform 1: Decorators to JSON // AST Explorer: Input class Foo extends Component { @argument(‘string’) stringArg; } // AST Explorer: Parsed AST class Foo extends Component { @argument(‘string’) stringArg; }
  • 40. // codemod-cli project transform input.find(j.ClassBody).forEach((classBody) => { classBody.value.body.filter((classProp) => { return classProp.decorators.some((decorator) => { return ( decorator.expression.type === 'CallExpression' && (decorator.expression.callee.name === 'type' || decorator.expression.callee.name === 'argument') ); });C }).forEach(parseDecoratorArgs);B });A fs.writeFileSync( ‘map.json’, JSON.stringify(parsedDecorators, null, 2), { encoding: ‘utf8' } ); Transform 1: Decorators to JSON // AST Explorer: Input class Foo extends Component { @argument(‘string’) stringArg; } // AST Explorer: Parsed AST class Foo extends Component { @argument(‘string’) stringArg; }
  • 41. Multiple codemod passes with increasing scope Test Fixtures One Component Directory with a few components All components
  • 53. import { argument } from '@ember- decorators/argument'; class Foo extends Component { // This is a string arg @someUnrelatedDecorator @argument(‘string’) stringArg; } Remove Argument Decorators class Foo extends Component { // This is a string arg @someUnrelatedDecorator stringArg; }
  • 54. Putting them all together Map Decorators to JSON Use JSON to build Template Helpers Remove Argument Decorators JSON HBS JS
  • 55. github.com/ember-decorators/ argument-codemod ember-decorators/ argument-codemod > npx @ember-decorators/argument-codemod decorators-to-json ./app/components > npx @ember-decorators/argument-codemod json-to-template ./app/templates/components > npx @ember-decorators/argument-codemod decorators-cleanup ./app/components
  • 58. Takeaways Your code(mod) doesn’t need to be perfect Sometimes you have to be the one to fix it Don’t stop at one Opportunities to improve tooling