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 |
684 |
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 |
867 | "
868 | `);
869 | });
870 |
871 | test('tilde', () => {
872 | let input = `
873 | {{#if foo~}}
874 | {{some-component}}
875 | {{/if}}
876 | `;
877 |
878 | expect(runTest('tilde.hbs', input)).toMatchInlineSnapshot(`
879 | "
880 | {{#if foo~}}
881 | {{some-component}}
882 | {{/if}}
883 | "
884 | `);
885 | });
886 |
887 | test('undefined-subexp', () => {
888 | let input = `
889 | {{some-component selected=(is-equal this.bar undefined)}}
890 | `;
891 |
892 | expect(runTest('undefined-subexp.hbs', input)).toMatchInlineSnapshot(`
893 | "
894 |
895 | "
896 | `);
897 | });
898 |
899 | test('unless', () => {
900 | let input = `
901 | {{#unless this.hasPaid}}
902 | You owe: \${{this.total}}
903 | {{/unless}}
904 | `;
905 |
906 | expect(runTest('unless.hbs', input)).toMatchInlineSnapshot(`
907 | "
908 | {{#unless this.hasPaid}}
909 | You owe: \${{this.total}}
910 | {{/unless}}
911 | "
912 | `);
913 | });
914 |
915 | test('whitespace-control', () => {
916 | let input = `
917 |
918 | {{~both~}}
919 |
920 |
921 | {{~left}}
922 |
923 |
924 | {{right~}}
925 |
926 | `;
927 |
928 | expect(runTest('whitespace-control.hbs', input)).toMatchInlineSnapshot(`
929 | "
930 |
931 | {{~both~}}
932 |
933 |
934 | {{~left}}
935 |
936 |
937 | {{right~}}
938 |
939 | "
940 | `);
941 | });
942 |
943 | test('skip-default-helpers', () => {
944 | let input = `
945 |
946 | {{liquid-outlet}}
947 |
948 |
950 |
953 | {{#liquid-if showOne class="nested-explode-transition-scenario"}}
954 |
955 | {{#liquid-if showA use="toLeft"}}
956 |
One: A
957 | {{else}}
958 |
One: B
959 | {{/liquid-if}}
960 |
961 | {{else}}
962 |
963 | Two
964 |
965 | {{/liquid-if}}
966 | {{moment '12-25-1995' 'MM-DD-YYYY'}}
967 | {{moment-from '1995-12-25' '2995-12-25' hideAffix=true}}
968 | {{some-component foo=true}}
969 | {{some-helper1 foo=true}}
970 | {{some-helper2 foo=true}}
971 | `;
972 |
973 | let options = {
974 | helpers: ['some-helper1', 'some-helper2', 'some-helper3'],
975 | };
976 |
977 | expect(runTest('skip-default-helpers.hbs', input, options)).toMatchInlineSnapshot(`
978 | "
979 |
980 | {{liquid-outlet}}
981 |
982 |
984 |
987 | {{#liquid-if showOne class=\\"nested-explode-transition-scenario\\"}}
988 |
989 | {{#liquid-if showA use=\\"toLeft\\"}}
990 |
One: A
991 | {{else}}
992 |
One: B
993 | {{/liquid-if}}
994 |
995 | {{else}}
996 |
997 | Two
998 |
999 | {{/liquid-if}}
1000 | {{moment '12-25-1995' 'MM-DD-YYYY'}}
1001 | {{moment-from '1995-12-25' '2995-12-25' hideAffix=true}}
1002 |
1003 | {{some-helper1 foo=true}}
1004 | {{some-helper2 foo=true}}
1005 | "
1006 | `);
1007 | });
1008 |
1009 | test('skip-default-helpers (no-config)', () => {
1010 | let input = `
1011 |
1012 | {{liquid-outlet}}
1013 |
1014 |
1016 |
1019 | {{#liquid-if showOne class="nested-explode-transition-scenario"}}
1020 |
1021 | {{#liquid-if showA use="toLeft"}}
1022 |
One: A
1023 | {{else}}
1024 |
One: B
1025 | {{/liquid-if}}
1026 |
1027 | {{else}}
1028 |
1029 | Two
1030 |
1031 | {{/liquid-if}}
1032 | {{moment '12-25-1995' 'MM-DD-YYYY'}}
1033 | {{moment-from '1995-12-25' '2995-12-25' hideAffix=true}}
1034 | {{some-component foo=true}}
1035 | {{some-helper1 foo=true}}
1036 | {{some-helper2 foo=true}}
1037 | `;
1038 |
1039 | expect(runTest('skip-default-helpers.hbs', input)).toMatchInlineSnapshot(`
1040 | "
1041 |
1042 | {{liquid-outlet}}
1043 |
1044 |
1046 |
1049 | {{#liquid-if showOne class=\\"nested-explode-transition-scenario\\"}}
1050 |
1051 | {{#liquid-if showA use=\\"toLeft\\"}}
1052 |
One: A
1053 | {{else}}
1054 |
One: B
1055 | {{/liquid-if}}
1056 |
1057 | {{else}}
1058 |
1059 | Two
1060 |
1061 | {{/liquid-if}}
1062 | {{moment '12-25-1995' 'MM-DD-YYYY'}}
1063 | {{moment-from '1995-12-25' '2995-12-25' hideAffix=true}}
1064 |
1065 |
1066 |
1067 | "
1068 | `);
1069 | });
1070 |
1071 | test('custom-options', () => {
1072 | let input = `
1073 | {{some-component foo=true}}
1074 | {{some-helper1 foo=true}}
1075 | {{some-helper2 foo=true}}
1076 | {{link-to "Title" "some.route"}}
1077 | {{textarea value=this.model.body}}
1078 | {{input type="checkbox" name="email-opt-in" checked=this.model.emailPreference}}
1079 | `;
1080 |
1081 | let options = {
1082 | helpers: ['some-helper1', 'some-helper2', 'some-helper3'],
1083 | skipBuiltInComponents: true,
1084 | };
1085 |
1086 | expect(runTest('custom-options.hbs', input, options)).toMatchInlineSnapshot(`
1087 | "
1088 |
1089 | {{some-helper1 foo=true}}
1090 | {{some-helper2 foo=true}}
1091 | {{link-to \\"Title\\" \\"some.route\\"}}
1092 | {{textarea value=this.model.body}}
1093 | {{input type=\\"checkbox\\" name=\\"email-opt-in\\" checked=this.model.emailPreference}}
1094 | "
1095 | `);
1096 | });
1097 |
1098 | test('specific-components', () => {
1099 | let input = `
1100 | {{some-component foo=true}}
1101 | {{my-component foo=true}}
1102 | {{x-foo foo=true}}
1103 | {{#my-card as |card|}}
1104 | {{card.title title="My Card Title"}}
1105 | {{/my-card}}
1106 | `;
1107 |
1108 | let options = {
1109 | components: ['some-component', 'x-foo', 'my-card'],
1110 | };
1111 |
1112 | expect(runTest('specific-components.hbs', input, options)).toMatchInlineSnapshot(`
1113 | "
1114 |
1115 | {{my-component foo=true}}
1116 |
1117 |
1118 | {{card.title title=\\"My Card Title\\"}}
1119 |
1120 | "
1121 | `);
1122 | });
1123 |
1124 | test('skip-attributes', () => {
1125 | let input = `
1126 | {{some-component data-test-foo=true aria-label="bar" foo=true}}
1127 | `;
1128 |
1129 | let options = {
1130 | skipAttributesThatMatchRegex: ['/data-/gim', '/aria-/gim'],
1131 | };
1132 |
1133 | expect(runTest('skip-attributes.hbs', input, options)).toMatchInlineSnapshot(`
1134 | "
1135 |
1136 | "
1137 | `);
1138 | });
1139 |
1140 | test('skip-attributes with invalid regex', () => {
1141 | let input = `
1142 | {{some-component data-test-foo=true aria-label="bar" foo=true}}
1143 | `;
1144 |
1145 | let options = {
1146 | skipAttributesThatMatchRegex: [null],
1147 | };
1148 |
1149 | expect(runTest('skip-attributes-invalid-regex.hbs', input, options)).toMatchInlineSnapshot(`
1150 | "
1151 |
1152 | "
1153 | `);
1154 | });
1155 |
1156 | test('regex-options', () => {
1157 | let input = `
1158 | {{some-component foo=true}}
1159 | `;
1160 |
1161 | let options = {
1162 | skipFilesThatMatchRegex: /[A-F]oo|[A-Z]ar/gim,
1163 | };
1164 |
1165 | expect(runTest('regex-options.hbs', input, options)).toMatchInlineSnapshot(`
1166 | "
1167 | {{some-component foo=true}}
1168 | "
1169 | `);
1170 | });
1171 |
1172 | test('preserve arguments', () => {
1173 | let input = `
1174 | {{foo-bar class="baz"}}
1175 | {{foo-bar data-baz class="baz"}}
1176 | {{link-to (t "show") "flight" event.flight.id class="btn btn-default btn-sm pull-right"}}
1177 | `;
1178 |
1179 | expect(runTest('preserve-arguments.hbs', input)).toMatchInlineSnapshot(`
1180 | "
1181 |
1182 | {{foo-bar data-baz class=\\"baz\\"}}
1183 | {{t \\"show\\"}}
1184 | "
1185 | `);
1186 | });
1187 |
1188 | test('handles link-to concat', () => {
1189 | let input = `
1190 | {{#link-to (concat someVariable ".detail") class="some-class"}}click{{/link-to}}
1191 | `;
1192 |
1193 | expect(runTest('handles-link-to-concat.hbs', input)).toMatchInlineSnapshot(`
1194 | "
1195 | click
1196 | "
1197 | `);
1198 | });
1199 |
1200 | test('handles link-to concat with hash', () => {
1201 | let input = `
1202 | {{#link-to (random-helper someVariable ".detail" do-the="thing") class="some-class"}}click{{/link-to}}
1203 | `;
1204 |
1205 | expect(runTest('handles-link-to-concat.hbs', input)).toMatchInlineSnapshot(`
1206 | "
1207 | click
1208 | "
1209 | `);
1210 | });
1211 |
1212 | test('component-else', () => {
1213 | let input = `
1214 | {{#foo bar="baz"}}
1215 | 42
1216 | {{else}}
1217 | 123
1218 | {{/foo}}
1219 | `;
1220 |
1221 | expect(runTest('component-else.hbs', input)).toMatchInlineSnapshot(`
1222 | "
1223 | {{#foo bar=\\"baz\\"}}
1224 | 42
1225 | {{else}}
1226 | 123
1227 | {{/foo}}
1228 | "
1229 | `);
1230 | });
1231 |
1232 | test('hyphens with nested usage', () => {
1233 | let input = `
1234 | {{shared/documents-modal/-email-client}}
1235 | {{shared/-documents-modal/-email-client}}
1236 | {{-shared/-documents-modal/-email-client}}
1237 | {{-shared/documents-modal/-email-client}}
1238 | {{-shared/documents-modal/email-client}}
1239 | {{shared/-documents-modal/email-client}}
1240 | `;
1241 |
1242 | expect(runTest('hyphens-everywhere.hbs', input)).toMatchInlineSnapshot(`
1243 | "
1244 |
1245 |
1246 | <-Shared::-DocumentsModal::-EmailClient />
1247 | <-Shared::DocumentsModal::-EmailClient />
1248 | <-Shared::DocumentsModal::EmailClient />
1249 |
1250 | "
1251 | `);
1252 | });
1253 |
1254 | test('wallstreet', () => {
1255 | let input = `
1256 | {{#foo-bar$baz-bang/foo-bar/bang}}
1257 |
1258 | {{foo bar="baz"}}
1259 |
1260 | {{/foo-bar$baz-bang/foo-bar/bang}}
1261 | {{foo-bar$baz-bang/foo-bar/bang}}
1262 | `;
1263 |
1264 | expect(runTest('wallstreet.hbs', input)).toMatchInlineSnapshot(`
1265 | "
1266 |
1267 |
1268 |
1269 |
1270 |
1271 |
1272 | "
1273 | `);
1274 | });
1275 |
1276 | test('wallstreet-telemetry', () => {
1277 | let input = `
1278 | {{nested$helper}}
1279 | {{nested::helper}}
1280 | {{nested::helper param="yeah!"}}
1281 | {{helper-1}}
1282 | `;
1283 |
1284 | expect(runTest('wallstreet-telemetry.hbs', input)).toMatchInlineSnapshot(`
1285 | "
1286 | {{nested$helper}}
1287 | {{nested::helper}}
1288 | {{nested::helper param=\\"yeah!\\"}}
1289 | {{helper-1}}
1290 | "
1291 | `);
1292 | });
1293 |
1294 | test('wrapping-helpers-with-parens', () => {
1295 | let input = `
1296 | {{fooknownhelper}}
1297 | {{fooknownhelper}}
1298 | {{fooknownhelper data-test-foo foo="bar"}}
1299 | {{foounknownhelper}}
1300 | `;
1301 |
1302 | expect(runTest('wrapping-helpers-with-parens.hbs', input)).toMatchInlineSnapshot(`
1303 | "
1304 | {{fooknownhelper}}
1305 | {{fooknownhelper}}
1306 | {{fooknownhelper data-test-foo foo=\\"bar\\"}}
1307 | {{foounknownhelper}}
1308 | "
1309 | `);
1310 | });
1311 |
1312 | test('attr-space', () => {
1313 | let input = `
1314 |
1315 |
1316 |
1317 | `;
1318 |
1319 | expect(runTest('attr-space.hbs', input)).toMatchInlineSnapshot(`
1320 | "
1321 |
1322 |
1323 |
1324 | "
1325 | `);
1326 | });
1327 |
1328 | test('No telemetry', () => {
1329 | let input = `
1330 | {{#my-card as |card|}}
1331 | {{card.title title="My Card Title"}}
1332 | {{#card.content}}
1333 | hello
1334 | {{/card.content}}
1335 | {{card.foo-bar}}
1336 | {{card.foo}}
1337 | {{/my-card}}
1338 | `;
1339 |
1340 | expect(runTestWithData('no-telemetry.hbs', input, {}, {})).toMatchInlineSnapshot(`
1341 | "
1342 |
1343 |
1344 |
1345 | hello
1346 |
1347 |
1348 | {{card.foo}}
1349 |
1350 | "
1351 | `);
1352 | });
1353 |
1354 | test('pipe', () => {
1355 | let input = `"}} as |bar|>`;
1356 |
1357 | expect(runTestWithData('pipe.hbs', input, {}, {})).toMatchInlineSnapshot(
1358 | `"\\"}} as |bar|>"`
1359 | );
1360 | });
1361 |
1362 | test('outlet', () => {
1363 | let input = `{{outlet}}`;
1364 |
1365 | expect(runTestWithData('pipe.hbs', input, {}, {})).toMatchInlineSnapshot(`"{{outlet}}"`);
1366 | });
1367 |
1368 | test('yield', () => {
1369 | let input = `{{yield}}`;
1370 |
1371 | expect(runTestWithData('pipe.hbs', input, {}, {})).toMatchInlineSnapshot(`"{{yield}}"`);
1372 | });
1373 |
1374 | test('unknown helper with args', () => {
1375 | let input = `
1376 | {{api-reference component=this.currentComponent}}
1377 | {{api-reference someArg}}
1378 | {{api-reference}}
1379 | `;
1380 |
1381 | expect(runTestWithData('pipe.hbs', input, {}, {})).toMatchInlineSnapshot(`
1382 | "
1383 |
1384 | {{api-reference someArg}}
1385 | {{api-reference}}
1386 | "
1387 | `);
1388 | });
1389 |
1390 | test('unambiguousHelpers: true', () => {
1391 | let input = `
1392 | {{helper-1}}
1393 | {{nested/helper "some.string" param="string" another=1}}
1394 | `;
1395 |
1396 | expect(runTest('unambiguousHelpers: true', input, { unambiguousHelpers: true }))
1397 | .toMatchInlineSnapshot(`
1398 | "
1399 | {{(helper-1)}}
1400 | {{(nested/helper \\"some.string\\" param=\\"string\\" another=1)}}
1401 | "
1402 | `);
1403 | });
1404 |
--------------------------------------------------------------------------------