├── test ├── fixtures │ ├── with-telemetry │ │ ├── input │ │ │ ├── app │ │ │ │ ├── styles │ │ │ │ │ └── app.css │ │ │ │ ├── components │ │ │ │ │ ├── .gitkeep │ │ │ │ │ └── foo-bar.js │ │ │ │ ├── helpers │ │ │ │ │ ├── .gitkeep │ │ │ │ │ ├── -wat-wat.js │ │ │ │ │ ├── utils │ │ │ │ │ │ ├── bee-bop.js │ │ │ │ │ │ └── -wat-wat.js │ │ │ │ │ └── biz-baz.js │ │ │ │ ├── models │ │ │ │ │ └── .gitkeep │ │ │ │ ├── routes │ │ │ │ │ └── .gitkeep │ │ │ │ ├── controllers │ │ │ │ │ ├── .gitkeep │ │ │ │ │ └── application.js │ │ │ │ ├── templates │ │ │ │ │ ├── components │ │ │ │ │ │ ├── file-less.hbs │ │ │ │ │ │ └── foo-bar.hbs │ │ │ │ │ └── application.hbs │ │ │ │ ├── router.js │ │ │ │ ├── app.js │ │ │ │ └── index.html │ │ │ ├── vendor │ │ │ │ └── .gitkeep │ │ │ ├── tests │ │ │ │ ├── helpers │ │ │ │ │ └── .gitkeep │ │ │ │ ├── unit │ │ │ │ │ └── .gitkeep │ │ │ │ ├── integration │ │ │ │ │ ├── .gitkeep │ │ │ │ │ ├── helpers │ │ │ │ │ │ └── biz-baz-test.js │ │ │ │ │ └── components │ │ │ │ │ │ └── foo-bar-test.js │ │ │ │ ├── test-helper.js │ │ │ │ └── index.html │ │ │ ├── .watchmanconfig │ │ │ ├── public │ │ │ │ └── robots.txt │ │ │ ├── .prettierrc.js │ │ │ ├── .template-lintrc.js │ │ │ ├── config │ │ │ │ ├── optional-features.json │ │ │ │ ├── ember-cli-update.json │ │ │ │ ├── targets.js │ │ │ │ └── environment.js │ │ │ ├── .eslintignore │ │ │ ├── .ember-cli │ │ │ ├── .prettierignore │ │ │ ├── .editorconfig │ │ │ ├── .gitignore │ │ │ ├── .travis.yml │ │ │ ├── testem.js │ │ │ ├── ember-cli-build.js │ │ │ ├── .eslintrc.js │ │ │ ├── README.md │ │ │ └── package.json │ │ └── output │ │ │ ├── vendor │ │ │ └── .gitkeep │ │ │ ├── app │ │ │ ├── helpers │ │ │ │ ├── .gitkeep │ │ │ │ ├── -wat-wat.js │ │ │ │ ├── utils │ │ │ │ │ ├── -wat-wat.js │ │ │ │ │ └── bee-bop.js │ │ │ │ └── biz-baz.js │ │ │ ├── models │ │ │ │ └── .gitkeep │ │ │ ├── routes │ │ │ │ └── .gitkeep │ │ │ ├── styles │ │ │ │ └── app.css │ │ │ ├── components │ │ │ │ ├── .gitkeep │ │ │ │ └── foo-bar.js │ │ │ ├── controllers │ │ │ │ ├── .gitkeep │ │ │ │ └── application.js │ │ │ ├── templates │ │ │ │ ├── components │ │ │ │ │ ├── file-less.hbs │ │ │ │ │ └── foo-bar.hbs │ │ │ │ └── application.hbs │ │ │ ├── router.js │ │ │ ├── app.js │ │ │ └── index.html │ │ │ ├── tests │ │ │ ├── helpers │ │ │ │ └── .gitkeep │ │ │ ├── unit │ │ │ │ └── .gitkeep │ │ │ ├── integration │ │ │ │ └── .gitkeep │ │ │ ├── test-helper.js │ │ │ └── index.html │ │ │ ├── public │ │ │ └── robots.txt │ │ │ ├── config │ │ │ ├── targets.js │ │ │ └── environment.js │ │ │ ├── testem.js │ │ │ ├── ember-cli-build.js │ │ │ ├── README.md │ │ │ └── package.json │ └── without-telemetry │ │ ├── input │ │ ├── vendor │ │ │ └── .gitkeep │ │ ├── app │ │ │ ├── helpers │ │ │ │ ├── .gitkeep │ │ │ │ ├── -wat-wat.js │ │ │ │ ├── utils │ │ │ │ │ ├── -wat-wat.js │ │ │ │ │ └── bee-bop.js │ │ │ │ └── biz-baz.js │ │ │ ├── models │ │ │ │ └── .gitkeep │ │ │ ├── routes │ │ │ │ └── .gitkeep │ │ │ ├── styles │ │ │ │ └── app.css │ │ │ ├── components │ │ │ │ ├── .gitkeep │ │ │ │ └── foo-bar.js │ │ │ ├── controllers │ │ │ │ └── .gitkeep │ │ │ ├── resolver.js │ │ │ ├── router.js │ │ │ ├── templates │ │ │ │ └── application.hbs │ │ │ ├── app.js │ │ │ └── index.html │ │ ├── tests │ │ │ ├── unit │ │ │ │ └── .gitkeep │ │ │ ├── helpers │ │ │ │ └── .gitkeep │ │ │ ├── integration │ │ │ │ ├── .gitkeep │ │ │ │ ├── helpers │ │ │ │ │ └── biz-baz-test.js │ │ │ │ └── components │ │ │ │ │ └── foo-bar-test.js │ │ │ ├── test-helper.js │ │ │ └── index.html │ │ ├── .watchmanconfig │ │ ├── config │ │ │ ├── optional-features.json │ │ │ ├── targets.js │ │ │ └── environment.js │ │ ├── public │ │ │ └── robots.txt │ │ ├── .eslintignore │ │ ├── .ember-cli │ │ ├── .travis.yml │ │ ├── .editorconfig │ │ ├── .gitignore │ │ ├── testem.js │ │ ├── .eslintrc.js │ │ ├── ember-cli-build.js │ │ ├── README.md │ │ └── package.json │ │ └── output │ │ ├── app │ │ ├── models │ │ │ └── .gitkeep │ │ ├── routes │ │ │ └── .gitkeep │ │ ├── styles │ │ │ └── app.css │ │ ├── components │ │ │ ├── .gitkeep │ │ │ └── foo-bar.js │ │ ├── controllers │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ ├── .gitkeep │ │ │ ├── -wat-wat.js │ │ │ ├── utils │ │ │ │ ├── -wat-wat.js │ │ │ │ └── bee-bop.js │ │ │ └── biz-baz.js │ │ ├── resolver.js │ │ ├── router.js │ │ ├── templates │ │ │ └── application.hbs │ │ ├── app.js │ │ └── index.html │ │ ├── tests │ │ ├── unit │ │ │ └── .gitkeep │ │ ├── helpers │ │ │ └── .gitkeep │ │ ├── integration │ │ │ └── .gitkeep │ │ ├── test-helper.js │ │ └── index.html │ │ ├── vendor │ │ └── .gitkeep │ │ ├── public │ │ └── robots.txt │ │ ├── config │ │ ├── targets.js │ │ └── environment.js │ │ ├── testem.js │ │ ├── ember-cli-build.js │ │ ├── README.md │ │ └── package.json ├── run-test-without-telemetry.js └── run-test.js ├── .eslintignore ├── .prettierrc.js ├── lib └── logger.js ├── .eslintrc.js ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ ├── publish.yml │ └── plan-release.yml ├── bin └── cli.js ├── .release-plan.json ├── LICENSE ├── .gitignore ├── transforms └── angle-brackets │ ├── telemetry │ ├── invokable.js │ ├── mock-invokables.js │ └── invokable.test.js │ ├── index.js │ ├── known-helpers.js │ ├── transform.js │ └── transform.test.js ├── RELEASE.md ├── package.json ├── README.md └── CHANGELOG.md /test/fixtures/with-telemetry/input/app/styles/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/tests/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/styles/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/tests/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/styles/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/routes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/styles/app.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/tests/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/vendor/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/tests/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/components/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/controllers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/tests/helpers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/__testfixtures__/**/*.js 2 | **/fixtures/** 3 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/tests/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/.watchmanconfig: -------------------------------------------------------------------------------- 1 | { 2 | "ignore_dirs": ["tmp", "dist"] 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/templates/components/file-less.hbs: -------------------------------------------------------------------------------- 1 |
this template has no js
2 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "jquery-integration": true 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/templates/components/file-less.hbs: -------------------------------------------------------------------------------- 1 |
this template has no js
2 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/public/robots.txt: -------------------------------------------------------------------------------- 1 | # http://www.robotstxt.org 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/resolver.js: -------------------------------------------------------------------------------- 1 | import Resolver from 'ember-resolver'; 2 | 3 | export default Resolver; 4 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/.template-lintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | extends: 'recommended', 5 | }; 6 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | singleQuote: true, 5 | trailingComma: 'es5', 6 | printWidth: 100, 7 | }; 8 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/components/foo-bar.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | 3 | export default Component.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/components/foo-bar.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | 3 | export default Component.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/components/foo-bar.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | 3 | export default Component.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/components/foo-bar.js: -------------------------------------------------------------------------------- 1 | import Component from '@ember/component'; 2 | 3 | export default Component.extend({ 4 | }); 5 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/templates/components/foo-bar.hbs: -------------------------------------------------------------------------------- 1 | 4 | {{biz-baz canConvert="no" why="helper" where="local"}} 5 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/templates/components/foo-bar.hbs: -------------------------------------------------------------------------------- 1 | 4 | {{biz-baz canConvert="no" why="helper" where="local"}} 5 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/helpers/-wat-wat.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function watWat() { 4 | return 'wat-wat'; 5 | } 6 | 7 | export default helper(watWat); 8 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/helpers/utils/bee-bop.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function beeBop() { 4 | return 'bee-bop'; 5 | } 6 | 7 | export default helper(beeBop); 8 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/helpers/-wat-wat.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function watWat() { 4 | return 'wat-wat'; 5 | } 6 | 7 | export default helper(watWat); 8 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/helpers/-wat-wat.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function watWat() { 4 | return 'wat-wat'; 5 | } 6 | 7 | export default helper(watWat); 8 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/helpers/-wat-wat.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function watWat() { 4 | return 'wat-wat'; 5 | } 6 | 7 | export default helper(watWat); 8 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/helpers/utils/-wat-wat.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function watWat() { 4 | return 'wat-wat'; 5 | } 6 | 7 | export default helper(watWat); 8 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/helpers/utils/-wat-wat.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function watWat() { 4 | return 'wat-wat'; 5 | } 6 | 7 | export default helper(watWat); 8 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/helpers/utils/bee-bop.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function beeBop() { 4 | return 'bee-bop'; 5 | } 6 | 7 | export default helper(beeBop); 8 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/helpers/utils/-wat-wat.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function watWat() { 4 | return 'wat-wat'; 5 | } 6 | 7 | export default helper(watWat); 8 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/helpers/utils/bee-bop.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function beeBop() { 4 | return 'bee-bop'; 5 | } 6 | 7 | export default helper(beeBop); 8 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/helpers/utils/-wat-wat.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function watWat() { 4 | return 'wat-wat'; 5 | } 6 | 7 | export default helper(watWat); 8 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/helpers/utils/bee-bop.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function beeBop() { 4 | return 'bee-bop'; 5 | } 6 | 7 | export default helper(beeBop); 8 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/helpers/biz-baz.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function bizBaz(params/*, hash*/) { 4 | return params; 5 | } 6 | 7 | export default helper(bizBaz); 8 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/helpers/biz-baz.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function bizBaz(params/*, hash*/) { 4 | return params; 5 | } 6 | 7 | export default helper(bizBaz); 8 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/helpers/biz-baz.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function bizBaz(params/*, hash*/) { 4 | return params; 5 | } 6 | 7 | export default helper(bizBaz); 8 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/helpers/biz-baz.js: -------------------------------------------------------------------------------- 1 | import { helper } from '@ember/component/helper'; 2 | 3 | export function bizBaz(params/*, hash*/) { 4 | return params; 5 | } 6 | 7 | export default helper(bizBaz); 8 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/config/optional-features.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-template-wrapper": false, 3 | "default-async-observers": true, 4 | "jquery-integration": false, 5 | "template-only-glimmer-components": true 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/controllers/application.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | 4 | export default class ApplicationController extends Controller { 5 | @action addModal() {} 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/controllers/application.js: -------------------------------------------------------------------------------- 1 | import Controller from '@ember/controller'; 2 | import { action } from '@ember/object'; 3 | 4 | export default class ApplicationController extends Controller { 5 | @action addModal() {} 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | 12 | # misc 13 | /coverage/ 14 | 15 | # ember-try 16 | /.node_modules.ember-try/ 17 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from '../app'; 2 | import config from '../config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import { start } from 'ember-qunit'; 5 | 6 | setApplication(Application.create(config.APP)); 7 | 8 | start(); 9 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from '../app'; 2 | import config from '../config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import { start } from 'ember-qunit'; 5 | 6 | setApplication(Application.create(config.APP)); 7 | 8 | start(); 9 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from '../app'; 2 | import config from '../config/environment'; 3 | import { setApplication } from '@ember/test-helpers'; 4 | import { start } from 'ember-qunit'; 5 | 6 | setApplication(Application.create(config.APP)); 7 | 8 | start(); 9 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'app/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from 'app/config/environment'; 3 | 4 | export default class Router extends EmberRouter { 5 | location = config.locationType; 6 | rootURL = config.rootURL; 7 | } 8 | 9 | Router.map(function () {}); 10 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/.eslintignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | 12 | # misc 13 | /coverage/ 14 | !.* 15 | .*/ 16 | .eslintcache 17 | 18 | # ember-try 19 | /.node_modules.ember-try/ 20 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | const Router = EmberRouter.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootURL 7 | }); 8 | 9 | Router.map(function() { 10 | }); 11 | 12 | export default Router; 13 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/router.js: -------------------------------------------------------------------------------- 1 | import EmberRouter from '@ember/routing/router'; 2 | import config from './config/environment'; 3 | 4 | const Router = EmberRouter.extend({ 5 | location: config.locationType, 6 | rootURL: config.rootURL 7 | }); 8 | 9 | Router.map(function() { 10 | }); 11 | 12 | export default Router; 13 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/.ember-cli: -------------------------------------------------------------------------------- 1 | { 2 | /** 3 | Ember CLI sends analytics information by default. The data is completely 4 | anonymous, but there are times when you might want to disable this behavior. 5 | 6 | Setting `disableAnalytics` to true will prevent any data from being sent. 7 | */ 8 | "disableAnalytics": false 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/.prettierignore: -------------------------------------------------------------------------------- 1 | # unconventional js 2 | /blueprints/*/files/ 3 | /vendor/ 4 | 5 | # compiled output 6 | /dist/ 7 | /tmp/ 8 | 9 | # dependencies 10 | /bower_components/ 11 | /node_modules/ 12 | 13 | # misc 14 | /coverage/ 15 | !.* 16 | .eslintcache 17 | 18 | # ember-try 19 | /.node_modules.ember-try/ 20 | /bower.json.ember-try 21 | /package.json.ember-try 22 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{#each this.availableCountries as |country|}} 5 | {{country.name}} 6 | {{/each}} 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{#each this.availableCountries as |country|}} 5 | {{country.name}} 6 | {{/each}} 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /lib/logger.js: -------------------------------------------------------------------------------- 1 | const { createLogger, format, transports } = require('winston'); 2 | const { combine, timestamp, printf } = format; 3 | 4 | const logFormatter = printf((info) => { 5 | return `${info.timestamp} [${info.level}] ${info.message}`; 6 | }); 7 | 8 | module.exports = createLogger({ 9 | format: combine(timestamp(), logFormatter), 10 | transports: [new transports.File({ filename: 'codemods.log' })], 11 | }); 12 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/tests/test-helper.js: -------------------------------------------------------------------------------- 1 | import Application from 'app/app'; 2 | import config from 'app/config/environment'; 3 | import * as QUnit from 'qunit'; 4 | import { setApplication } from '@ember/test-helpers'; 5 | import { setup } from 'qunit-dom'; 6 | import { start } from 'ember-qunit'; 7 | 8 | setApplication(Application.create(config.APP)); 9 | 10 | setup(QUnit.assert); 11 | 12 | start(); 13 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions' 7 | ]; 8 | 9 | const isCI = !!process.env.CI; 10 | const isProduction = process.env.EMBER_ENV === 'production'; 11 | 12 | if (isCI || isProduction) { 13 | browsers.push('ie 11'); 14 | } 15 | 16 | module.exports = { 17 | browsers 18 | }; 19 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions' 7 | ]; 8 | 9 | const isCI = !!process.env.CI; 10 | const isProduction = process.env.EMBER_ENV === 'production'; 11 | 12 | if (isCI || isProduction) { 13 | browsers.push('ie 11'); 14 | } 15 | 16 | module.exports = { 17 | browsers 18 | }; 19 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions' 7 | ]; 8 | 9 | const isCI = !!process.env.CI; 10 | const isProduction = process.env.EMBER_ENV === 'production'; 11 | 12 | if (isCI || isProduction) { 13 | browsers.push('ie 11'); 14 | } 15 | 16 | module.exports = { 17 | browsers 18 | }; 19 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "6" 5 | 6 | sudo: false 7 | dist: trusty 8 | 9 | addons: 10 | chrome: stable 11 | 12 | cache: 13 | directories: 14 | - $HOME/.npm 15 | 16 | env: 17 | global: 18 | # See https://git.io/vdao3 for details. 19 | - JOBS=1 20 | 21 | before_install: 22 | - npm config set spin false 23 | 24 | script: 25 | - npm run lint:js 26 | - npm test 27 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'app/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from 'ember-resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from 'app/config/environment'; 5 | 6 | export default class App extends Application { 7 | modulePrefix = config.modulePrefix; 8 | podModulePrefix = config.podModulePrefix; 9 | Resolver = Resolver; 10 | } 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | const App = Application.extend({ 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix, 9 | Resolver 10 | }); 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/app.js: -------------------------------------------------------------------------------- 1 | import Application from '@ember/application'; 2 | import Resolver from './resolver'; 3 | import loadInitializers from 'ember-load-initializers'; 4 | import config from './config/environment'; 5 | 6 | const App = Application.extend({ 7 | modulePrefix: config.modulePrefix, 8 | podModulePrefix: config.podModulePrefix, 9 | Resolver 10 | }); 11 | 12 | loadInitializers(App, config.modulePrefix); 13 | 14 | export default App; 15 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 'latest', 5 | }, 6 | env: { 7 | node: true, 8 | es6: true, 9 | }, 10 | extends: ['eslint:recommended', 'prettier'], 11 | plugins: ['prettier'], 12 | rules: { 13 | 'prettier/prettier': 'error', 14 | }, 15 | overrides: [ 16 | { 17 | files: ['**/test.js', '**/*.test.js'], 18 | env: { 19 | jest: true, 20 | }, 21 | }, 22 | ], 23 | }; 24 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.hbs] 16 | insert_final_newline = false 17 | 18 | [*.{diff,md}] 19 | trim_trailing_whitespace = false 20 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | indent_style = space 14 | indent_size = 2 15 | 16 | [*.hbs] 17 | insert_final_newline = false 18 | 19 | [*.{diff,md}] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.sass-cache 13 | /connect.lock 14 | /coverage/ 15 | /libpeerconnection.log 16 | /npm-debug.log* 17 | /testem.log 18 | /yarn-error.log 19 | 20 | # ember-try 21 | /.node_modules.ember-try/ 22 | /bower.json.ember-try 23 | /package.json.ember-try 24 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist/ 5 | /tmp/ 6 | 7 | # dependencies 8 | /bower_components/ 9 | /node_modules/ 10 | 11 | # misc 12 | /.sass-cache 13 | /.eslintcache 14 | /connect.lock 15 | /coverage/ 16 | /libpeerconnection.log 17 | /npm-debug.log* 18 | /testem.log 19 | /yarn-error.log 20 | 21 | # ember-try 22 | /.node_modules.ember-try/ 23 | /bower.json.ember-try 24 | /package.json.ember-try 25 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | language: node_js 3 | node_js: 4 | - "12" 5 | 6 | dist: xenial 7 | 8 | addons: 9 | chrome: stable 10 | 11 | cache: 12 | directories: 13 | - $HOME/.npm 14 | 15 | env: 16 | global: 17 | # See https://git.io/vdao3 for details. 18 | - JOBS=1 19 | 20 | branches: 21 | only: 22 | - master 23 | 24 | before_install: 25 | - curl -o- -L https://yarnpkg.com/install.sh | bash 26 | - export PATH=$HOME/.yarn/bin:$PATH 27 | 28 | script: 29 | - yarn test 30 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/config/ember-cli-update.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0.0", 3 | "packages": [ 4 | { 5 | "name": "ember-cli", 6 | "version": "3.28.6", 7 | "blueprints": [ 8 | { 9 | "name": "app", 10 | "outputRepo": "https://github.com/ember-cli/ember-new-output", 11 | "codemodsSource": "ember-app-codemods-manifest@1", 12 | "isBaseBlueprint": true, 13 | "options": [ 14 | "--yarn" 15 | ] 16 | } 17 | ] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: "/" 5 | groups: 6 | dev-dependencies: 7 | dependency-type: "development" 8 | update-types: 9 | - "minor" 10 | - "patch" 11 | schedule: 12 | interval: weekly 13 | time: "03:00" 14 | timezone: America/New_York 15 | open-pull-requests-limit: 10 16 | versioning-strategy: increase 17 | - package-ecosystem: github-actions 18 | directory: "/" 19 | schedule: 20 | interval: daily 21 | time: "03:00" 22 | timezone: America/New_York 23 | open-pull-requests-limit: 10 24 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/tests/integration/helpers/biz-baz-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import hbs from 'htmlbars-inline-precompile'; 5 | 6 | module('Integration | Helper | biz-baz', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | // Replace this with your real tests. 10 | test('it renders', async function(assert) { 11 | this.set('inputValue', '1234'); 12 | 13 | await render(hbs`{{biz-baz inputValue}}`); 14 | 15 | assert.equal(this.element.textContent.trim(), '1234'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/tests/integration/helpers/biz-baz-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import hbs from 'htmlbars-inline-precompile'; 5 | 6 | module('Integration | Helper | biz-baz', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | // Replace this with your real tests. 10 | test('it renders', async function(assert) { 11 | this.set('inputValue', '1234'); 12 | 13 | await render(hbs`{{biz-baz inputValue}}`); 14 | 15 | assert.equal(this.element.textContent.trim(), '1234'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | Components 5 |

6 | {{#bs-nav type="pills" stacked=true as |nav|}} 7 | {{#each this.model as |comp|}} 8 | {{#nav.item}} 9 | {{#nav.link-to route=comp.demoRoute}} 10 | {{comp.title}} 11 | {{/nav.link-to}} 12 | {{/nav.item}} 13 | {{/each}} 14 | {{/bs-nav}} 15 |
16 |
17 | {{-wat-wat}} 18 | {{outlet}} 19 | {{file-less foo=true}} 20 |
21 |
22 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/templates/application.hbs: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | Components 5 |

6 | 7 | {{#each this.model as |comp|}} 8 | 9 | 10 | {{comp.title}} 11 | 12 | 13 | {{/each}} 14 | 15 |
16 |
17 | {{-wat-wat}} 18 | {{outlet}} 19 | 20 |
21 |
22 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/testem.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | test_page: 'tests/index.html?hidepassed', 5 | disable_watching: true, 6 | launch_in_ci: ['Chrome'], 7 | launch_in_dev: ['Chrome'], 8 | browser_start_timeout: 120, 9 | browser_args: { 10 | Chrome: { 11 | ci: [ 12 | // --no-sandbox is needed when running Chrome inside a container 13 | process.env.CI ? '--no-sandbox' : null, 14 | '--headless', 15 | '--disable-dev-shm-usage', 16 | '--disable-software-rasterizer', 17 | '--mute-audio', 18 | '--remote-debugging-port=0', 19 | '--window-size=1440,900', 20 | ].filter(Boolean), 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test_page: 'tests/index.html?hidepassed', 3 | disable_watching: true, 4 | launch_in_ci: [ 5 | 'Chrome' 6 | ], 7 | launch_in_dev: [ 8 | 'Chrome' 9 | ], 10 | browser_args: { 11 | Chrome: { 12 | ci: [ 13 | // --no-sandbox is needed when running Chrome inside a container 14 | process.env.CI ? '--no-sandbox' : null, 15 | '--headless', 16 | '--disable-gpu', 17 | '--disable-dev-shm-usage', 18 | '--disable-software-rasterizer', 19 | '--mute-audio', 20 | '--remote-debugging-port=0', 21 | '--window-size=1440,900' 22 | ].filter(Boolean) 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test_page: 'tests/index.html?hidepassed', 3 | disable_watching: true, 4 | launch_in_ci: [ 5 | 'Chrome' 6 | ], 7 | launch_in_dev: [ 8 | 'Chrome' 9 | ], 10 | browser_args: { 11 | Chrome: { 12 | ci: [ 13 | // --no-sandbox is needed when running Chrome inside a container 14 | process.env.CI ? '--no-sandbox' : null, 15 | '--headless', 16 | '--disable-gpu', 17 | '--disable-dev-shm-usage', 18 | '--disable-software-rasterizer', 19 | '--mute-audio', 20 | '--remote-debugging-port=0', 21 | '--window-size=1440,900' 22 | ].filter(Boolean) 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/testem.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test_page: 'tests/index.html?hidepassed', 3 | disable_watching: true, 4 | launch_in_ci: [ 5 | 'Chrome' 6 | ], 7 | launch_in_dev: [ 8 | 'Chrome' 9 | ], 10 | browser_args: { 11 | Chrome: { 12 | ci: [ 13 | // --no-sandbox is needed when running Chrome inside a container 14 | process.env.CI ? '--no-sandbox' : null, 15 | '--headless', 16 | '--disable-gpu', 17 | '--disable-dev-shm-usage', 18 | '--disable-software-rasterizer', 19 | '--mute-audio', 20 | '--remote-debugging-port=0', 21 | '--window-size=1440,900' 22 | ].filter(Boolean) 23 | } 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | const { gatherTelemetryForUrl, analyzeEmberObject } = require('ember-codemods-telemetry-helpers'); 4 | 5 | const argv = require('yargs') 6 | .usage('Usage: $0 --telemetry=http://localhost:4200 [path or glob]') 7 | .alias('t', 'telemetry') 8 | .nargs('t', 1) 9 | .describe('t', 'Address of the development server') 10 | .help('h') 11 | .alias('h', 'help').argv; 12 | 13 | (async () => { 14 | if (argv.telemetry) { 15 | await gatherTelemetryForUrl(argv.telemetry, analyzeEmberObject); 16 | } 17 | 18 | require('codemod-cli').runTransform( 19 | __dirname, 20 | 'angle-brackets' /* transform name */, 21 | process.argv.slice(2) /* paths or globs */, 22 | 'hbs' 23 | ); 24 | })(); 25 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/config/targets.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const browsers = [ 4 | 'last 1 Chrome versions', 5 | 'last 1 Firefox versions', 6 | 'last 1 Safari versions', 7 | ]; 8 | 9 | // Ember's browser support policy is changing, and IE11 support will end in 10 | // v4.0 onwards. 11 | // 12 | // See https://deprecations.emberjs.com/v3.x#toc_3-0-browser-support-policy 13 | // 14 | // If you need IE11 support on a version of Ember that still offers support 15 | // for it, uncomment the code block below. 16 | // 17 | // const isCI = Boolean(process.env.CI); 18 | // const isProduction = process.env.EMBER_ENV === 'production'; 19 | // 20 | // if (isCI || isProduction) { 21 | // browsers.push('ie 11'); 22 | // } 23 | 24 | module.exports = { 25 | browsers, 26 | }; 27 | -------------------------------------------------------------------------------- /.release-plan.json: -------------------------------------------------------------------------------- 1 | { 2 | "solution": { 3 | "ember-angle-brackets-codemod": { 4 | "impact": "patch", 5 | "oldVersion": "7.0.0", 6 | "newVersion": "7.0.1", 7 | "constraints": [ 8 | { 9 | "impact": "patch", 10 | "reason": "Appears in changelog section :bug: Bug Fix" 11 | } 12 | ], 13 | "pkgJSONPath": "./package.json" 14 | } 15 | }, 16 | "description": "## Release (2024-02-14)\n\nember-angle-brackets-codemod 7.0.1 (patch)\n\n#### :bug: Bug Fix\n* [#515](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/515) Move helper disambiguation to a flag that is off by default ([@lolmaus](https://github.com/lolmaus))\n\n#### Committers: 1\n- Andrey Mikhaylov (lolmaus) ([@lolmaus](https://github.com/lolmaus))\n" 17 | } 18 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | App 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | App 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | App 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | App 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | 12 | 13 | 14 | 15 | {{content-for "head-footer"}} 16 | 17 | 18 | {{content-for "body"}} 19 | 20 | 21 | 22 | 23 | {{content-for "body-footer"}} 24 | 25 | 26 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | parserOptions: { 4 | ecmaVersion: 2017, 5 | sourceType: 'module' 6 | }, 7 | plugins: [ 8 | 'ember' 9 | ], 10 | extends: [ 11 | 'eslint:recommended', 12 | 'plugin:ember/recommended' 13 | ], 14 | env: { 15 | browser: true 16 | }, 17 | rules: { 18 | }, 19 | overrides: [ 20 | // node files 21 | { 22 | files: [ 23 | 'ember-cli-build.js', 24 | 'testem.js', 25 | 'blueprints/*/index.js', 26 | 'config/**/*.js', 27 | 'lib/*/index.js' 28 | ], 29 | parserOptions: { 30 | sourceType: 'script', 31 | ecmaVersion: 2015 32 | }, 33 | env: { 34 | browser: false, 35 | node: true 36 | } 37 | } 38 | ] 39 | }; 40 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - master 8 | pull_request: {} 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | node-version: [16.x, 18.x, 20.x] 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: pnpm/action-setup@v3 22 | with: 23 | version: 8 24 | - uses: actions/setup-node@v4 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | cache: pnpm 28 | - run: pnpm install --frozen-lockfile 29 | - run: pnpm lint 30 | - run: pnpm test 31 | - uses: coverallsapp/github-action@master 32 | with: 33 | github-token: ${{ secrets.GITHUB_TOKEN }} 34 | - run: pnpm test:integration 35 | - run: pnpm test:notelemetry 36 | 37 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/tests/integration/components/foo-bar-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import hbs from 'htmlbars-inline-precompile'; 5 | 6 | module('Integration | Component | foo-bar', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it renders', async function(assert) { 10 | // Set any properties with this.set('myProperty', 'value'); 11 | // Handle any actions with this.set('myAction', function(val) { ... }); 12 | 13 | await render(hbs`{{foo-bar}}`); 14 | 15 | assert.equal(this.element.textContent.trim(), ''); 16 | 17 | // Template block usage: 18 | await render(hbs` 19 | {{#foo-bar}} 20 | template block text 21 | {{/foo-bar}} 22 | `); 23 | 24 | assert.equal(this.element.textContent.trim(), 'template block text'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/tests/integration/components/foo-bar-test.js: -------------------------------------------------------------------------------- 1 | import { module, test } from 'qunit'; 2 | import { setupRenderingTest } from 'ember-qunit'; 3 | import { render } from '@ember/test-helpers'; 4 | import hbs from 'htmlbars-inline-precompile'; 5 | 6 | module('Integration | Component | foo-bar', function(hooks) { 7 | setupRenderingTest(hooks); 8 | 9 | test('it renders', async function(assert) { 10 | // Set any properties with this.set('myProperty', 'value'); 11 | // Handle any actions with this.set('myAction', function(val) { ... }); 12 | 13 | await render(hbs`{{foo-bar}}`); 14 | 15 | assert.equal(this.element.textContent.trim(), ''); 16 | 17 | // Template block usage: 18 | await render(hbs` 19 | {{#foo-bar}} 20 | template block text 21 | {{/foo-bar}} 22 | `); 23 | 24 | assert.equal(this.element.textContent.trim(), 'template block text'); 25 | }); 26 | }); 27 | -------------------------------------------------------------------------------- /test/run-test-without-telemetry.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const execa = require('execa'); 4 | const path = require('path'); 5 | 6 | // resolved from the root of the project 7 | const inputDir = path.resolve('./test/fixtures/without-telemetry/input'); 8 | const binPath = path.resolve('./bin/cli.js'); 9 | const execOpts = { cwd: inputDir, stderr: 'inherit' }; 10 | 11 | (async () => { 12 | console.log('running codemod'); 13 | 14 | const codemod = execa(binPath, ['app'], execOpts); 15 | codemod.stdout.pipe(process.stdout); 16 | await codemod; 17 | 18 | console.log('codemod complete'); 19 | 20 | console.log('comparing results'); 21 | 22 | try { 23 | await execa('diff', ['-rq', './app', '../output/app'], execOpts); 24 | } catch (e) { 25 | console.error('codemod did not run successfully'); 26 | console.log(e); 27 | 28 | process.exit(1); 29 | } 30 | 31 | console.log('codemod ran successfully! 🎉'); 32 | process.exit(0); 33 | })(); 34 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function (defaults) { 6 | let app = new EmberApp(defaults, { 7 | 'ember-bootstrap': { 8 | 'bootstrapVersion': 4, 9 | 'importBootstrapFont': false, 10 | 'importBootstrapCSS': true 11 | } 12 | }); 13 | 14 | // Use `app.import` to add additional libraries to the generated 15 | // output files. 16 | // 17 | // If you need to use different assets in different 18 | // environments, specify an object as the first parameter. That 19 | // object's keys should be the environment name and the values 20 | // should be the asset to use in that environment. 21 | // 22 | // If the library that you are including contains AMD or ES6 23 | // modules that you would like to import into your application 24 | // please specify an object with the list of modules as keys 25 | // along with the exports of each module as its value. 26 | 27 | return app.toTree(); 28 | }; 29 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function(defaults) { 6 | let app = new EmberApp(defaults, { 7 | 'ember-bootstrap': { 8 | 'bootstrapVersion': 4, 9 | 'importBootstrapFont': false, 10 | 'importBootstrapCSS': true 11 | } 12 | }); 13 | 14 | // Use `app.import` to add additional libraries to the generated 15 | // output files. 16 | // 17 | // If you need to use different assets in different 18 | // environments, specify an object as the first parameter. That 19 | // object's keys should be the environment name and the values 20 | // should be the asset to use in that environment. 21 | // 22 | // If the library that you are including contains AMD or ES6 23 | // modules that you would like to import into your application 24 | // please specify an object with the list of modules as keys 25 | // along with the exports of each module as its value. 26 | 27 | return app.toTree(); 28 | }; 29 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function(defaults) { 6 | let app = new EmberApp(defaults, { 7 | 'ember-bootstrap': { 8 | 'bootstrapVersion': 4, 9 | 'importBootstrapFont': false, 10 | 'importBootstrapCSS': true 11 | } 12 | }); 13 | 14 | // Use `app.import` to add additional libraries to the generated 15 | // output files. 16 | // 17 | // If you need to use different assets in different 18 | // environments, specify an object as the first parameter. That 19 | // object's keys should be the environment name and the values 20 | // should be the asset to use in that environment. 21 | // 22 | // If the library that you are including contains AMD or ES6 23 | // modules that you would like to import into your application 24 | // please specify an object with the list of modules as keys 25 | // along with the exports of each module as its value. 26 | 27 | return app.toTree(); 28 | }; 29 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/ember-cli-build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const EmberApp = require('ember-cli/lib/broccoli/ember-app'); 4 | 5 | module.exports = function(defaults) { 6 | let app = new EmberApp(defaults, { 7 | 'ember-bootstrap': { 8 | 'bootstrapVersion': 4, 9 | 'importBootstrapFont': false, 10 | 'importBootstrapCSS': true 11 | } 12 | }); 13 | 14 | // Use `app.import` to add additional libraries to the generated 15 | // output files. 16 | // 17 | // If you need to use different assets in different 18 | // environments, specify an object as the first parameter. That 19 | // object's keys should be the environment name and the values 20 | // should be the asset to use in that environment. 21 | // 22 | // If the library that you are including contains AMD or ES6 23 | // modules that you would like to import into your application 24 | // please specify an object with the list of modules as keys 25 | // along with the exports of each module as its value. 26 | 27 | return app.toTree(); 28 | }; 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 ember-codemods 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | App Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | App Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | App Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | {{content-for "body-footer"}} 31 | {{content-for "test-body-footer"}} 32 | 33 | 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | #editors 64 | .vscode 65 | 66 | 67 | tmp 68 | *.log 69 | test/**/yarn.lock 70 | -mock-telemetry.json 71 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | App Tests 7 | 8 | 9 | 10 | {{content-for "head"}} 11 | {{content-for "test-head"}} 12 | 13 | 14 | 15 | 16 | 17 | {{content-for "head-footer"}} 18 | {{content-for "test-head-footer"}} 19 | 20 | 21 | {{content-for "body"}} 22 | {{content-for "test-body"}} 23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | {{content-for "body-footer"}} 38 | {{content-for "test-body-footer"}} 39 | 40 | 41 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'app', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false 17 | } 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | } 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // here you can enable a production-specific feature 48 | } 49 | 50 | return ENV; 51 | }; 52 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'app', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false 17 | } 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | } 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // here you can enable a production-specific feature 48 | } 49 | 50 | return ENV; 51 | }; 52 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (environment) { 4 | let ENV = { 5 | modulePrefix: 'app', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false, 17 | }, 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | }, 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // here you can enable a production-specific feature 48 | } 49 | 50 | return ENV; 51 | }; 52 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/config/environment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(environment) { 4 | let ENV = { 5 | modulePrefix: 'app', 6 | environment, 7 | rootURL: '/', 8 | locationType: 'auto', 9 | EmberENV: { 10 | FEATURES: { 11 | // Here you can enable experimental features on an ember canary build 12 | // e.g. 'with-controller': true 13 | }, 14 | EXTEND_PROTOTYPES: { 15 | // Prevent Ember Data from overriding Date.parse. 16 | Date: false 17 | } 18 | }, 19 | 20 | APP: { 21 | // Here you can pass flags/options to your application instance 22 | // when it is created 23 | } 24 | }; 25 | 26 | if (environment === 'development') { 27 | // ENV.APP.LOG_RESOLVER = true; 28 | // ENV.APP.LOG_ACTIVE_GENERATION = true; 29 | // ENV.APP.LOG_TRANSITIONS = true; 30 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true; 31 | // ENV.APP.LOG_VIEW_LOOKUPS = true; 32 | } 33 | 34 | if (environment === 'test') { 35 | // Testem prefers this... 36 | ENV.locationType = 'none'; 37 | 38 | // keep test console output quieter 39 | ENV.APP.LOG_ACTIVE_GENERATION = false; 40 | ENV.APP.LOG_VIEW_LOOKUPS = false; 41 | 42 | ENV.APP.rootElement = '#ember-testing'; 43 | ENV.APP.autoboot = false; 44 | } 45 | 46 | if (environment === 'production') { 47 | // here you can enable a production-specific feature 48 | } 49 | 50 | return ENV; 51 | }; 52 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/.eslintrc.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | ecmaVersion: 2018, 8 | sourceType: 'module', 9 | ecmaFeatures: { 10 | legacyDecorators: true, 11 | }, 12 | }, 13 | plugins: ['ember'], 14 | extends: [ 15 | 'eslint:recommended', 16 | 'plugin:ember/recommended', 17 | 'plugin:prettier/recommended', 18 | ], 19 | env: { 20 | browser: true, 21 | }, 22 | rules: {}, 23 | overrides: [ 24 | // node files 25 | { 26 | files: [ 27 | './.eslintrc.js', 28 | './.prettierrc.js', 29 | './.template-lintrc.js', 30 | './ember-cli-build.js', 31 | './testem.js', 32 | './blueprints/*/index.js', 33 | './config/**/*.js', 34 | './lib/*/index.js', 35 | './server/**/*.js', 36 | ], 37 | parserOptions: { 38 | sourceType: 'script', 39 | }, 40 | env: { 41 | browser: false, 42 | node: true, 43 | }, 44 | plugins: ['node'], 45 | extends: ['plugin:node/recommended'], 46 | rules: { 47 | // this can be removed once the following is fixed 48 | // https://github.com/mysticatea/eslint-plugin-node/issues/77 49 | 'node/no-unpublished-require': 'off', 50 | }, 51 | }, 52 | { 53 | // Test files: 54 | files: ['tests/**/*-test.{js,ts}'], 55 | extends: ['plugin:qunit/recommended'], 56 | }, 57 | ], 58 | }; 59 | -------------------------------------------------------------------------------- /transforms/angle-brackets/telemetry/invokable.js: -------------------------------------------------------------------------------- 1 | const logger = require('debug'); 2 | const debug = logger('transform'); 3 | const HELPER = 'Helper'; 4 | const COMPONENT = 'Component'; 5 | 6 | function invokableName(name, type) { 7 | let invokePath; 8 | 9 | if (name.startsWith('@ember/component/')) { 10 | invokePath = '@ember/component/'; 11 | } else if (name.startsWith('@ember/routing/')) { 12 | invokePath = '@ember/routing/'; 13 | } else if (type === HELPER) { 14 | invokePath = '/helpers/'; 15 | } else { 16 | invokePath = '/components/'; 17 | } 18 | 19 | return name.substring(name.lastIndexOf(invokePath) + invokePath.length, name.length); 20 | } 21 | 22 | function getInvokableData(telemetry) { 23 | let helpers = new Set(); 24 | let components = new Set(); 25 | let telemetryKeys = Object.keys(telemetry); 26 | 27 | debug(`\nknown modules:\n${JSON.stringify(telemetryKeys, null, 2)}`); 28 | for (let name of telemetryKeys) { 29 | let entry = telemetry[name]; 30 | if (entry.type === HELPER) { 31 | helpers.add(invokableName(name, entry.type)); 32 | } 33 | if (entry.type === COMPONENT) { 34 | components.add(invokableName(name, entry.type)); 35 | } 36 | } 37 | 38 | helpers = Array.from(helpers); 39 | components = Array.from(components); 40 | 41 | debug(`\nHelpers found in telemetry:\n${JSON.stringify(helpers, null, 2)}`); 42 | debug(`\nComponents found in telemetry:\n${JSON.stringify(components, null, 2)}`); 43 | return { helpers, components }; 44 | } 45 | 46 | module.exports = { 47 | getInvokableData, 48 | }; 49 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/README.md: -------------------------------------------------------------------------------- 1 | # input 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | A short introduction of this app could easily go here. 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | * [Git](https://git-scm.com/) 11 | * [Node.js](https://nodejs.org/) (with npm) 12 | * [Ember CLI](https://ember-cli.com/) 13 | * [Google Chrome](https://google.com/chrome/) 14 | 15 | ## Installation 16 | 17 | * `git clone ` this repository 18 | * `cd input` 19 | * `npm install` 20 | 21 | ## Running / Development 22 | 23 | * `ember serve` 24 | * Visit your app at [http://localhost:4200](http://localhost:4200). 25 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 26 | 27 | ### Code Generators 28 | 29 | Make use of the many generators for code, try `ember help generate` for more details 30 | 31 | ### Running Tests 32 | 33 | * `ember test` 34 | * `ember test --server` 35 | 36 | ### Linting 37 | 38 | * `yarn lint` 39 | * `yarn lint:fix` 40 | 41 | ### Building 42 | 43 | * `ember build` (development) 44 | * `ember build --environment production` (production) 45 | 46 | ### Deploying 47 | 48 | Specify what it takes to deploy your app. 49 | 50 | ## Further Reading / Useful Links 51 | 52 | * [ember.js](https://emberjs.com/) 53 | * [ember-cli](https://ember-cli.com/) 54 | * Development Browser Extensions 55 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 56 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 57 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/README.md: -------------------------------------------------------------------------------- 1 | # input 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | A short introduction of this app could easily go here. 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | * [Git](https://git-scm.com/) 11 | * [Node.js](https://nodejs.org/) (with npm) 12 | * [Ember CLI](https://ember-cli.com/) 13 | * [Google Chrome](https://google.com/chrome/) 14 | 15 | ## Installation 16 | 17 | * `git clone ` this repository 18 | * `cd input` 19 | * `npm install` 20 | 21 | ## Running / Development 22 | 23 | * `ember serve` 24 | * Visit your app at [http://localhost:4200](http://localhost:4200). 25 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 26 | 27 | ### Code Generators 28 | 29 | Make use of the many generators for code, try `ember help generate` for more details 30 | 31 | ### Running Tests 32 | 33 | * `ember test` 34 | * `ember test --server` 35 | 36 | ### Linting 37 | 38 | * `npm run lint:js` 39 | * `npm run lint:js -- --fix` 40 | 41 | ### Building 42 | 43 | * `ember build` (development) 44 | * `ember build --environment production` (production) 45 | 46 | ### Deploying 47 | 48 | Specify what it takes to deploy your app. 49 | 50 | ## Further Reading / Useful Links 51 | 52 | * [ember.js](https://emberjs.com/) 53 | * [ember-cli](https://ember-cli.com/) 54 | * Development Browser Extensions 55 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 56 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 57 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/README.md: -------------------------------------------------------------------------------- 1 | # input 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | A short introduction of this app could easily go here. 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | * [Git](https://git-scm.com/) 11 | * [Node.js](https://nodejs.org/) (with npm) 12 | * [Ember CLI](https://ember-cli.com/) 13 | * [Google Chrome](https://google.com/chrome/) 14 | 15 | ## Installation 16 | 17 | * `git clone ` this repository 18 | * `cd input` 19 | * `npm install` 20 | 21 | ## Running / Development 22 | 23 | * `ember serve` 24 | * Visit your app at [http://localhost:4200](http://localhost:4200). 25 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 26 | 27 | ### Code Generators 28 | 29 | Make use of the many generators for code, try `ember help generate` for more details 30 | 31 | ### Running Tests 32 | 33 | * `ember test` 34 | * `ember test --server` 35 | 36 | ### Linting 37 | 38 | * `npm run lint:js` 39 | * `npm run lint:js -- --fix` 40 | 41 | ### Building 42 | 43 | * `ember build` (development) 44 | * `ember build --environment production` (production) 45 | 46 | ### Deploying 47 | 48 | Specify what it takes to deploy your app. 49 | 50 | ## Further Reading / Useful Links 51 | 52 | * [ember.js](https://emberjs.com/) 53 | * [ember-cli](https://ember-cli.com/) 54 | * Development Browser Extensions 55 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 56 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 57 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/README.md: -------------------------------------------------------------------------------- 1 | # input 2 | 3 | This README outlines the details of collaborating on this Ember application. 4 | A short introduction of this app could easily go here. 5 | 6 | ## Prerequisites 7 | 8 | You will need the following things properly installed on your computer. 9 | 10 | * [Git](https://git-scm.com/) 11 | * [Node.js](https://nodejs.org/) (with npm) 12 | * [Ember CLI](https://ember-cli.com/) 13 | * [Google Chrome](https://google.com/chrome/) 14 | 15 | ## Installation 16 | 17 | * `git clone ` this repository 18 | * `cd input` 19 | * `npm install` 20 | 21 | ## Running / Development 22 | 23 | * `ember serve` 24 | * Visit your app at [http://localhost:4200](http://localhost:4200). 25 | * Visit your tests at [http://localhost:4200/tests](http://localhost:4200/tests). 26 | 27 | ### Code Generators 28 | 29 | Make use of the many generators for code, try `ember help generate` for more details 30 | 31 | ### Running Tests 32 | 33 | * `ember test` 34 | * `ember test --server` 35 | 36 | ### Linting 37 | 38 | * `npm run lint:js` 39 | * `npm run lint:js -- --fix` 40 | 41 | ### Building 42 | 43 | * `ember build` (development) 44 | * `ember build --environment production` (production) 45 | 46 | ### Deploying 47 | 48 | Specify what it takes to deploy your app. 49 | 50 | ## Further Reading / Useful Links 51 | 52 | * [ember.js](https://emberjs.com/) 53 | * [ember-cli](https://ember-cli.com/) 54 | * Development Browser Extensions 55 | * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) 56 | * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/) 57 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release Process 2 | 3 | Releases in this repo are mostly automated using [release-plan](https://github.com/embroider-build/release-plan/). Once you label all your PRs correctly (see below) you will have an automatically generated PR that updates your CHANGELOG.md file and a `.release-plan.json` that is used prepare the release once the PR is merged. 4 | 5 | ## Preparation 6 | 7 | Since the majority of the actual release process is automated, the remaining tasks before releasing are: 8 | 9 | - correctly labeling **all** pull requests that have been merged since the last release 10 | - updating pull request titles so they make sense to our users 11 | 12 | Some great information on why this is important can be found at [keepachangelog.com](https://keepachangelog.com/en/1.1.0/), but the overall 13 | guiding principle here is that changelogs are for humans, not machines. 14 | 15 | When reviewing merged PR's the labels to be used are: 16 | 17 | * breaking - Used when the PR is considered a breaking change. 18 | * enhancement - Used when the PR adds a new feature or enhancement. 19 | * bug - Used when the PR fixes a bug included in a previous release. 20 | * documentation - Used when the PR adds or updates documentation. 21 | * internal - Internal changes or things that don't fit in any other category. 22 | 23 | **Note:** `release-plan` requires that **all** PRs are labeled. If a PR doesn't fit in a category it's fine to label it as `internal` 24 | 25 | ## Release 26 | 27 | Once the prep work is completed, the actual release is straight forward: you just need to merge the open [Plan Release](https://github.com/ember-codemods/ember-angle-brackets-codemod/pulls?q=is%3Apr+is%3Aopen+%22Prepare+Release%22+in%3Atitle) PR 28 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/output/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Small description for app goes here", 6 | "license": "MIT", 7 | "author": "", 8 | "directories": { 9 | "doc": "doc", 10 | "test": "tests" 11 | }, 12 | "repository": "", 13 | "scripts": { 14 | "build": "ember build", 15 | "lint:js": "eslint .", 16 | "start": "ember serve", 17 | "test": "ember test" 18 | }, 19 | "devDependencies": { 20 | "@ember/jquery": "^0.6.0", 21 | "@ember/optional-features": "^0.7.0", 22 | "bootstrap": "^4.3.1", 23 | "broccoli-asset-rev": "^3.0.0", 24 | "ember-ajax": "^5.0.0", 25 | "ember-bootstrap": "^2.8.1", 26 | "ember-cli": "~3.10.1", 27 | "ember-cli-app-version": "^3.2.0", 28 | "ember-cli-babel": "^7.7.3", 29 | "ember-cli-dependency-checker": "^3.1.0", 30 | "ember-cli-eslint": "^5.1.0", 31 | "ember-cli-htmlbars": "^3.0.1", 32 | "ember-cli-htmlbars-inline-precompile": "^2.1.0", 33 | "ember-cli-inject-live-reload": "^1.8.2", 34 | "ember-cli-sri": "^2.1.1", 35 | "ember-cli-template-lint": "^1.0.0-beta.1", 36 | "ember-cli-uglify": "^2.1.0", 37 | "ember-composable-helpers": "^2.3.1", 38 | "ember-data": "~3.10.0", 39 | "ember-export-application-global": "^2.0.0", 40 | "ember-load-initializers": "^2.0.0", 41 | "ember-maybe-import-regenerator": "^0.1.6", 42 | "ember-qunit": "^4.4.1", 43 | "ember-resolver": "^5.0.1", 44 | "ember-source": "~3.10.0", 45 | "ember-welcome-page": "^4.0.0", 46 | "eslint-plugin-ember": "^6.2.0", 47 | "eslint-plugin-node": "^9.0.1", 48 | "loader.js": "^4.7.0", 49 | "qunit-dom": "^0.8.4" 50 | }, 51 | "engines": { 52 | "node": "6.* || 8.* || >= 10.*" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/input/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Small description for app goes here", 6 | "license": "MIT", 7 | "author": "", 8 | "directories": { 9 | "doc": "doc", 10 | "test": "tests" 11 | }, 12 | "repository": "", 13 | "scripts": { 14 | "build": "ember build", 15 | "lint:js": "eslint .", 16 | "start": "ember serve", 17 | "test": "ember test" 18 | }, 19 | "devDependencies": { 20 | "@ember/jquery": "^0.6.0", 21 | "@ember/optional-features": "^0.7.0", 22 | "bootstrap": "^4.3.1", 23 | "broccoli-asset-rev": "^3.0.0", 24 | "ember-ajax": "^5.0.0", 25 | "ember-bootstrap": "^2.8.1", 26 | "ember-cli": "~3.10.1", 27 | "ember-cli-app-version": "^3.2.0", 28 | "ember-cli-babel": "^7.7.3", 29 | "ember-cli-dependency-checker": "^3.1.0", 30 | "ember-cli-eslint": "^5.1.0", 31 | "ember-cli-htmlbars": "^3.0.1", 32 | "ember-cli-htmlbars-inline-precompile": "^2.1.0", 33 | "ember-cli-inject-live-reload": "^1.8.2", 34 | "ember-cli-sri": "^2.1.1", 35 | "ember-cli-string-helpers": "^4.0.5", 36 | "ember-cli-template-lint": "^1.0.0-beta.1", 37 | "ember-cli-uglify": "^2.1.0", 38 | "ember-data": "~3.10.0", 39 | "ember-export-application-global": "^2.0.0", 40 | "ember-load-initializers": "^2.0.0", 41 | "ember-maybe-import-regenerator": "^0.1.6", 42 | "ember-qunit": "^4.4.1", 43 | "ember-resolver": "^5.0.1", 44 | "ember-source": "~3.10.0", 45 | "ember-welcome-page": "^4.0.0", 46 | "eslint-plugin-ember": "^6.2.0", 47 | "eslint-plugin-node": "^9.0.1", 48 | "loader.js": "^4.7.0", 49 | "qunit-dom": "^0.8.4" 50 | }, 51 | "engines": { 52 | "node": "6.* || 8.* || >= 10.*" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/fixtures/without-telemetry/output/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Small description for app goes here", 6 | "license": "MIT", 7 | "author": "", 8 | "directories": { 9 | "doc": "doc", 10 | "test": "tests" 11 | }, 12 | "repository": "", 13 | "scripts": { 14 | "build": "ember build", 15 | "lint:js": "eslint .", 16 | "start": "ember serve", 17 | "test": "ember test" 18 | }, 19 | "devDependencies": { 20 | "@ember/jquery": "^0.6.0", 21 | "@ember/optional-features": "^0.7.0", 22 | "bootstrap": "^4.3.1", 23 | "broccoli-asset-rev": "^3.0.0", 24 | "ember-ajax": "^5.0.0", 25 | "ember-bootstrap": "^2.8.1", 26 | "ember-cli": "~3.10.1", 27 | "ember-cli-app-version": "^3.2.0", 28 | "ember-cli-babel": "^7.7.3", 29 | "ember-cli-dependency-checker": "^3.1.0", 30 | "ember-cli-eslint": "^5.1.0", 31 | "ember-cli-htmlbars": "^3.0.1", 32 | "ember-cli-htmlbars-inline-precompile": "^2.1.0", 33 | "ember-cli-inject-live-reload": "^1.8.2", 34 | "ember-cli-sri": "^2.1.1", 35 | "ember-cli-template-lint": "^1.0.0-beta.1", 36 | "ember-cli-uglify": "^2.1.0", 37 | "ember-composable-helpers": "^2.3.1", 38 | "ember-data": "~3.10.0", 39 | "ember-export-application-global": "^2.0.0", 40 | "ember-load-initializers": "^2.0.0", 41 | "ember-maybe-import-regenerator": "^0.1.6", 42 | "ember-qunit": "^4.4.1", 43 | "ember-resolver": "^5.0.1", 44 | "ember-source": "~3.10.0", 45 | "ember-welcome-page": "^4.0.0", 46 | "eslint-plugin-ember": "^6.2.0", 47 | "eslint-plugin-node": "^9.0.1", 48 | "loader.js": "^4.7.0", 49 | "qunit-dom": "^0.8.4" 50 | }, 51 | "engines": { 52 | "node": "6.* || 8.* || >= 10.*" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/run-test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | const { spawn } = require('child_process'); 4 | const execa = require('execa'); 5 | const path = require('path'); 6 | 7 | // resolved from the root of the project 8 | const inputDir = path.resolve('./test/fixtures/with-telemetry/input'); 9 | const execOpts = { cwd: inputDir, stderr: 'inherit' }; 10 | 11 | (async () => { 12 | console.log('installing deps'); 13 | 14 | await execa('pnpm', ['install', '--no-lockfile'], execOpts); 15 | 16 | console.log('starting serve'); 17 | 18 | // We use spawn for this one so we can kill it later without throwing an error 19 | const emberServe = spawn('pnpm', ['start'], execOpts); 20 | emberServe.stderr.pipe(process.stderr); 21 | emberServe.stdout.pipe(process.stdout); 22 | 23 | await new Promise((resolve) => { 24 | emberServe.stdout.on('data', (data) => { 25 | if (data.toString().includes('Build successful')) { 26 | resolve(); 27 | } 28 | }); 29 | }); 30 | 31 | console.log('running codemod'); 32 | 33 | const codemod = execa( 34 | path.resolve('./bin/cli.js'), 35 | ['--telemetry', 'http://localhost:4200', 'app'], 36 | execOpts 37 | ); 38 | codemod.stdout.pipe(process.stdout); 39 | await codemod; 40 | 41 | console.log('codemod complete, ending serve'); 42 | 43 | emberServe.kill('SIGTERM'); 44 | 45 | console.log('comparing results'); 46 | 47 | try { 48 | const compareFixture = await import('compare-fixture'); 49 | compareFixture.default(path.join(inputDir, 'app'), path.join(inputDir, '../output/app')); 50 | } catch (e) { 51 | console.error('codemod did not run successfully'); 52 | console.log(e); 53 | 54 | process.exit(1); 55 | } 56 | 57 | console.log('codemod ran successfully! 🎉'); 58 | process.exit(0); 59 | })(); 60 | -------------------------------------------------------------------------------- /transforms/angle-brackets/index.js: -------------------------------------------------------------------------------- 1 | const { getOptions: getCLIOptions } = require('codemod-cli'); 2 | const { getTelemetry } = require('ember-codemods-telemetry-helpers'); 3 | const path = require('path'); 4 | const fs = require('fs'); 5 | const transform = require('./transform'); 6 | const { getInvokableData } = require('./telemetry/invokable'); 7 | 8 | function getOptions() { 9 | let options = {}; 10 | 11 | let cliOptions = getCLIOptions(); 12 | if (cliOptions.config) { 13 | let filePath = path.join(process.cwd(), cliOptions.config); 14 | let config = JSON.parse(fs.readFileSync(filePath)); 15 | 16 | if (config.helpers) { 17 | options.helpers = config.helpers; 18 | } 19 | 20 | if (config.components) { 21 | options.components = config.components; 22 | } 23 | 24 | if (config.skipAttributesThatMatchRegex) { 25 | options.skipAttributesThatMatchRegex = config.skipAttributesThatMatchRegex; 26 | } 27 | 28 | if (config.skipFilesThatMatchRegex) { 29 | options.skipFilesThatMatchRegex = new RegExp(config.skipFilesThatMatchRegex); 30 | } 31 | 32 | options.includeValuelessDataTestAttributes = !!config.includeValuelessDataTestAttributes; 33 | options.skipBuiltInComponents = !!config.skipBuiltInComponents; 34 | options.unambiguousHelpers = !!config.unambiguousHelpers; 35 | } 36 | 37 | if (cliOptions.telemetry) { 38 | options.telemetry = cliOptions.telemetry; 39 | } 40 | 41 | return options; 42 | } 43 | 44 | module.exports = function (file) { 45 | const options = getOptions(); 46 | let invokableData = options.telemetry ? getInvokableData(getTelemetry()) : {}; 47 | try { 48 | return transform(file, options, invokableData); 49 | } catch (e) { 50 | throw new Error( 51 | `Transformation errored on file ${file.path}. Reason ${e}. Please report this in https://github.com/ember-codemods/ember-angle-brackets-codemod/issues\n\nStack trace:\n${e.stack}` 52 | ); 53 | } 54 | }; 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ember-angle-brackets-codemod", 3 | "version": "7.0.1", 4 | "description": "Codemod to convert to Angle brackets syntax", 5 | "keywords": [ 6 | "codemod-cli", 7 | "codemods", 8 | "ember", 9 | "ember-codemod" 10 | ], 11 | "homepage": "https://github.com/ember-codemods/ember-angle-brackets-codemod#readme", 12 | "bugs": { 13 | "url": "https://github.com/ember-codemods/ember-angle-brackets-codemod/issues" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/ember-codemods/ember-angle-brackets-codemod.git" 18 | }, 19 | "license": "MIT", 20 | "author": "", 21 | "bin": "./bin/cli.js", 22 | "scripts": { 23 | "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls", 24 | "debug:integration": "node --inspect-brk ./test/run-test.js", 25 | "debug:test": "node --inspect-brk ./node_modules/.bin/jest -i -t ", 26 | "lint": "eslint . --cache", 27 | "test": "codemod-cli test --coverage", 28 | "test:integration": "node ./test/run-test.js", 29 | "test:notelemetry": " node ./test/run-test-without-telemetry.js" 30 | }, 31 | "jest": { 32 | "collectCoverageFrom": [ 33 | "transforms/angle-brackets/transform.js" 34 | ] 35 | }, 36 | "dependencies": { 37 | "codemod-cli": "^3.1.2", 38 | "debug": "^4.3.1", 39 | "ember-codemods-telemetry-helpers": "^3.0.0", 40 | "ember-template-recast": "^6.1.4", 41 | "winston": "^3.3.3", 42 | "yargs": "^17.7.2" 43 | }, 44 | "devDependencies": { 45 | "compare-fixture": "^1.1.2", 46 | "coveralls": "^3.1.1", 47 | "eslint": "^8.56.0", 48 | "eslint-config-prettier": "^8.5.0", 49 | "eslint-plugin-prettier": "^4.2.1", 50 | "execa": "^5.0.0", 51 | "jest": "^28.1.3", 52 | "prettier": "^2.7.1", 53 | "release-plan": "^0.7.0" 54 | }, 55 | "engines": { 56 | "node": "16.* || 18.* || >=20.*" 57 | }, 58 | "publishConfig": { 59 | "registry": "https://registry.npmjs.org" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # For every push to the master branch, this checks if the release-plan was 2 | # updated and if it was it will publish stable npm packages based on the 3 | # release plan 4 | 5 | name: Publish Stable 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - main 12 | - master 13 | 14 | concurrency: 15 | group: publish-${{ github.head_ref || github.ref }} 16 | cancel-in-progress: true 17 | 18 | jobs: 19 | check-plan: 20 | name: "Check Release Plan" 21 | runs-on: ubuntu-latest 22 | outputs: 23 | command: ${{ steps.check-release.outputs.command }} 24 | 25 | steps: 26 | - uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | ref: 'master' 30 | # This will only cause the `check-plan` job to have a result of `success` 31 | # when the .release-plan.json file was changed on the last commit. This 32 | # plus the fact that this action only runs on main will be enough of a guard 33 | - id: check-release 34 | run: if git diff --name-only HEAD HEAD~1 | grep -w -q ".release-plan.json"; then echo "command=release"; fi >> $GITHUB_OUTPUT 35 | 36 | publish: 37 | name: "NPM Publish" 38 | runs-on: ubuntu-latest 39 | needs: check-plan 40 | if: needs.check-plan.outputs.command == 'release' 41 | permissions: 42 | contents: write 43 | pull-requests: write 44 | 45 | steps: 46 | - uses: actions/checkout@v4 47 | - uses: actions/setup-node@v4 48 | with: 49 | node-version: 18 50 | # This creates an .npmrc that reads the NODE_AUTH_TOKEN environment variable 51 | registry-url: 'https://registry.npmjs.org' 52 | 53 | - uses: pnpm/action-setup@v3 54 | with: 55 | version: 8 56 | - run: pnpm install --frozen-lockfile 57 | - name: npm publish 58 | run: pnpm release-plan publish 59 | 60 | env: 61 | GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }} 62 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 63 | -------------------------------------------------------------------------------- /test/fixtures/with-telemetry/input/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Small description for app goes here", 6 | "license": "MIT", 7 | "author": "", 8 | "directories": { 9 | "doc": "doc", 10 | "test": "tests" 11 | }, 12 | "repository": "", 13 | "scripts": { 14 | "build": "ember build --environment=production", 15 | "lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"", 16 | "lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix", 17 | "lint:hbs:fix": "ember-template-lint . --fix", 18 | "lint:js": "eslint . --cache", 19 | "lint:js:fix": "eslint . --fix", 20 | "start": "ember serve", 21 | "test": "npm-run-all lint test:*", 22 | "test:ember": "ember test" 23 | }, 24 | "devDependencies": { 25 | "@ember/optional-features": "^2.0.0", 26 | "@ember/test-helpers": "^2.6.0", 27 | "@glimmer/component": "^1.0.4", 28 | "@glimmer/tracking": "^1.0.4", 29 | "babel-eslint": "^10.1.0", 30 | "bootstrap": "^4.3.1", 31 | "broccoli-asset-rev": "^3.0.0", 32 | "ember-auto-import": "^1.12.0", 33 | "ember-bootstrap": "^4.0.0", 34 | "ember-cli": "~3.28.6", 35 | "ember-cli-app-version": "^5.0.0", 36 | "ember-cli-babel": "^7.26.10", 37 | "ember-cli-dependency-checker": "^3.2.0", 38 | "ember-cli-htmlbars": "^5.7.2", 39 | "ember-cli-inject-live-reload": "^2.1.0", 40 | "ember-cli-sri": "^2.1.1", 41 | "ember-cli-string-helpers": "^4.0.5", 42 | "ember-cli-terser": "^4.0.2", 43 | "ember-data": "~3.28.6", 44 | "ember-export-application-global": "^2.0.1", 45 | "ember-fetch": "^8.1.1", 46 | "ember-load-initializers": "^2.1.2", 47 | "ember-maybe-import-regenerator": "^0.1.6", 48 | "ember-page-title": "^6.2.2", 49 | "ember-qunit": "^5.1.5", 50 | "ember-resolver": "^8.0.3", 51 | "ember-source": "~3.28.8", 52 | "ember-template-lint": "^3.15.0", 53 | "ember-welcome-page": "^4.1.0", 54 | "eslint": "^7.32.0", 55 | "eslint-config-prettier": "^8.3.0", 56 | "eslint-plugin-ember": "^10.5.8", 57 | "eslint-plugin-node": "^11.1.0", 58 | "eslint-plugin-prettier": "^3.4.1", 59 | "eslint-plugin-qunit": "^6.2.0", 60 | "loader.js": "^4.7.0", 61 | "npm-run-all": "^4.1.5", 62 | "prettier": "^2.5.1", 63 | "qunit": "^2.17.2", 64 | "qunit-dom": "^1.6.0" 65 | }, 66 | "engines": { 67 | "node": "12.* || 14.* || >= 16" 68 | }, 69 | "ember": { 70 | "edition": "octane" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /transforms/angle-brackets/known-helpers.js: -------------------------------------------------------------------------------- 1 | const KNOWN_HELPERS = [ 2 | // Ember.js 3 | 'action', 4 | 'array', 5 | 'component', 6 | 'concat', 7 | 'debugger', 8 | 'each', 9 | 'each-in', 10 | 'else', 11 | 'get', 12 | 'hash', 13 | 'if', 14 | 'if-unless', 15 | 'in-element', 16 | '-in-element', 17 | 'let', 18 | 'loc', 19 | 'log', 20 | 'mut', 21 | 'partial', 22 | 'query-params', 23 | 'readonly', 24 | 'unbound', 25 | 'unless', 26 | 'with', 27 | 28 | // Glimmer VM 29 | 'identity', // glimmer blocks 30 | 'render-inverse', // glimmer blocks 31 | '-get-dynamic-var', // glimmer internal helper 32 | 33 | // ember-route-helpers 34 | 'transition-to', 35 | 'replace-with', 36 | 'transition-to-external', 37 | 'replace-with-external', 38 | 39 | // ember-intl 40 | 'format-date', 41 | 'format-message', 42 | 'format-relative', 43 | 'format-time', 44 | 'format-money', 45 | 'format-number', 46 | 't', 47 | 48 | // ember-moment 49 | 'is-after', 50 | 'is-before', 51 | 'is-between', 52 | 'is-same', 53 | 'is-same-or-after', 54 | 'is-same-or-before', 55 | 'moment', 56 | 'moment-calendar', 57 | 'moment-diff', 58 | 'moment-duration', 59 | 'moment-format', 60 | 'moment-from', 61 | 'moment-from-now', 62 | 'moment-to', 63 | 'moment-to-now', 64 | 'now', 65 | 'unix', 66 | 67 | // ember-cp-validations 68 | 'v-get', 69 | 70 | // ember-route-action-helper 71 | 'route-action', 72 | 73 | // ember-composable-helpers 74 | 'map-by', 75 | 'sort-by', 76 | 'filter-by', 77 | 'reject-by', 78 | 'find-by', 79 | 'object-at', 80 | 'has-block', 81 | 'has-next', 82 | 'has-previous', 83 | 'group-by', 84 | 'not-eq', 85 | 'is-array', 86 | 'is-empty', 87 | 'is-equal', 88 | 89 | // liquid-fire 90 | 'liquid-unless', 91 | 'liquid-container', 92 | 'liquid-outlet', 93 | 'liquid-versions', 94 | 'liquid-bind', 95 | 'liquid-spacer', 96 | 'liquid-sync', 97 | 'liquid-measured', 98 | 'liquid-child', 99 | 'liquid-if', 100 | 101 | // ember-animated 102 | 'animated-beacon', 103 | 'animated-each', 104 | 'animated-if', 105 | 'animated-orphans', 106 | 'animated-value', 107 | 108 | // ember-app-version 109 | 'app-version', 110 | 111 | // ember-font-awesome 112 | 'fa-icon', 113 | 'fa-list', 114 | 'fa-stack', 115 | 116 | // ember-svg-jar 117 | 'svg-jar', 118 | 119 | // ember-concurrency 120 | 'perform', 121 | 122 | // ember-maybe-in-element 123 | 'maybe-in-element', 124 | ]; 125 | 126 | module.exports = KNOWN_HELPERS; 127 | -------------------------------------------------------------------------------- /transforms/angle-brackets/telemetry/mock-invokables.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'app/components/deep-component': { 3 | type: 'Component', 4 | }, 5 | 'app/components/bs-button': { 6 | type: 'Component', 7 | }, 8 | 'app/components/bs-button-group': { 9 | type: 'Component', 10 | }, 11 | 'app/components/my-component': { 12 | type: 'Component', 13 | }, 14 | 'app/components/x-foo': { 15 | type: 'Component', 16 | }, 17 | 'app/components/common/accordion-component': { 18 | type: 'Component', 19 | }, 20 | 'app/components/some-component': { 21 | type: 'Component', 22 | }, 23 | 'app/components/foo': { 24 | type: 'Component', 25 | }, 26 | 'app/components/ui/site-header': { 27 | type: 'Component', 28 | }, 29 | 'app/components/ui/button': { 30 | type: 'Component', 31 | }, 32 | 'app/components/some-path/another-path/super-select': { 33 | type: 'Component', 34 | }, 35 | 'app/components/site-header': { 36 | type: 'Component', 37 | }, 38 | 'app/components/super-select': { 39 | type: 'Component', 40 | }, 41 | 'app/components/my-card': { 42 | type: 'Component', 43 | }, 44 | 'app/components/wrapper': { 45 | type: 'Component', 46 | }, 47 | 'app/components/foo/bar': { 48 | type: 'Component', 49 | }, 50 | 'app/components/some-helper1': { 51 | type: 'Component', 52 | }, 53 | 'app/components/some-helper2': { 54 | type: 'Component', 55 | }, 56 | 'app/components/shared/documents-modal/-email-client': { 57 | type: 'Component', 58 | }, 59 | 'app/components/shared/-documents-modal/-email-client': { 60 | type: 'Component', 61 | }, 62 | 'app/components/-shared/-documents-modal/-email-client': { 63 | type: 'Component', 64 | }, 65 | 'app/components/-shared/documents-modal/-email-client': { 66 | type: 'Component', 67 | }, 68 | 'app/components/-shared/documents-modal/email-client': { 69 | type: 'Component', 70 | }, 71 | 'app/components/shared/-documents-modal/email-client': { 72 | type: 'Component', 73 | }, 74 | 'app/components/my-component1': { 75 | type: 'Component', 76 | }, 77 | 'app/components/my-component2': { 78 | type: 'Component', 79 | }, 80 | 'app/components/x-foo/x-bar': { 81 | type: 'Component', 82 | }, 83 | 'app/components/my-component3': { 84 | type: 'Component', 85 | }, 86 | 'app/components/my-component4': { 87 | type: 'Component', 88 | }, 89 | 'app/components/my-component5': { 90 | type: 'Component', 91 | }, 92 | 'app/components/foo-bar': { 93 | type: 'Component', 94 | }, 95 | 'app/components/baz-bang/foo-bar/bang': { 96 | type: 'Component', 97 | }, 98 | 'app/helpers/helper-1': { 99 | type: 'Helper', 100 | }, 101 | 'app/helpers/nested/helper': { 102 | type: 'Helper', 103 | }, 104 | 'app/helpers/fooknownhelper': { 105 | type: 'Helper', 106 | }, 107 | }; 108 | -------------------------------------------------------------------------------- /.github/workflows/plan-release.yml: -------------------------------------------------------------------------------- 1 | name: Release Plan Review 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - master 7 | pull_request: 8 | types: 9 | - labeled 10 | 11 | concurrency: 12 | group: plan-release # only the latest one of these should ever be running 13 | cancel-in-progress: true 14 | 15 | jobs: 16 | check-plan: 17 | name: "Check Release Plan" 18 | runs-on: ubuntu-latest 19 | outputs: 20 | command: ${{ steps.check-release.outputs.command }} 21 | 22 | steps: 23 | - uses: actions/checkout@v4 24 | with: 25 | fetch-depth: 0 26 | ref: 'master' 27 | # This will only cause the `check-plan` job to have a "command" of `release` 28 | # when the .release-plan.json file was changed on the last commit. 29 | - id: check-release 30 | run: if git diff --name-only HEAD HEAD~1 | grep -w -q ".release-plan.json"; then echo "command=release"; fi >> $GITHUB_OUTPUT 31 | 32 | prepare_release_notes: 33 | name: Prepare Release Notes 34 | runs-on: ubuntu-latest 35 | timeout-minutes: 5 36 | needs: check-plan 37 | permissions: 38 | contents: write 39 | pull-requests: write 40 | outputs: 41 | explanation: ${{ steps.explanation.outputs.text }} 42 | # only run on push event if plan wasn't updated (don't create a release plan when we're releasing) 43 | # only run on labeled event if the PR has already been merged 44 | if: (github.event_name == 'push' && needs.check-plan.outputs.command != 'release') || (github.event_name == 'pull_request' && github.event.pull_request.merged == true) 45 | 46 | steps: 47 | - uses: actions/checkout@v4 48 | # We need to download lots of history so that 49 | # lerna-changelog can discover what's changed since the last release 50 | with: 51 | fetch-depth: 0 52 | - uses: actions/setup-node@v4 53 | with: 54 | node-version: 18 55 | 56 | - uses: pnpm/action-setup@v3 57 | with: 58 | version: 8 59 | - run: pnpm install --frozen-lockfile 60 | 61 | - name: "Generate Explanation and Prep Changelogs" 62 | id: explanation 63 | run: | 64 | set -x 65 | 66 | pnpm release-plan prepare 67 | 68 | echo 'text<> $GITHUB_OUTPUT 69 | jq .description .release-plan.json -r >> $GITHUB_OUTPUT 70 | echo 'EOF' >> $GITHUB_OUTPUT 71 | env: 72 | GITHUB_AUTH: ${{ secrets.GITHUB_TOKEN }} 73 | 74 | - uses: peter-evans/create-pull-request@v6 75 | with: 76 | commit-message: "Prepare Release using 'release-plan'" 77 | labels: "internal" 78 | branch: release-preview 79 | title: Prepare Release 80 | body: | 81 | This PR is a preview of the release that [release-plan](https://github.com/embroider-build/release-plan) has prepared. To release you should just merge this PR 👍 82 | 83 | ----------------------------------------- 84 | 85 | ${{ steps.explanation.outputs.text }} 86 | -------------------------------------------------------------------------------- /transforms/angle-brackets/telemetry/invokable.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { getInvokableData } = require('../telemetry/invokable'); 3 | 4 | describe('Extract Helpers from Telemetry Data`', () => { 5 | test('helper names from addons: addon/helpers/*', () => { 6 | const addonTelemetry = { 7 | 'ember-bootstrap/helpers/bs-contains': { 8 | type: 'Helper', 9 | }, 10 | }; 11 | expect(getInvokableData(addonTelemetry)).toEqual({ 12 | components: [], 13 | helpers: ['bs-contains'], 14 | }); 15 | }); 16 | 17 | test('helper names from addons: addon/helpers/* - subfolders', () => { 18 | const addonTelemetry = { 19 | 'ember-bootstrap/helpers/utils/bs-contains': { 20 | type: 'Helper', 21 | }, 22 | 'ember-bootstrap/components/bs-navbar/nav': { 23 | type: 'Component', 24 | }, 25 | }; 26 | expect(getInvokableData(addonTelemetry)).toEqual({ 27 | components: ['bs-navbar/nav'], 28 | helpers: ['utils/bs-contains'], 29 | }); 30 | }); 31 | 32 | test('helper names from host app: app/helpers/*', () => { 33 | const appTelemetry = { 34 | 'app/helpers/biz-baz': { 35 | type: 'Helper', 36 | }, 37 | }; 38 | expect(getInvokableData(appTelemetry)).toEqual({ components: [], helpers: ['biz-baz'] }); 39 | }); 40 | 41 | test('helper names from host app: app/helpesr/* - subfolders', () => { 42 | const appTelemetry = { 43 | 'app/helpers/utility/biz-baz': { 44 | type: 'Helper', 45 | }, 46 | }; 47 | expect(getInvokableData(appTelemetry)).toEqual({ 48 | components: [], 49 | helpers: ['utility/biz-baz'], 50 | }); 51 | }); 52 | 53 | test('helper names from host app: app/helpesr/* - hyphen usage', () => { 54 | const appTelemetry = { 55 | 'app/helpers/-biz-baz': { 56 | type: 'Helper', 57 | }, 58 | }; 59 | expect(getInvokableData(appTelemetry)).toEqual({ components: [], helpers: ['-biz-baz'] }); 60 | }); 61 | 62 | test('helper names from host app: app/helpesr/* - hyphen usage / subfolders', () => { 63 | const appTelemetry = { 64 | 'app/helpers/utility/-biz-baz': { 65 | type: 'Helper', 66 | }, 67 | }; 68 | expect(getInvokableData(appTelemetry)).toEqual({ 69 | components: [], 70 | helpers: ['utility/-biz-baz'], 71 | }); 72 | }); 73 | 74 | test('helper names from host app: app/helpesr/* - deeply nested hyphen mixture', () => { 75 | const appTelemetry = { 76 | 'app/helpers/utility/-/deeply/-nested/-in/-biz-baz': { 77 | type: 'Helper', 78 | }, 79 | }; 80 | expect(getInvokableData(appTelemetry)).toEqual({ 81 | components: [], 82 | helpers: ['utility/-/deeply/-nested/-in/-biz-baz'], 83 | }); 84 | }); 85 | 86 | test('it discovers templates as a `Component` type', () => { 87 | const appTelemetry = { 88 | 'app/templates/components/file-less': { 89 | type: 'Component', 90 | }, 91 | 'app/components/standard-component': { 92 | type: 'Component', 93 | }, 94 | }; 95 | expect(getInvokableData(appTelemetry)).toEqual({ 96 | components: ['file-less', 'standard-component'], 97 | helpers: [], 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ember-angle-brackets-codemod 2 | 3 | [![Ember Observer Score](https://emberobserver.com/badges/ember-angle-brackets-codemod.svg)](https://emberobserver.com/addons/ember-angle-brackets-codemod) 4 | [![Build Status](https://travis-ci.org/ember-codemods/ember-angle-brackets-codemod.svg?branch=master)](https://travis-ci.org/ember-codemods/ember-angle-brackets-codemod) 5 | [![Coverage Status](https://coveralls.io/repos/github/ember-codemods/ember-angle-brackets-codemod/badge.svg?branch=master)](https://coveralls.io/github/ember-codemods/ember-angle-brackets-codemod?branch=master) 6 | [![npm version](http://img.shields.io/npm/v/ember-angle-brackets-codemod.svg?style=flat)](https://npmjs.org/package/ember-angle-brackets-codemod 'View this project on npm') 7 | [![dependencies Status](https://david-dm.org/ember-codemods/ember-angle-brackets-codemod/status.svg)](https://david-dm.org/ember-codemods/ember-angle-brackets-codemod) 8 | [![devDependencies Status](https://david-dm.org/ember-codemods/ember-angle-brackets-codemod/dev-status.svg)](https://david-dm.org/ember-codemods/ember-angle-brackets-codemod?type=dev) 9 | 10 | A [jscodeshift](https://github.com/facebook/jscodeshift) Codemod to convert curly braces syntax to angle brackets syntax for templates 11 | in an Ember.js app 12 | 13 | Refer to this [RFC](https://github.com/emberjs/rfcs/blob/master/text/0311-angle-bracket-invocation.md) for more details on Angle brackets invocation syntax. 14 | 15 | ## Requirements 16 | 17 | This codemod currently only supports Ember's classic filesystem layout, meaning it won't work if your app uses pods. For more info, [see this issue](https://github.com/ember-codemods/ember-angle-brackets-codemod/issues/217). 18 | 19 | ## Usage 20 | 21 | **WARNING**: `jscodeshift`, and thus this codemod, **edits your files in place**. 22 | It does not make a copy. Make sure your code is checked into a source control 23 | repository like Git and that you have no outstanding changes to commit before 24 | running this tool. 25 | 26 | 1. Start your ember development server 27 | 2. Run Codemod, pointing it at the address of the development server 28 | 29 | ```sh 30 | $ cd my-ember-app-or-addon 31 | $ npx ember-angle-brackets-codemod --telemetry=http://localhost:4200 ./path/of/files/ or ./some**/*glob.hbs 32 | ``` 33 | 34 | Telemetry helpers runs the app, grabs basic info about all of the modules at runtime. This allows the codemod to know the names of every helper, component, route, controller, etc. in the app without guessing / relying on static analysis. They basically help you to create "runtime assisted codemods". 35 | 36 | See "Gathering runtime data" section of [ember-native-class-codemod](https://github.com/ember-codemods/ember-native-class-codemod#gathering-runtime-data) for some additonal information. 37 | 38 | ### Running the codemod without Telemetry 39 | 40 | ```sh 41 | $ cd my-ember-app-or-addon 42 | $ npx ember-angle-brackets-codemod ./path/of/files/ or ./some**/*glob.hbs 43 | ``` 44 | 45 | **NOTE** If you are not using telemetry, you will probably need to [manually configure the codemod to at least skip any helpers](#skipping-helpers) that are invoked in the template files you are running it on. 46 | 47 | ## From 48 | 49 | ```hbs 50 | {{site-header user=this.user class=(if this.user.isAdmin "admin")}} 51 | 52 | {{#super-select selected=this.user.country as |s|}} 53 | {{#each this.availableCountries as |country|}} 54 | {{#s.option value=country}}{{country.name}}{{/s.option}} 55 | {{/each}} 56 | {{/super-select}} 57 | 58 | {{ui/button text="Click me"}} 59 | ``` 60 | 61 | ## To 62 | 63 | ```hbs 64 | 65 | 66 | {{#each this.availableCountries as |country|}} 67 | 68 | {{country.name}} 69 | 70 | {{/each}} 71 | 72 | 73 | 74 | ``` 75 | 76 | ## Advanced Usage 77 | 78 | ### Skipping helpers 79 | 80 | To help the codemod disambiguate components and helpers, you can define a list of helpers from your application in a configuration file as follows: 81 | 82 | **config/anglebrackets-codemod-config.json** 83 | 84 | ```js 85 | { 86 | "helpers": [ 87 | "date-formatter", 88 | "info-pill" 89 | ] 90 | } 91 | ``` 92 | 93 | The codemod will then ignore the above list of helpers and prevent them from being transformed into the new angle-brackets syntax. 94 | 95 | You can also disable the conversion of the built-in components `{{link-to}}`, `{{input}}` and `{{textarea}}` as follows: 96 | 97 | **config/anglebrackets-codemod-config.json** 98 | 99 | ```js 100 | { 101 | "helpers": [], 102 | "skipBuiltInComponents": true 103 | } 104 | ``` 105 | 106 | You can execute the codemod with custom configuration by specifying a `--config` command line option as follows: 107 | 108 | ```sh 109 | $ cd my-ember-app-or-addon 110 | $ npx ember-angle-brackets-codemod angle-brackets app/templates --config ./config/anglebrackets-codemod-config.json 111 | ``` 112 | 113 | To get a list of helpers in your app you can do this in the Developer Console in your browser inside of your app: 114 | 115 | ```js 116 | var componentLikeHelpers = Object.keys(require.entries) 117 | .filter(name => name.includes('/helpers/') || name.includes('/helper')) 118 | .filter(name => !name.includes('/-')) 119 | .map(name => { 120 | let path = name.split('/helpers/'); 121 | return path.pop(); 122 | }) 123 | .filter(name => !name.includes('/')) 124 | .uniq(); 125 | 126 | copy(JSON.stringify(componentLikeHelpers)); 127 | ``` 128 | 129 | ### Skipping some files 130 | 131 | If there are files that don't convert well, you can skip them by specifying an optional `skipFilesThatMatchRegex` configuration setting. For example, with the configuration below, all files that contain `"foo"` or `"bar"` will be skipped: 132 | 133 | **config/anglebrackets-codemod-config.json** 134 | 135 | ```js 136 | { 137 | "helpers": [], 138 | "skipBuiltInComponents": true, 139 | "skipFilesThatMatchRegex": "foo|bar" 140 | } 141 | ``` 142 | 143 | ### Skipping some attributes 144 | 145 | If there are cases where some attributes should not be prefixed with `@`, you can skip them by specifying an optional `skipAttributesThatMatchRegex` configuration setting. 146 | For example, with the configuration below, all attributes that matches either `/data-/gim` or `/aria-/gim` will not be prefixed with `@`: 147 | 148 | ### Processing valueless data test attributes 149 | 150 | Curly invocations that have `data-test-` attributes with no value are not processed by default. The configuration below will cause them to be processed: 151 | 152 | **config/anglebrackets-codemod-config.json** 153 | 154 | ```js 155 | { 156 | "includeValuelessDataTestAttributes": true 157 | } 158 | ``` 159 | 160 | **config/anglebrackets-codemod-config.json** 161 | 162 | ```js 163 | { 164 | "helpers": [], 165 | "skipBuiltInComponents": true, 166 | "skipAttributesThatMatchRegex": ["/data-/gim", "/aria-/gim"] 167 | } 168 | ``` 169 | 170 | Input: 171 | 172 | ```js 173 | {{some-component data-test-foo=true aria-label="bar" foo=true}} 174 | ``` 175 | 176 | Output: 177 | 178 | ```js 179 | 180 | ``` 181 | 182 | ### Converting specific components only 183 | 184 | If you would like to only convert certain component invocations to use the angle brackets syntax, use the `components` configuration setting and specify component names. For example, with the configuration below, only the `{{baz}}` and `{{bat}}` components will be converted, leaving everything else intact. 185 | 186 | **config/anglebrackets-codemod-config.json** 187 | 188 | ```js 189 | { 190 | "components": ["baz", "bat"] 191 | } 192 | ``` 193 | 194 | ### Making helper invocations unambiguous 195 | 196 | You may want to convert invocations like `{{concat "foo" "bar"}}` into `{{(concat "foo" "bar")}}`, which may be useful as a temporary step when upgrading to strict-mode Embroider. 197 | 198 | In your **config/anglebrackets-codemod-config.json**, add this: 199 | 200 | ```js 201 | { 202 | "unambiguousHelpers": true 203 | } 204 | ``` 205 | 206 | Note that unambiguous helpers do not work in non-Embroider Ember, as of January 2024. 207 | 208 | Note that ambiguous invocations that cannot be statically distinguished between a helper, a property and a component — will not be modified. 209 | 210 | ## Debugging Workflow 211 | 212 | Oftentimes, you want to debug the codemod or the transform to identify issues with the code or to understand 213 | how the transforms are working, or to troubleshoot why some tests are failing. 214 | 215 | Hence we recommend a debugging work-flow like below to quickly find out what is causing the issue. 216 | 217 | ### 1. Place `debugger` statements 218 | 219 | Add `debugger` statements, in appropriate places in the code. For example: 220 | 221 | ```js 222 | ... 223 | const params = a.value.params.map(p => { 224 | debugger; 225 | if(p.type === "SubExpression") { 226 | return transformNestedSubExpression(p) 227 | ... 228 | ``` 229 | 230 | ### 2. Inspect the process with node debug 231 | 232 | Here we are going to start the tests selectively in node debug mode. Since the 233 | codemod is using [jest](https://jestjs.io/) in turn 234 | to run the tests, jest is having an option `-t ` to run a particular 235 | set of tests instead of running the whole test suite. 236 | 237 | We are making use of both these features to start our tests in this particular fashion. 238 | For more details on node debug, visit the [official](https://nodejs.org/en/docs/guides/debugging-getting-started/) 239 | Node.js debugging guide, and for jest documentation on tests, please refer [here](https://jestjs.io/docs/en/cli). 240 | 241 | ```sh 242 | node --inspect-brk ./node_modules/.bin/jest --runInBand --testNamePattern 243 | ``` 244 | 245 | For example, if you want to debug the `null-subexp` test or only that particular test case is failing because of an issue. 246 | 247 | ```sh 248 | node --inspect-brk ./node_modules/.bin/jest --runInBand --testNamePattern 'null-subexp' 249 | ``` 250 | 251 | Or you can make use of the npm scripts defined in package.json. All you need to pass the test name as the extra parameter with the script. 252 | 253 | ```sh 254 | npm run debug:test 'null-subexp' 255 | ``` 256 | 257 | Using yarn 258 | ```sh 259 | yarn debug:test 'null-subexp' 260 | ``` 261 | 262 | Once you run the above command, your tests will start running in debug mode and your breakpoints will be 263 | triggered appropriately when that particular block of code gets executed. You can run the debugger inside 264 | Chrome browser dev-tools. More details on [here](https://developers.google.com/web/tools/chrome-devtools/javascript/) 265 | 266 | ## AST Explorer playground 267 | 268 | 1. Go to the [AST Explorer](https://astexplorer.net/#/gist/b128d5545d7ccc52400b922f3b5010b4/642c6a8d3cc021257110bcf6b1714d1065891aec) 269 | 2. Paste your curly brace syntax code in the top left corner window (Source) 270 | 3. You will get the converted angle bracket syntax in the bottom right corner window (Transform Output) 271 | 272 | ## RFC 273 | 274 | - [Angle Bracket Invocation](https://github.com/emberjs/rfcs/blob/master/text/0311-angle-bracket-invocation.md) 275 | - [Angle Bracket Invocations For Built-in Components](https://github.com/emberjs/rfcs/blob/32a25b31d67d67bc7581dd0bead559063b06f076/text/0459-angle-bracket-built-in-components.md) 276 | 277 | ## Known issues 278 | 279 | - No formatting preserved 280 | 281 | ## References: 282 | 283 | - https://github.com/glimmerjs/glimmer-vm/issues/685 284 | - https://github.com/q2ebanking/ember-template-rewrite 285 | - https://github.com/ember-template-lint/ember-template-recast 286 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## Release (2024-02-14) 3 | 4 | ember-angle-brackets-codemod 7.0.1 (patch) 5 | 6 | #### :bug: Bug Fix 7 | * [#515](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/515) Move helper disambiguation to a flag that is off by default ([@lolmaus](https://github.com/lolmaus)) 8 | 9 | #### Committers: 1 10 | - Andrey Mikhaylov (lolmaus) ([@lolmaus](https://github.com/lolmaus)) 11 | ## Release (2023-12-05) 12 | 13 | ember-angle-brackets-codemod 7.0.0 (major) 14 | 15 | #### :boom: Breaking Change 16 | * [#511](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/511) drop support for node < 16 ([@mansona](https://github.com/mansona)) 17 | * [#509](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/509) Make helpers unambiguious with parens aka subexpressions ([@lolmaus](https://github.com/lolmaus)) 18 | 19 | #### :rocket: Enhancement 20 | * [#505](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/505) Bump ember-template-recast to ^6.1.4 ([@lolmaus](https://github.com/lolmaus)) 21 | 22 | #### :house: Internal 23 | * [#512](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/512) move to pnpm ([@mansona](https://github.com/mansona)) 24 | * [#510](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/510) setup release-plan ([@mansona](https://github.com/mansona)) 25 | * [#506](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/506) Move fixtures to with-telemetry/ for consistency ([@lolmaus](https://github.com/lolmaus)) 26 | 27 | #### Committers: 2 28 | - Andrey Mikhaylov (lolmaus) ([@lolmaus](https://github.com/lolmaus)) 29 | - Chris Manson ([@mansona](https://github.com/mansona)) 30 | 31 | Version 9 of Highlight.js has reached EOL and is no longer supported. 32 | Please upgrade or ask whatever dependency you are using to upgrade. 33 | https://github.com/highlightjs/highlight.js/issues/2877 34 | 35 | 36 | ## v6.0.0 (2022-08-19) 37 | 38 | #### :boom: Breaking Change 39 | * [#499](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/499) breaking: drop EOL versions of Node ([@mansona](https://github.com/mansona)) 40 | 41 | #### :house: Internal 42 | * [#501](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/501) update all dev-dependencies ([@mansona](https://github.com/mansona)) 43 | * [#500](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/500) update ember-codemods-telemetry-helpers to support M1 macs ([@mansona](https://github.com/mansona)) 44 | * [#498](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/498) move from travis to github actions ([@mansona](https://github.com/mansona)) 45 | 46 | #### Committers: 1 47 | - Chris Manson ([@mansona](https://github.com/mansona)) 48 | 49 | ## v5.1.0 (2022-01-28) 50 | 51 | #### :rocket: Enhancement 52 | * [#437](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/437) [Feature]: Add ability to convert only specific component invocations with each run ([@kcarmonamurphy](https://github.com/kcarmonamurphy)) 53 | 54 | #### :bug: Bug Fix 55 | * [#486](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/486) fix any number of models in link-to ([@tylerturdenpants](https://github.com/tylerturdenpants)) 56 | 57 | #### :house: Internal 58 | * [#486](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/486) fix any number of models in link-to ([@tylerturdenpants](https://github.com/tylerturdenpants)) 59 | * [#452](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/452) Update dependencies ([@tylerturdenpants](https://github.com/tylerturdenpants)) 60 | * [#393](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/393) fix eslint prettier errors ([@tylerturdenpants](https://github.com/tylerturdenpants)) 61 | 62 | #### Committers: 4 63 | - Buck Doyle ([@backspace](https://github.com/backspace)) 64 | - Kevin Carmona-Murphy ([@kcarmonamurphy](https://github.com/kcarmonamurphy)) 65 | - Ryan Mark ([@tylerturdenpants](https://github.com/tylerturdenpants)) 66 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview) 67 | 68 | ## v5.0.0 (2020-05-06) 69 | 70 | #### :boom: Breaking Change 71 | * [#287](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/287) Add `--telemetry` argument and ensure running without telemetry is possible ([@rajasegar](https://github.com/rajasegar)) 72 | 73 | #### :house: Internal 74 | * [#320](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/320) Update release automation settings. ([@rwjblue](https://github.com/rwjblue)) 75 | 76 | #### Committers: 4 77 | - Rajasegar Chandran ([@rajasegar](https://github.com/rajasegar)) 78 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue)) 79 | - Tom Wayson ([@tomwayson](https://github.com/tomwayson)) 80 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview) 81 | 82 | 83 | * Add config to enable transform on valueless attrs (#241) (7e06914) 84 | * Bump execa from 3.4.0 to 4.0.0 (#211) (0b06732) 85 | * Bump jest from 24.9.0 to 25.1.0 (#235) (d8fa744) 86 | 87 | ## v4.0.0 (2020-02-03) 88 | 89 | #### :house: Internal 90 | * [#238](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/238) Drop node 8 support ([@tylerturdenpants](https://github.com/tylerturdenpants)) 91 | 92 | #### Committers: 3 93 | - Rajasegar Chandran ([@rajasegar](https://github.com/rajasegar)) 94 | - Ryan Mark ([@tylerturdenpants](https://github.com/tylerturdenpants)) 95 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview) 96 | 97 | ## v3.1.2 (2020-01-13) 98 | 99 | #### :bug: Bug Fix 100 | * [#234](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/234) Add skipAttributesThatMatchRegex to config ([@suchitadoshi1987](https://github.com/suchitadoshi1987)) 101 | 102 | #### Committers: 1 103 | - Suchita Doshi ([@suchitadoshi1987](https://github.com/suchitadoshi1987)) 104 | 105 | ## v3.1.1 (2020-01-10) 106 | 107 | #### :bug: Bug Fix 108 | * [#229](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/229) Accept `skipAttributesThatMatchRegex ` option values as an array of regex strings ([@suchitadoshi1987](https://github.com/suchitadoshi1987)) 109 | 110 | #### Committers: 1 111 | - Suchita Doshi ([@suchitadoshi1987](https://github.com/suchitadoshi1987)) 112 | 113 | ## v3.1.0 (2020-01-08) 114 | 115 | #### :rocket: Enhancement 116 | * [#226](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/226) Add support to skip prefixing `@` for user provided attributes ([@suchitadoshi1987](https://github.com/suchitadoshi1987)) 117 | 118 | #### :bug: Bug Fix 119 | * [#214](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/214) Fix false negatives for `link-to` with `data-` prefixed route name ([@tylerturdenpants](https://github.com/tylerturdenpants)) 120 | 121 | #### Committers: 4 122 | - Ryan Mark ([@tylerturdenpants](https://github.com/tylerturdenpants)) 123 | - Sam Selikoff ([@samselikoff](https://github.com/samselikoff)) 124 | - Suchita Doshi ([@suchitadoshi1987](https://github.com/suchitadoshi1987)) 125 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview) 126 | 127 | ## v3.0.1 (2019-12-12) 128 | 129 | #### :bug: Bug Fix 130 | * [#205](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/205) fix wallstreet integration with telemetry data ([@hmajoros](https://github.com/hmajoros)) 131 | * [#203](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/203) Update README.md ([@samselikoff](https://github.com/samselikoff)) 132 | 133 | #### :memo: Documentation 134 | * [#203](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/203) Update README.md ([@samselikoff](https://github.com/samselikoff)) 135 | 136 | #### Committers: 3 137 | - Hank Majoros ([@hmajoros](https://github.com/hmajoros)) 138 | - Sam Selikoff ([@samselikoff](https://github.com/samselikoff)) 139 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview) 140 | 141 | ## v3.0.0 (2019-11-20) 142 | 143 | #### :boom: Breaking Change 144 | * [#154](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/154) Use telemetry data to detect known components and helpers ([@tylerturdenpants](https://github.com/tylerturdenpants)) 145 | 146 | #### :rocket: Enhancement 147 | * [#154](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/154) Use telemetry data to detect known components and helpers ([@tylerturdenpants](https://github.com/tylerturdenpants)) 148 | 149 | #### Committers: 2 150 | - Ryan Mark ([@tylerturdenpants](https://github.com/tylerturdenpants)) 151 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview) 152 | 153 | ## v2.1.0 (2019-11-12) 154 | 155 | #### :rocket: Enhancement 156 | * [#181](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/181) Add support for templates containing `~` (whitespace control) ([@Turbo87](https://github.com/Turbo87)) 157 | * [#155](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/155) Add a Logger ([@tylerturdenpants](https://github.com/tylerturdenpants)) 158 | 159 | #### :bug: Bug Fix 160 | * [#182](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/182) fix hyphen usage ([@tylerturdenpants](https://github.com/tylerturdenpants)) 161 | 162 | #### :house: Internal 163 | * [#175](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/175) Refactor nested functions to the outer scope ([@tylerturdenpants](https://github.com/tylerturdenpants)) 164 | 165 | #### Committers: 4 166 | - Ryan Mark ([@tylerturdenpants](https://github.com/tylerturdenpants)) 167 | - Suchita Doshi ([@suchitadoshi1987](https://github.com/suchitadoshi1987)) 168 | - Tobias Bieniek ([@Turbo87](https://github.com/Turbo87)) 169 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview) 170 | 171 | ## v2.0.0 (2019-10-22) 172 | 173 | #### :boom: Breaking Change 174 | * [#97](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/97) Use `ember-template-recast` to preserve existing template formatting as much as possible ([@tylerturdenpants](https://github.com/tylerturdenpants)) 175 | * [#100](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/100) Make node version explicit (Node 8 & >= 10) ([@kellyselden](https://github.com/kellyselden)) 176 | 177 | #### :rocket: Enhancement 178 | * [#152](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/152) Preserve existing arguments (don't convert from named arguments to attributes during codemod) ([@tylerturdenpants](https://github.com/tylerturdenpants)) 179 | * [#145](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/145) Add more known common helpers ([@Turbo87](https://github.com/Turbo87)) 180 | * [#97](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/97) Use `ember-template-recast` to preserve existing template formatting as much as possible ([@tylerturdenpants](https://github.com/tylerturdenpants)) 181 | * [#137](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/137) Update list of common helpers ([@Turbo87](https://github.com/Turbo87)) 182 | 183 | #### :bug: Bug Fix 184 | * [#172](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/172) Update to `ember-template-recast@3.2.6` to fix issues with loosing nested else-if contents ([@dependabot-preview[bot]](https://github.com/apps/dependabot-preview)) 185 | * [#168](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/168) Skip block component conversion if the component has an `inverse` block ([@Turbo87](https://github.com/Turbo87)) 186 | * [#165](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/165) Fix `link-to` with `SubExpression` computing the target route name ([@makepanic](https://github.com/makepanic)) 187 | * [#158](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/158) Ensure using `link-to` with conditional models works properly ([@tylerturdenpants](https://github.com/tylerturdenpants)) 188 | * [#144](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/144) Fix inline link-to with subexpression caption crash ([@Turbo87](https://github.com/Turbo87)) 189 | * [#140](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/140) bin/cli: Set correct file extension ([@Turbo87](https://github.com/Turbo87)) 190 | * [#131](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/131) package.json: Remove broken `main` field ([@Turbo87](https://github.com/Turbo87)) 191 | 192 | #### :house: Internal 193 | * [#163](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/163) Add test case for nested else if ([@Turbo87](https://github.com/Turbo87)) 194 | * [#149](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/149) Add test case for splattributes ([@Turbo87](https://github.com/Turbo87)) 195 | * [#47](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/47) Add whitespace control test ([@GavinJoyce](https://github.com/GavinJoyce)) 196 | * [#143](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/143) Use yarn instead of npm internally ([@tylerturdenpants](https://github.com/tylerturdenpants)) 197 | * [#142](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/142) tests: Use inline snapshots ([@Turbo87](https://github.com/Turbo87)) 198 | * [#141](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/141) Adjust `tilde` test fixture ([@Turbo87](https://github.com/Turbo87)) 199 | * [#138](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/138) Convert test suite to use Jest snapshot tests instead of fixture files ([@Turbo87](https://github.com/Turbo87)) 200 | * [#139](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/139) CI: Run ESLint ([@Turbo87](https://github.com/Turbo87)) 201 | * [#135](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/135) ESLint: Fix test path pattern ([@Turbo87](https://github.com/Turbo87)) 202 | * [#134](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/134) Remove obsolete ASTExplorer files ([@Turbo87](https://github.com/Turbo87)) 203 | * [#136](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/136) Cleanup file structure ([@Turbo87](https://github.com/Turbo87)) 204 | * [#133](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/133) Update dependencies ([@Turbo87](https://github.com/Turbo87)) 205 | * [#132](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/132) Replace `ember-addon` keyword with `ember-codemod` ([@Turbo87](https://github.com/Turbo87)) 206 | * [#130](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/130) package.json: Sort contents according to documentation ([@Turbo87](https://github.com/Turbo87)) 207 | * [#128](https://github.com/ember-codemods/ember-angle-brackets-codemod/pull/128) Remove `husky` and `lint-staged` dev dependencies ([@Turbo87](https://github.com/Turbo87)) 208 | 209 | #### Committers: 8 210 | - Christian ([@makepanic](https://github.com/makepanic)) 211 | - Gavin Joyce ([@GavinJoyce](https://github.com/GavinJoyce)) 212 | - Kelly Selden ([@kellyselden](https://github.com/kellyselden)) 213 | - Rajasegar Chandran ([@rajasegar](https://github.com/rajasegar)) 214 | - Robert Jackson ([@rwjblue](https://github.com/rwjblue)) 215 | - Ryan Mark ([@tylerturdenpants](https://github.com/tylerturdenpants)) 216 | - Tobias Bieniek ([@Turbo87](https://github.com/Turbo87)) 217 | - [@dependabot-preview[bot]](https://github.com/apps/dependabot-preview) 218 | 219 | -------------------------------------------------------------------------------- /transforms/angle-brackets/transform.js: -------------------------------------------------------------------------------- 1 | const recast = require('ember-template-recast'); 2 | const logger = require('../../lib/logger'); 3 | 4 | const KNOWN_HELPERS = require('./known-helpers'); 5 | const _EMPTY_STRING_ = `ANGLE_BRACKET_EMPTY_${Date.now()}`; 6 | const { builders: b } = recast; 7 | 8 | /** 9 | * List of HTML attributes for which @ should not be appended 10 | */ 11 | const HTML_ATTRIBUTES = ['class', 'placeholder', 'required']; 12 | const BUILT_IN_COMPONENTS = ['link-to', 'input', 'textarea']; 13 | 14 | let inAttr = false; 15 | 16 | function isAttribute(key) { 17 | return HTML_ATTRIBUTES.includes(key) || isDataAttribute(key); 18 | } 19 | 20 | /** 21 | * Returns true if the key matches any of the user provided regex from the 22 | * `skipAttributesThatMatchRegex` array. 23 | * @param {*} key 24 | * @param {*} config 25 | */ 26 | function shouldSkipAttribute(key, config) { 27 | if (config.skipAttributesThatMatchRegex && config.skipAttributesThatMatchRegex.length) { 28 | return config.skipAttributesThatMatchRegex.some((rx) => { 29 | // Get the user provided string and convert it to regex. 30 | const match = /^\/(.*)\/([a-z]*)$/.exec(rx); 31 | if (match) { 32 | const regex = new RegExp(match[1], match[2]); 33 | return regex.test(key); 34 | } 35 | }); 36 | } 37 | return false; 38 | } 39 | 40 | function isDataAttribute(key) { 41 | return key.startsWith('data-'); 42 | } 43 | 44 | function isBuiltInComponent(key) { 45 | return BUILT_IN_COMPONENTS.includes(key); 46 | } 47 | 48 | function isNestedComponentTagName(tagName) { 49 | return ( 50 | tagName && 51 | tagName.includes && 52 | (tagName.includes('/') || (tagName.includes('-') && tagName.includes('.'))) 53 | ); 54 | } 55 | 56 | function isWallStreet(tagName) { 57 | return tagName.includes('$') || tagName.includes('::'); 58 | } 59 | 60 | /** 61 | * Returns a transformed capitalized tagname for angle brackets syntax 62 | * {{my-component}} => MyComponent 63 | */ 64 | function transformTagName(tagName) { 65 | const SIMPLE_DASHERIZE_REGEXP = /[a-z]|\/|-/g; 66 | const ALPHA = /[A-Za-z0-9]/; 67 | 68 | if (tagName.includes('.')) { 69 | return tagName; 70 | } 71 | 72 | tagName = tagName.replace(SIMPLE_DASHERIZE_REGEXP, (char, index) => { 73 | if (char === '/') { 74 | return '::'; 75 | } 76 | 77 | if (index === 0 || !ALPHA.test(tagName[index - 1])) { 78 | return char.toUpperCase(); 79 | } 80 | 81 | // Remove all occurrences of '-'s from the tagName that aren't starting with `-` 82 | return char === '-' ? '' : char.toLowerCase(); 83 | }); 84 | 85 | return tagName; 86 | } 87 | 88 | function transformNestedSubExpression(subExpression) { 89 | let positionalArgs = subExpression.params.map((param) => { 90 | if (param.type === 'SubExpression') { 91 | return transformNestedSubExpression(param); 92 | } else if (param.type === 'StringLiteral') { 93 | return `"${param.original}"`; 94 | } else { 95 | return param.original; 96 | } 97 | }); 98 | 99 | let namedArgs = []; 100 | if (subExpression.hash.pairs.length > 0) { 101 | namedArgs = subExpression.hash.pairs.map((pair) => { 102 | if (pair.value.type === 'SubExpression') { 103 | let nestedValue = transformNestedSubExpression(pair.value); 104 | return `${pair.key}=${nestedValue}`; 105 | } else { 106 | if (pair.value.type === 'StringLiteral') { 107 | return `${pair.key}="${pair.value.original}"`; 108 | } 109 | return `${pair.key}=${pair.value.original}`; 110 | } 111 | }); 112 | } 113 | 114 | let args = positionalArgs.concat(namedArgs); 115 | return `(${subExpression.path.original} ${args.join(' ')})`; 116 | } 117 | 118 | function shouldSkipFile(fileInfo, config) { 119 | let source = fileInfo.source; 120 | 121 | if (config.skipFilesThatMatchRegex && config.skipFilesThatMatchRegex.test(source)) { 122 | logger.warn( 123 | `WARNING: ${fileInfo.path} was skipped as its content matches the "skipFilesThatMatchRegex" config setting: ${config.skipFilesThatMatchRegex}` 124 | ); 125 | return true; 126 | } 127 | 128 | return false; 129 | } 130 | 131 | function transformAttrs(tagName, attrs, nodeParams, config) { 132 | const newAttrs = attrs.map((a) => { 133 | let _key = a.key; 134 | let _valueType = a.value.type; 135 | let _value; 136 | if ( 137 | (!isAttribute(_key) || !isBuiltInComponent(tagName)) && 138 | !shouldSkipAttribute(_key, config) 139 | ) { 140 | _key = `@${_key}`; 141 | } 142 | 143 | if (_valueType === 'PathExpression') { 144 | _value = b.mustache(a.value); 145 | } else if (_valueType === 'SubExpression') { 146 | if (a.value.hash.pairs.length > 0) { 147 | a.value.type = 'MustacheStatement'; 148 | _value = a.value; 149 | } else { 150 | const params = a.value.params 151 | .map((p) => { 152 | if (p.type === 'SubExpression') { 153 | return transformNestedSubExpression(p); 154 | } else if (p.type === 'StringLiteral') { 155 | return `"${p.original}"`; 156 | } else if (p.type === 'NullLiteral') { 157 | return 'null'; 158 | } else if (p.type === 'UndefinedLiteral') { 159 | return 'undefined'; 160 | } else { 161 | return p.original; 162 | } 163 | }) 164 | .join(' '); 165 | 166 | _value = b.mustache(b.path(`${a.value.path.original} ${params}`)); 167 | } 168 | } else if (_valueType === 'BooleanLiteral') { 169 | _value = b.mustache(b.boolean(a.value.original)); 170 | } else if (_valueType === 'NumberLiteral') { 171 | _value = b.mustache(b.number(a.value.original)); 172 | } else if (_valueType === 'NullLiteral') { 173 | _value = b.mustache('null'); 174 | } else if (_valueType === 'UndefinedLiteral') { 175 | _value = b.mustache('undefined'); 176 | } else { 177 | _value = b.text(a.value.original || _EMPTY_STRING_); 178 | } 179 | return b.attr(_key, _value); 180 | }); 181 | 182 | const newValuelessAttrs = nodeParams 183 | .filter((param) => !!param.parts) 184 | .filter((param) => isAttribute(param.parts[0])) 185 | .map((param) => { 186 | const attr = b.attr(param.parts[0], b.text('')); 187 | attr.isValueless = true; 188 | return attr; 189 | }); 190 | 191 | return [...newAttrs, ...newValuelessAttrs]; 192 | } 193 | 194 | function isQueryParam(param) { 195 | return ( 196 | param && param.type === 'SubExpression' && param.path && param.path.original === 'query-params' 197 | ); 198 | } 199 | 200 | function transformLinkToTextParam(textParam) { 201 | if (textParam.type === 'SubExpression') { 202 | return subExpressionToMustacheStatement(textParam); 203 | } else if (textParam.type.includes('Literal')) { 204 | return b.text(textParam.value); 205 | } else { 206 | return b.mustache(textParam.original); 207 | } 208 | } 209 | 210 | function transformModelParams(modelParam) { 211 | let type = modelParam.type; 212 | if (type === 'StringLiteral') { 213 | return b.text(modelParam.value); 214 | } else if (type === 'NumberLiteral') { 215 | return b.mustache(b.number(modelParam.original)); 216 | } else { 217 | return b.mustache(modelParam.original); 218 | } 219 | } 220 | 221 | function transformLinkToAttrs(params) { 222 | let attributes = []; 223 | let dataAttributes = getDataAttributesFromParams(params); 224 | params = getNonDataAttributesFromParams(params); 225 | 226 | let firstParamInput = params[0]; 227 | let firstParamOutput; 228 | 229 | if (isQueryParam(firstParamInput)) { 230 | firstParamOutput = b.attr('@query', b.mustache(b.path('hash'), [], firstParamInput.hash)); 231 | } else if (firstParamInput.type === 'PathExpression') { 232 | firstParamOutput = b.attr('@route', b.mustache(firstParamInput.original)); 233 | } else if (firstParamInput.type === 'SubExpression') { 234 | firstParamOutput = b.attr( 235 | '@route', 236 | b.mustache(firstParamInput.path, firstParamInput.params, firstParamInput.hash) 237 | ); 238 | } else { 239 | firstParamOutput = b.attr('@route', b.text(firstParamInput.value)); 240 | } 241 | 242 | if (params.length === 1) { 243 | attributes = [firstParamOutput]; 244 | } else if (params.length === 2) { 245 | // @route and @model param 246 | 247 | // eslint-disable-next-line no-unused-vars 248 | let [_, secondParamInput] = params; 249 | if (secondParamInput.type === 'SubExpression') { 250 | let _queryParamOrModel; 251 | if (isQueryParam(secondParamInput)) { 252 | _queryParamOrModel = b.attr( 253 | '@query', 254 | b.mustache(b.path('hash'), [], secondParamInput.hash) 255 | ); 256 | } else { 257 | _queryParamOrModel = b.attr( 258 | '@model', 259 | b.mustache(secondParamInput.path, secondParamInput.params) 260 | ); 261 | } 262 | attributes = [firstParamOutput, _queryParamOrModel]; 263 | } else { 264 | let _modelParam = b.attr('@model', transformModelParams(secondParamInput)); 265 | attributes = [firstParamOutput, _modelParam]; 266 | } 267 | } else if (params.length > 2) { 268 | // @route and @models params 269 | // eslint-disable-next-line no-unused-vars 270 | let [_, ...models] = params; 271 | let hasQueryParamHelper = isQueryParam(models[models.length - 1]); 272 | let _modelsParam; 273 | let _qpParam; 274 | 275 | if (hasQueryParamHelper) { 276 | if (models.length < 3) { 277 | _modelsParam = b.attr('@model', transformModelParams(models[0])); 278 | } else { 279 | _modelsParam = b.attr( 280 | '@models', 281 | b.mustache(b.path('array'), models.slice(0, models.length - 1)) 282 | ); 283 | } 284 | _qpParam = b.attr('@query', b.mustache(b.path('hash'), [], models[models.length - 1].hash)); 285 | } else { 286 | _modelsParam = b.attr('@models', b.mustache(b.path('array'), models)); 287 | } 288 | attributes = [firstParamOutput, _modelsParam]; 289 | if (_qpParam) { 290 | attributes.push(_qpParam); 291 | } 292 | } 293 | 294 | return attributes.concat(dataAttributes); 295 | } 296 | 297 | function hasValuelessDataParams(params) { 298 | return getDataAttributesFromParams(params).length > 0; 299 | } 300 | 301 | /** 302 | * 303 | * data-* attributes are generally omitted, 304 | * but this config allows including nodes with data-test-* attributes. 305 | */ 306 | function shouldSkipDataTestParams(params, includeValuelessDataTestAttributes) { 307 | if (includeValuelessDataTestAttributes) { 308 | const dataAttrs = getDataAttributesFromParams(params); 309 | // This is true for nodes with data-* attributes too, 310 | // as long as there is one with data-test-* attribute. 311 | return !dataAttrs.some((attr) => attr.original.startsWith('data-test-')); 312 | } 313 | return true; 314 | } 315 | 316 | function transformNodeAttributes(tagName, node, config) { 317 | let attributes = transformAttrs(tagName, node.hash.pairs, node.params, config); 318 | return node.params.concat(attributes); 319 | } 320 | 321 | function isDataAttrPathExpression(node) { 322 | return node.type === 'PathExpression' && node.original.startsWith('data-'); 323 | } 324 | 325 | function getDataAttributesFromParams(params) { 326 | return params.filter((it) => isDataAttrPathExpression(it)); 327 | } 328 | 329 | function getNonDataAttributesFromParams(params) { 330 | return params.filter((it) => !isDataAttrPathExpression(it)); 331 | } 332 | 333 | function isKnownHelper(fullName, config, invokableData) { 334 | let { helpers, components } = invokableData; 335 | let isTelemetryData = !!(helpers || components); 336 | 337 | let name = fullName; 338 | // replace `::` with `/`, and ignore the path before $ 339 | if (isWallStreet(name)) { 340 | name = name.split('$').pop().replace('::', '/'); 341 | } 342 | 343 | if (isTelemetryData) { 344 | if (config.unambiguousHelpers) { 345 | let isComponent = 346 | !config.helpers.includes(name) && 347 | [...(components || []), ...BUILT_IN_COMPONENTS].includes(name); 348 | 349 | if (isComponent) { 350 | return false; 351 | } 352 | 353 | let mergedHelpers = [...KNOWN_HELPERS, ...(helpers || [])]; 354 | let isHelper = mergedHelpers.includes(name) || config.helpers.includes(name); 355 | let strName = `${name}`; // coerce boolean and number to string 356 | return isHelper && !strName.includes('.'); 357 | } else { 358 | let mergedHelpers = [...KNOWN_HELPERS, ...(helpers || [])]; 359 | let isHelper = mergedHelpers.includes(name) || config.helpers.includes(name); 360 | let isComponent = [...(components || []), ...BUILT_IN_COMPONENTS].includes(name); 361 | let strName = `${name}`; // coerce boolean and number to string 362 | return (isHelper || !isComponent) && !strName.includes('.'); 363 | } 364 | } else { 365 | return KNOWN_HELPERS.includes(name) || config.helpers.includes(name); 366 | } 367 | } 368 | 369 | function nodeHasPositionalParameters(node) { 370 | if (node.params.length > 0) { 371 | let firstParamType = node.params[0].type; 372 | 373 | if (['StringLiteral', 'NumberLiteral', 'SubExpression'].includes(firstParamType)) { 374 | return true; 375 | } else if (firstParamType === 'PathExpression') { 376 | if (!isAttribute(node.params[0].original)) { 377 | return true; 378 | } 379 | } 380 | } 381 | 382 | return false; 383 | } 384 | 385 | function transformComponentNode(node, fileInfo, config) { 386 | if ( 387 | hasValuelessDataParams(node.params) && 388 | shouldSkipDataTestParams(node.params, config.includeValuelessDataTestAttributes) 389 | ) { 390 | return; 391 | } 392 | let selfClosing = node.type !== 'BlockStatement'; 393 | const tagName = node.path.original; 394 | 395 | if (config.skipBuiltInComponents && isBuiltInComponent(tagName)) { 396 | return; 397 | } 398 | 399 | if (node.inverse) { 400 | return; 401 | } 402 | 403 | const newTagName = transformTagName(tagName, inAttr); 404 | 405 | let attributes; 406 | let children = node.program ? node.program.body : undefined; 407 | let blockParams = node.program ? node.program.blockParams : undefined; 408 | 409 | if (tagName === 'link-to') { 410 | selfClosing = false; 411 | 412 | if (node.type === 'MustacheStatement') { 413 | let params = node.params.slice(); 414 | let textParam = params.shift(); //the first param becomes the block content 415 | 416 | attributes = transformLinkToAttrs(params); 417 | children = [transformLinkToTextParam(textParam)]; 418 | } else { 419 | attributes = transformLinkToAttrs(node.params); 420 | } 421 | 422 | let namesParams = transformAttrs(tagName, node.hash.pairs, node.params, config); 423 | attributes = attributes.concat(namesParams); 424 | } else { 425 | if (nodeHasPositionalParameters(node)) { 426 | logger.warn( 427 | `WARNING: {{${node.path.original}}} was not converted as it has positional parameters which can't be automatically converted. Source: ${fileInfo.path}` 428 | ); 429 | return; 430 | } 431 | 432 | if (inAttr) { 433 | return; 434 | } 435 | attributes = transformNodeAttributes(tagName, node, config); 436 | } 437 | return b.element( 438 | { name: newTagName, selfClosing }, 439 | { 440 | attrs: attributes, 441 | children, 442 | blockParams, 443 | } 444 | ); 445 | } 446 | 447 | function transformHelperNode(node) { 448 | return b.mustache(b.sexpr(node.path, node.params, node.hash)); 449 | } 450 | 451 | function subExpressionToMustacheStatement(subExpression) { 452 | return b.mustache(subExpression.path, subExpression.params, subExpression.hash); 453 | } 454 | 455 | module.exports = function transform(fileInfo, config, invokableData = {}) { 456 | config = config || {}; 457 | config.helpers = config.helpers || []; 458 | config.skipBuiltInComponents = 459 | 'skipBuiltInComponents' in config ? config.skipBuiltInComponents : false; 460 | config.skipFilesThatMatchRegex = config.skipFilesThatMatchRegex || null; 461 | 462 | if (shouldSkipFile(fileInfo, config)) { 463 | return fileInfo.source; 464 | } 465 | 466 | let { code: toAngleBracket } = recast.transform(fileInfo.source, () => 467 | transformToAngleBracket(fileInfo, config, invokableData) 468 | ); 469 | 470 | let attrEqualEmptyString = new RegExp(_EMPTY_STRING_, 'gi'); 471 | let dataEqualsNoValue = /(data-\S+)=""/gim; 472 | 473 | toAngleBracket = toAngleBracket.replace(attrEqualEmptyString, ''); 474 | toAngleBracket = toAngleBracket.replace(dataEqualsNoValue, '$1'); 475 | return toAngleBracket; 476 | }; 477 | 478 | function transformToAngleBracket(fileInfo, config, invokableData) { 479 | /** 480 | * Transform the attributes names & values properly 481 | */ 482 | return { 483 | MustacheStatement(node, walkerPath) { 484 | const tagName = `${node.path && node.path.original}`; 485 | 486 | if (config.components && !config.components.includes(tagName)) return; 487 | 488 | const isTagKnownHelper = isKnownHelper(tagName, config, invokableData); 489 | 490 | // Don't change attribute statements 491 | const isValidMustacheComponent = node.loc.source !== '(synthetic)' && !isTagKnownHelper; 492 | const isNestedComponent = isNestedComponentTagName(tagName); 493 | 494 | if ( 495 | isValidMustacheComponent && 496 | (node.hash.pairs.length > 0 || node.params.length > 0 || isNestedComponent) 497 | ) { 498 | return transformComponentNode(node, fileInfo, config); 499 | } else if ( 500 | config.unambiguousHelpers && 501 | isTagKnownHelper && 502 | node.path.type !== 'SubExpression' && 503 | walkerPath.parent.node.type !== 'AttrNode' && 504 | walkerPath.parent.node.type !== 'ConcatStatement' 505 | ) { 506 | return transformHelperNode(node, walkerPath); 507 | } 508 | }, 509 | BlockStatement(node) { 510 | let tagName = `${node.path.original}`; 511 | 512 | if (config.components && !config.components.includes(tagName)) return; 513 | 514 | if (!isKnownHelper(node.path.original, config, invokableData) || isWallStreet(tagName)) { 515 | return transformComponentNode(node, fileInfo, config); 516 | } 517 | }, 518 | AttrNode: { 519 | enter() { 520 | inAttr = true; 521 | }, 522 | exit() { 523 | inAttr = false; 524 | }, 525 | }, 526 | }; 527 | } 528 | -------------------------------------------------------------------------------- /transforms/angle-brackets/transform.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const transform = require('./transform'); 4 | const invokableData = require('./telemetry/mock-invokables'); 5 | const { getInvokableData } = require('./telemetry/invokable'); 6 | 7 | function runTest(path, source, options) { 8 | return transform({ path, source }, options, getInvokableData(invokableData)); 9 | } 10 | function runTestWithData(path, source, options, data) { 11 | return transform({ path, source }, options, data); 12 | } 13 | 14 | test('action-params', () => { 15 | let input = ` 16 | {{#bs-button onClick=(action "submit")}} 17 | Button 18 | {{/bs-button}} 19 | `; 20 | 21 | expect(runTest('action-params.hbs', input)).toMatchInlineSnapshot(` 22 | " 23 | 24 | Button 25 | 26 | " 27 | `); 28 | }); 29 | 30 | test('actions', () => { 31 | let input = ` 32 | {{#bs-button-group 33 | value=buttonGroupValue 34 | type="checkbox" 35 | onChange=(action (mut buttonGroupValue)) as |bg| 36 | }} 37 | {{#bg.button value=1}}1{{/bg.button}} 38 | {{#bg.button value=2}}2{{/bg.button}} 39 | {{#bg.button value=3}}3{{/bg.button}} 40 | {{/bs-button-group}} 41 | `; 42 | 43 | /** 44 | * NOTE: An issue has been opened in `ember-template-recast` (https://github.com/ember-template-lint/ember-template-recast/issues/82) 45 | * regarding to create an API to allow a transform to customize the whitespace for newly created nodes. 46 | * 47 | */ 48 | expect(runTest('actions.hbs', input)).toMatchInlineSnapshot(` 49 | " 50 | 51 | 1 52 | 2 53 | 3 54 | 55 | " 56 | `); 57 | }); 58 | 59 | test('boolean-values', () => { 60 | let input = ` 61 | {{my-component prop1=true prop2=false}} 62 | `; 63 | 64 | expect(runTest('true-values.hbs', input)).toMatchInlineSnapshot(` 65 | " 66 | 67 | " 68 | `); 69 | }); 70 | 71 | test('curly', () => { 72 | let input = ` 73 |
{{foo}}
74 |
{{{bar}}}
75 | `; 76 | 77 | expect(runTest('curly.hbs', input)).toMatchInlineSnapshot(` 78 | " 79 |
{{foo}}
80 |
{{{bar}}}
81 | " 82 | `); 83 | }); 84 | 85 | test('data-attributes', () => { 86 | let input = ` 87 | {{x-foo data-foo=true}} 88 | {{x-foo data-test-selector=true}} 89 | {{x-foo data-test-selector=post.id}} 90 | {{x-foo label="hi" data-test-selector=true}} 91 | {{x-foo data-test-foo }} 92 | 93 | {{#x-foo data-foo=true}} 94 | block 95 | {{/x-foo}} 96 | 97 | {{#x-foo data-test-selector=true}} 98 | block 99 | {{/x-foo}} 100 | 101 | {{#x-foo data-test-selector=post.id}} 102 | block 103 | {{/x-foo}} 104 | 105 | {{#common/accordion-component data-test-accordion as |accordion|}} 106 | block 107 | {{/common/accordion-component}} 108 | 109 | {{x-foo 110 | data-foo 111 | name="Sophie" 112 | }} 113 | `; 114 | 115 | expect(runTest('data-attributes.hbs', input)).toMatchInlineSnapshot(` 116 | " 117 | 118 | 119 | 120 | 121 | {{x-foo data-test-foo }} 122 | 123 | 124 | block 125 | 126 | 127 | 128 | block 129 | 130 | 131 | 132 | block 133 | 134 | 135 | {{#common/accordion-component data-test-accordion as |accordion|}} 136 | block 137 | {{/common/accordion-component}} 138 | 139 | {{x-foo 140 | data-foo 141 | name=\\"Sophie\\" 142 | }} 143 | " 144 | `); 145 | }); 146 | 147 | test('data-test-attributes', () => { 148 | let options = { 149 | includeValuelessDataTestAttributes: true, 150 | }; 151 | let input = ` 152 | {{x-foo data-foo=true}} 153 | {{x-foo data-test-selector=true}} 154 | {{x-foo data-test-selector=post.id}} 155 | {{x-foo label="hi" data-test-selector=true}} 156 | {{x-foo data-test-foo }} 157 | {{#x-foo data-foo=true}} 158 | block 159 | {{/x-foo}} 160 | {{#x-foo data-test-selector=true}} 161 | block 162 | {{/x-foo}} 163 | {{#x-foo data-test-selector=post.id}} 164 | block 165 | {{/x-foo}} 166 | {{#common/accordion-component data-test-accordion as |accordion|}} 167 | block 168 | {{/common/accordion-component}} 169 | {{#link-to data-test-foo "posts"}} 170 | Recent Posts 171 | {{/link-to}} 172 | {{#link-to data-test-foo this.dynamicPath (query-params direction="desc" showArchived=false)}} 173 | Recent Posts 174 | {{/link-to}} 175 | {{#link-to data-test-foo data-foo this.dynamicPath (query-params direction="desc" showArchived=false)}} 176 | Recent Posts 177 | {{/link-to}} 178 | 179 | {{x-foo 180 | data-foo 181 | name="Sophie" 182 | }} 183 | {{#x-foo data-foo}} 184 | block 185 | {{/x-foo}} 186 | {{#common/accordion-component data-accordion as |accordion|}} 187 | block 188 | {{/common/accordion-component}} 189 | {{#link-to data-foo "posts"}} 190 | Recent Posts 191 | {{/link-to}} 192 | {{#link-to data-foo this.dynamicPath (query-params direction="desc" showArchived=false)}} 193 | Recent Posts 194 | {{/link-to}} 195 | `; 196 | 197 | expect(runTest('data-test-attributes.hbs', input, options)).toMatchInlineSnapshot(` 198 | " 199 | 200 | 201 | 202 | 203 | 204 | 205 | block 206 | 207 | 208 | block 209 | 210 | 211 | block 212 | 213 | 214 | block 215 | 216 | 217 | Recent Posts 218 | 219 | 220 | Recent Posts 221 | 222 | 223 | Recent Posts 224 | 225 | 226 | {{x-foo 227 | data-foo 228 | name=\\"Sophie\\" 229 | }} 230 | {{#x-foo data-foo}} 231 | block 232 | {{/x-foo}} 233 | {{#common/accordion-component data-accordion as |accordion|}} 234 | block 235 | {{/common/accordion-component}} 236 | {{#link-to data-foo \\"posts\\"}} 237 | Recent Posts 238 | {{/link-to}} 239 | {{#link-to data-foo this.dynamicPath (query-params direction=\\"desc\\" showArchived=false)}} 240 | Recent Posts 241 | {{/link-to}} 242 | " 243 | `); 244 | }); 245 | 246 | test('data-test-empty-attributes', () => { 247 | let options = { 248 | includeValuelessDataTestAttributes: true, 249 | }; 250 | let input = ` 251 | {{x-foo data-test-foo }} 252 | `; 253 | 254 | expect(runTest('data-test-empty-attributes.hbs', input, options)).toMatchInlineSnapshot(` 255 | " 256 | 257 | " 258 | `); 259 | }); 260 | 261 | test('deeply-nested-sub', () => { 262 | let input = ` 263 | {{#some-component class=(concat foo (some-helper ted (some-dude bar (a b c)))) }} 264 | help 265 | {{/some-component}} 266 | {{some-component class=(concat foo (some-helper ted (some-dude bar (a b c)))) }} 267 | {{deep-component class=(concat foo (nice-helper ted (some-crazy bar (a d (d e f)))))}} 268 | {{some-component 269 | class=(concat foo (some-helper bar)) 270 | }} 271 | {{some-component 272 | class=(concat foo (some-helper bar quuz)) 273 | }} 274 | {{some-component 275 | person=(hash name="Sophie" age=1) 276 | message=(t "welcome" count=1) 277 | }} 278 | {{some-component 279 | people=(array 280 | (hash 281 | name="Alex" 282 | age=5 283 | nested=(hash oldest=true amount=(format-currency 350 sign="£")) 284 | disabled=(eq foo "bar") 285 | ) 286 | (hash name="Ben" age=4) 287 | (hash name="Sophie" age=1) 288 | ) 289 | }} 290 | `; 291 | /** 292 | * NOTE: An issue has been opened in `ember-template-recast` (https://github.com/ember-template-lint/ember-template-recast/issues/82) 293 | * regarding to create an API to allow a transform to customize the whitespace for newly created nodes. 294 | * 295 | */ 296 | expect(runTest('deeply-nested-sub.hbs', input)).toMatchInlineSnapshot(` 297 | " 298 | 299 | help 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | " 308 | `); 309 | }); 310 | 311 | test('each-in', () => { 312 | let input = ` 313 | {{#each-in this.people as |name person|}} 314 | Hello, {{name}}! You are {{person.age}} years old. 315 | {{else}} 316 | Sorry, nobody is here. 317 | {{/each-in}} 318 | `; 319 | 320 | expect(runTest('each-in.hbs', input)).toMatchInlineSnapshot(` 321 | " 322 | {{#each-in this.people as |name person|}} 323 | Hello, {{name}}! You are {{person.age}} years old. 324 | {{else}} 325 | Sorry, nobody is here. 326 | {{/each-in}} 327 | " 328 | `); 329 | }); 330 | 331 | test('entities', () => { 332 | let input = ` 333 | < > × 334 | {{#foo data-a=""Foo & Bar""}} Some text >{{/foo}} 335 | `; 336 | 337 | expect(runTest('entities.hbs', input)).toMatchInlineSnapshot(` 338 | " 339 | < > × 340 |  Some text > 341 | " 342 | `); 343 | }); 344 | 345 | test('html-tags', () => { 346 | let input = ` 347 | 352 | 353 | 357 | 358 | 362 | 363 |
364 | 365 |
366 |
367 |
368 | `; 369 | 370 | expect(runTest('html-tags.hbs', input)).toMatchInlineSnapshot(` 371 | " 372 | 377 | 378 | 382 | 383 | 387 | 388 |
389 | 390 |
391 |
392 |
393 | " 394 | `); 395 | }); 396 | 397 | test('if', () => { 398 | let input = ` 399 | {{#if (eq a b)}} 400 | {{my-component1 prop1="hello"}} 401 | {{else}} 402 | {{my-component2 prop2="world"}} 403 | {{/if}} 404 | `; 405 | 406 | expect(runTest('if.hbs', input)).toMatchInlineSnapshot(` 407 | " 408 | {{#if (eq a b)}} 409 | 410 | {{else}} 411 | 412 | {{/if}} 413 | " 414 | `); 415 | }); 416 | 417 | test('nested-else-if', () => { 418 | let input = ` 419 | {{#if a}} 420 | {{my-component1 foo="bar"}} 421 | {{else if b}} 422 | {{my-component2 foo="bar"}} 423 | {{else if c}} 424 | {{my-component3 foo="bar"}} 425 | {{else if d}} 426 | {{my-component4 foo="bar"}} 427 | {{else}} 428 | {{my-component5 foo="bar"}} 429 | {{/if}} 430 | `; 431 | 432 | expect(runTest('if.hbs', input)).toMatchInlineSnapshot(` 433 | " 434 | {{#if a}} 435 | 436 | {{else if b}} 437 | 438 | {{else if c}} 439 | 440 | {{else if d}} 441 | 442 | {{else}} 443 | 444 | {{/if}} 445 | " 446 | `); 447 | }); 448 | 449 | test('input-helper', () => { 450 | let input = ` 451 | {{input type="checkbox" name="email-opt-in" checked=this.model.emailPreference}} 452 | `; 453 | 454 | expect(runTest('input-helper.hbs', input)).toMatchInlineSnapshot(` 455 | " 456 | 457 | " 458 | `); 459 | }); 460 | 461 | test('let', () => { 462 | let input = ` 463 | {{#let (capitalize this.person.firstName) (capitalize this.person.lastName) 464 | as |firstName lastName| 465 | }} 466 | Welcome back {{concat firstName ' ' lastName}} 467 | 468 | Account Details: 469 | First Name: {{firstName}} 470 | Last Name: {{lastName}} 471 | {{/let}} 472 | `; 473 | 474 | expect(runTest('let.hbs', input)).toMatchInlineSnapshot(` 475 | " 476 | {{#let (capitalize this.person.firstName) (capitalize this.person.lastName) 477 | as |firstName lastName| 478 | }} 479 | Welcome back {{concat firstName ' ' lastName}} 480 | 481 | Account Details: 482 | First Name: {{firstName}} 483 | Last Name: {{lastName}} 484 | {{/let}} 485 | " 486 | `); 487 | }); 488 | 489 | test('link-to', () => { 490 | let input = ` 491 | {{#link-to "about"}}About Us{{/link-to}} 492 | {{#link-to "data-access"}}Accessing the Crates.io Data{{/link-to}} 493 | {{#link-to this.dynamicRoute}}About Us{{/link-to}} 494 | {{#link-to "user" this.first this.second}}Show{{/link-to}} 495 | {{#link-to "user" this.first this.second (query-params foo="baz")}}Show{{/link-to}} 496 | {{#link-to "user" this.first}}Show{{/link-to}} 497 | {{#link-to "user" this.first (query-params foo="baz")}}Show{{/link-to}} 498 | `; 499 | 500 | expect(runTest('link-to.hbs', input)).toMatchInlineSnapshot(` 501 | " 502 | About Us 503 | Accessing the Crates.io Data 504 | About Us 505 | Show 506 | Show 507 | Show 508 | Show 509 | " 510 | `); 511 | }); 512 | 513 | test('link-to-inline', () => { 514 | let input = ` 515 | {{link-to 'Title' 'some.route'}} 516 | {{link-to 517 | 'Segments' 518 | 'apps.segments' 519 | class='tabs__discrete-tab' 520 | activeClass='o__selected' 521 | current-when='apps.segments' 522 | data-test-segment-link='segments' 523 | }} 524 | {{link-to 525 | 'Segments' 526 | this.dynamicPath 527 | class='tabs__discrete-tab' 528 | activeClass='o__selected' 529 | current-when='apps.segments' 530 | data-test-segment-link='segments' 531 | }} 532 | {{link-to 533 | segment.name 534 | 'apps.app.companies.segments.segment' 535 | segment 536 | class="t__em-link" 537 | }} 538 | {{link-to (t "show") "flight" event.flight.id class="btn btn-default btn-sm pull-right"}} 539 | {{link-to (t "show") "user" (if linkActor event.actor.id event.user.id)}} 540 | {{link-to "Show" "user" this.first this.second}} 541 | {{link-to "Show" "user" this.first this.second (query-params foo="baz")}} 542 | {{link-to "Show" "user" this.first}} 543 | {{link-to "Show" "user" this.first (query-params foo="baz")}} 544 | `; 545 | 546 | /** 547 | * NOTE: An issue has been opened in `ember-template-recast` (https://github.com/ember-template-lint/ember-template-recast/issues/82) 548 | * regarding to create an API to allow a transform to customize the whitespace for newly created nodes. 549 | * 550 | */ 551 | expect(runTest('link-to-inline.hbs', input)).toMatchInlineSnapshot(` 552 | " 553 | Title 554 | Segments 555 | Segments 556 | {{segment.name}} 557 | {{t \\"show\\"}} 558 | {{t \\"show\\"}} 559 | Show 560 | Show 561 | Show 562 | Show 563 | " 564 | `); 565 | }); 566 | 567 | test('link-to-model', () => { 568 | let input = ` 569 | {{#link-to "post" post}}Read {{post.title}}...{{/link-to}} 570 | {{#link-to "post" "string-id"}}Read {{post.title}}...{{/link-to}} 571 | {{#link-to "post" 557}}Read {{post.title}}...{{/link-to}} 572 | `; 573 | /** 574 | * NOTE: An issue has been opened in `ember-template-recast` (https://github.com/ember-template-lint/ember-template-recast/issues/82) 575 | * regarding to create an API to allow a transform to customize the whitespace for newly created nodes. 576 | * 577 | */ 578 | expect(runTest('link-to-model.hbs', input)).toMatchInlineSnapshot(` 579 | " 580 | Read {{post.title}}... 581 | Read {{post.title}}... 582 | Read {{post.title}}... 583 | " 584 | `); 585 | }); 586 | 587 | test('link-to-model-array', () => { 588 | let input = ` 589 | {{#link-to "post.comment" post comment}} 590 | Comment by {{comment.author.name}} on {{comment.date}} 591 | {{/link-to}} 592 | {{#link-to this.dynamicPath post comment}} 593 | Comment by {{comment.author.name}} on {{comment.date}} 594 | {{/link-to}} 595 | `; 596 | 597 | expect(runTest('link-to-model-array.hbs', input)).toMatchInlineSnapshot(` 598 | " 599 | 600 | Comment by {{comment.author.name}} on {{comment.date}} 601 | 602 | 603 | Comment by {{comment.author.name}} on {{comment.date}} 604 | 605 | " 606 | `); 607 | }); 608 | 609 | test('link-to-query-param', () => { 610 | let input = ` 611 | {{#link-to "posts" (query-params direction="desc" showArchived=false)}} 612 | Recent Posts 613 | {{/link-to}} 614 | {{#link-to data-test-foo "posts"}} 615 | Recent Posts 616 | {{/link-to}} 617 | {{#link-to this.dynamicPath (query-params direction="desc" showArchived=false)}} 618 | Recent Posts 619 | {{/link-to}} 620 | {{#link-to data-test-foo this.dynamicPath (query-params direction="desc" showArchived=false)}} 621 | Recent Posts 622 | {{/link-to}} 623 | {{#link-to (query-params direction="desc" showArchived=false)}} 624 | Recent Posts 625 | {{/link-to}} 626 | {{link-to 627 | 'Users' 628 | 'apps.app.users.segments.segment' 629 | 'all-users' 630 | (query-params searchTerm=searchTerm) 631 | }} 632 | {{#link-to (concat parentName ".index") 633 | model.project.id 634 | model.projectVersion.compactVersion 635 | model.name 636 | (query-params anchor=undefined) 637 | class="tabbed-layout__menu__item" 638 | activeClass="tabbed-layout__menu__item_selected" 639 | current-when=(concat parentName ".index") 640 | data-test-tab="index" 641 | }} 642 | Events 643 | {{/link-to}} 644 | {{#link-to (concat parentName ".index") 645 | model.project.id 646 | model.projectVersion.compactVersion 647 | model.name 648 | model.description 649 | (query-params anchor=undefined) 650 | class="tabbed-layout__menu__item" 651 | activeClass="tabbed-layout__menu__item_selected" 652 | current-when=(concat parentName ".index") 653 | data-test-tab="index" 654 | }} 655 | Events 656 | {{/link-to}} 657 | `; 658 | /** 659 | * NOTE: An issue has been opened in `ember-template-recast` (https://github.com/ember-template-lint/ember-template-recast/issues/82) 660 | * regarding to create an API to allow a transform to customize the whitespace for newly created nodes. 661 | * 662 | */ 663 | expect(runTest('link-to-query-param.hbs', input)).toMatchInlineSnapshot(` 664 | " 665 | 666 | Recent Posts 667 | 668 | {{#link-to data-test-foo \\"posts\\"}} 669 | Recent Posts 670 | {{/link-to}} 671 | 672 | Recent Posts 673 | 674 | {{#link-to data-test-foo this.dynamicPath (query-params direction=\\"desc\\" showArchived=false)}} 675 | Recent Posts 676 | {{/link-to}} 677 | 678 | Recent Posts 679 | 680 | Users 681 | 682 | Events 683 | 684 | 685 | Events 686 | 687 | " 688 | `); 689 | }); 690 | 691 | test('nested', () => { 692 | let input = ` 693 | {{ui/site-header user=this.user class=(if this.user.isAdmin "admin")}} 694 | {{ui/button text="Click me"}} 695 | {{#some-path/another-path/super-select selected=this.user.country as |s|}} 696 | {{#each this.availableCountries as |country|}} 697 | {{#s.option value=country}}{{country.name}}{{/s.option}} 698 | {{/each}} 699 | {{/some-path/another-path/super-select}} 700 | {{x-foo/x-bar}} 701 | `; 702 | 703 | expect(runTest('nested.hbs', input)).toMatchInlineSnapshot(` 704 | " 705 | 706 | 707 | 708 | {{#each this.availableCountries as |country|}} 709 | {{country.name}} 710 | {{/each}} 711 | 712 | 713 | " 714 | `); 715 | }); 716 | 717 | test('null-subexp', () => { 718 | let input = ` 719 | {{some-component selected=(is-equal this.bar null)}} 720 | `; 721 | 722 | expect(runTest('null-subexp.hbs', input)).toMatchInlineSnapshot(` 723 | " 724 | 725 | " 726 | `); 727 | }); 728 | 729 | test('positional-params', () => { 730 | let input = ` 731 | {{some-component "foo"}} 732 | {{#some-component "foo"}} 733 | hi 734 | {{/some-component}} 735 | {{#some-component foo}} 736 | hi 737 | {{/some-component}} 738 | {{some-component 123}} 739 | {{some-component (some-helper 987)}} 740 | `; 741 | 742 | expect(runTest('positional-params.hbs', input)).toMatchInlineSnapshot(` 743 | " 744 | {{some-component \\"foo\\"}} 745 | {{#some-component \\"foo\\"}} 746 | hi 747 | {{/some-component}} 748 | {{#some-component foo}} 749 | hi 750 | {{/some-component}} 751 | {{some-component 123}} 752 | {{some-component (some-helper 987)}} 753 | " 754 | `); 755 | }); 756 | 757 | test('sample', () => { 758 | let input = ` 759 | {{site-header user=this.user class=(if this.user.isAdmin "admin")}} 760 | {{site-header user=null address=undefined}} 761 | 762 | {{#super-select selected=this.user.country as |s|}} 763 | {{#each this.availableCountries as |country|}} 764 | {{#s.option value=country}}{{country.name}}{{/s.option}} 765 | {{/each}} 766 | {{/super-select}} 767 | 768 | {{foo/bar tagName=''}} 769 | {{foo tagName='div' a="" b=''}} 770 | `; 771 | 772 | expect(runTest('sample.hbs', input)).toMatchInlineSnapshot(` 773 | " 774 | 775 | 776 | 777 | 778 | {{#each this.availableCountries as |country|}} 779 | {{country.name}} 780 | {{/each}} 781 | 782 | 783 | 784 | 785 | " 786 | `); 787 | }); 788 | 789 | test('sample2', () => { 790 | let input = ` 791 | {{#my-card as |card|}} 792 | {{card.title title="My Card Title"}} 793 | {{#card.content}} 794 |

hello

795 | {{/card.content}} 796 | {{card.foo-bar}} 797 | {{card.foo}} 798 | {{/my-card}} 799 | `; 800 | 801 | expect(runTest('sample2.hbs', input)).toMatchInlineSnapshot(` 802 | " 803 | 804 | 805 | 806 |

hello

807 |
808 | 809 | {{card.foo}} 810 |
811 | " 812 | `); 813 | }); 814 | 815 | test('splattributes', () => { 816 | let input = ` 817 | {{#wrapper}} 818 |
819 | {{foo bar="baz"}} 820 |
821 | {{/wrapper}} 822 | `; 823 | 824 | expect(runTest('splattributes.hbs', input)).toMatchInlineSnapshot(` 825 | " 826 | 827 |
828 | 829 |
830 |
831 | " 832 | `); 833 | }); 834 | 835 | test('t-helper', () => { 836 | let input = ` 837 | {{t "some.string" param="string" another=1}} 838 | `; 839 | 840 | expect(runTest('t-helper.hbs', input)).toMatchInlineSnapshot(` 841 | " 842 | {{t \\"some.string\\" param=\\"string\\" another=1}} 843 | " 844 | `); 845 | }); 846 | 847 | test('tag-name', () => { 848 | let input = ` 849 | {{foo/bar name=""}} 850 | `; 851 | 852 | expect(runTest('tag-name.hbs', input)).toMatchInlineSnapshot(` 853 | " 854 | 855 | " 856 | `); 857 | }); 858 | 859 | test('textarea', () => { 860 | let input = ` 861 | {{textarea value=this.model.body}} 862 | `; 863 | 864 | expect(runTest('textarea.hbs', input)).toMatchInlineSnapshot(` 865 | " 866 |