ember-codemod-remove-ember-css-modules 1.0.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -93,12 +93,34 @@ pnpm build
93
93
 
94
94
  `ember-css-modules` allows composing (really, inheriting) CSS code in two ways:
95
95
 
96
- - `{{local-class ... from="..."}}` in `*.hbs`
97
96
  - `composes: ... from '...';` in `*.css`
97
+ - `{{local-class ... from="..."}}` in `*.hbs`
98
+
99
+ The first syntax will continue to work in an Embroider app, provided that the import path is either `global` or a relative path.
100
+
101
+ ```css
102
+ /* app/components/ui/form/textarea.css */
103
+ .textarea {
104
+ composes: input from "./input.css";
105
+ }
106
+
107
+ .is-disabled {
108
+ composes: input-disabled from global;
109
+ }
110
+
111
+ /* These do not work */
112
+ .textarea {
113
+ composes: input from "my-app/components/ui/form/input.css";
114
+ }
115
+
116
+ .textarea {
117
+ composes: input from "my-v2-addon/components/ui/form/input.css";
118
+ }
119
+ ```
98
120
 
99
- Code inheritance can be signs of premature abstraction and incorrect component design. Ideally, you might duplicate code, then refactor it.
121
+ The second syntax isn't supported by the `{{local}}` helper from `embroider-css-modules`, so you will need to update the template and stylesheet accordingly.
100
122
 
101
- If short on time, you can import the relevant `styles` object in the backing class.
123
+ However, note that the presence of `composes` (in a set of related components) may indicate a premature abstraction and an incorrect component design. Ideally, you might duplicate the CSS, then refactor the components. If short on time, you can import the relevant `styles` object in the backing class.
102
124
 
103
125
  <details>
104
126
 
@@ -109,7 +131,7 @@ Case 1:
109
131
  ```hbs
110
132
  {{! app/components/ui/form/textarea.hbs }}
111
133
  <textarea
112
- class={{local-class "input" from="app/components/ui/form/input.css"}}
134
+ class={{local-class "input" from="my-app/components/ui/form/input.css"}}
113
135
  />
114
136
  ```
115
137
 
@@ -118,7 +140,7 @@ Case 2:
118
140
  ```css
119
141
  /* app/components/ui/form/textarea.css */
120
142
  .textarea {
121
- composes: input from "app/components/ui/form/input.css";
143
+ composes: input from "my-app/components/ui/form/input.css";
122
144
  }
123
145
  ```
124
146
 
@@ -1,7 +1,7 @@
1
1
  import Component from '@glimmer/component';
2
2
 
3
- <% if (options.componentStructure === 'nested') { %>import <%= options.__styles__ %> from './index.css';<% } else { %>import <%= options.__styles__ %> from './<%= entity.fileName %>.css';<% } %>
3
+ <% if (options.componentStructure === 'nested') { %>import styles from './index.css';<% } else { %>import styles from './<%= entity.fileName %>.css';<% } %>
4
4
 
5
5
  export default class <%= entity.classifiedName %>Component extends Component {
6
- <%= options.__styles__ %> = <%= options.__styles__ %>;
6
+ styles = styles;
7
7
  }
@@ -1,6 +1,6 @@
1
1
  import Component from '@glimmer/component';
2
2
 
3
- <% if (options.componentStructure === 'nested') { %>import <%= options.__styles__ %> from './index.css';<% } else { %>import <%= options.__styles__ %> from './<%= entity.fileName %>.css';<% } %>
3
+ <% if (options.componentStructure === 'nested') { %>import styles from './index.css';<% } else { %>import styles from './<%= entity.fileName %>.css';<% } %>
4
4
 
5
5
  interface <%= entity.classifiedName %>Signature {
6
6
  // The arguments accepted by the component
@@ -14,7 +14,7 @@ interface <%= entity.classifiedName %>Signature {
14
14
  }
15
15
 
16
16
  export default class <%= entity.classifiedName %>Component extends Component<<%= entity.classifiedName %>Signature> {
17
- <%= options.__styles__ %> = <%= options.__styles__ %>;
17
+ styles = styles;
18
18
  }
19
19
  <% if (options.project.hasGlint) { %>
20
20
  declare module '@glint/environment-ember-loose/registry' {
@@ -1,7 +1,7 @@
1
1
  import Controller from '@ember/controller';
2
2
 
3
- import <%= options.__styles__ %> from './<%= entity.fileName %>.css';
3
+ import styles from './<%= entity.fileName %>.css';
4
4
 
5
5
  export default class <%= entity.classifiedName %>Controller extends Controller {
6
- <%= options.__styles__ %> = <%= options.__styles__ %>;
6
+ styles = styles;
7
7
  }
@@ -1,7 +1,7 @@
1
1
  import Controller from '@ember/controller';
2
2
 
3
- import <%= options.__styles__ %> from './<%= entity.fileName %>.css';
3
+ import styles from './<%= entity.fileName %>.css';
4
4
 
5
5
  export default class <%= entity.classifiedName %>Controller extends Controller {
6
- <%= options.__styles__ %> = <%= options.__styles__ %>;
6
+ styles = styles;
7
7
  }
@@ -15,7 +15,6 @@ function analyzePackageJson(codemodOptions) {
15
15
  export function createOptions(codemodOptions) {
16
16
  const project = analyzePackageJson(codemodOptions);
17
17
  const options = {
18
- __styles__: 'styles',
19
18
  componentStructure: codemodOptions.componentStructure,
20
19
  project,
21
20
  projectRoot: codemodOptions.projectRoot,
@@ -15,7 +15,7 @@ function removeTemplateOnlyComponentMethod(file, data) {
15
15
  superClass.typeAnnotation = path.value.typeParameters;
16
16
  }
17
17
  return AST.builders.classExpression(null, AST.builders.classBody([
18
- AST.builders.classProperty(AST.builders.identifier(data.__styles__), AST.builders.identifier(data.__styles__)),
18
+ AST.builders.classProperty(AST.builders.identifier('styles'), AST.builders.identifier('styles')),
19
19
  ]), superClass);
20
20
  },
21
21
  visitImportDeclaration(path) {
@@ -54,9 +54,7 @@ function importStylesInClass(file, data) {
54
54
  const nodes = ast.program.body;
55
55
  // @ts-ignore: Assume that types from external packages are correct
56
56
  const index = lastImportDeclarationPath?.name ?? -1;
57
- nodes.splice(index + 1, 0, AST.builders.importDeclaration([
58
- AST.builders.importDefaultSpecifier(AST.builders.identifier(data.__styles__)),
59
- ], AST.builders.literal(`./${data.fileName}.css`)));
57
+ nodes.splice(index + 1, 0, AST.builders.importDeclaration([AST.builders.importDefaultSpecifier(AST.builders.identifier('styles'))], AST.builders.literal(`./${data.fileName}.css`)));
60
58
  return AST.print(ast);
61
59
  }
62
60
  function addStylesAsClassProperty(file, data) {
@@ -65,7 +63,7 @@ function addStylesAsClassProperty(file, data) {
65
63
  visitClassDeclaration(path) {
66
64
  const { body } = path.node.body;
67
65
  const nodesToAdd = [
68
- AST.builders.classProperty(AST.builders.identifier(data.__styles__), AST.builders.identifier(data.__styles__)),
66
+ AST.builders.classProperty(AST.builders.identifier('styles'), AST.builders.identifier('styles')),
69
67
  ];
70
68
  if (body.length > 0) {
71
69
  // @ts-ignore: Assume that types from external packages are correct
@@ -79,11 +77,10 @@ function addStylesAsClassProperty(file, data) {
79
77
  }
80
78
  export function updateClass(entityName, { customizations, options }) {
81
79
  const { getFilePath } = customizations;
82
- const { __styles__, projectRoot } = options;
80
+ const { projectRoot } = options;
83
81
  const filePath = getFilePath(entityName);
84
82
  const { ext, name } = parseFilePath(filePath);
85
83
  const data = {
86
- __styles__,
87
84
  fileName: name,
88
85
  isTypeScript: ext === '.ts',
89
86
  };
@@ -38,7 +38,7 @@ function sanitizeClassAndLocalClassAttributes(file) {
38
38
  });
39
39
  return AST.print(ast);
40
40
  }
41
- function mergeClassAndLocalClassAttributes(file, data) {
41
+ function mergeClassAndLocalClassAttributes(file) {
42
42
  const traverse = AST.traverse();
43
43
  const ast = traverse(file, {
44
44
  ElementNode(node) {
@@ -63,14 +63,14 @@ function mergeClassAndLocalClassAttributes(file, data) {
63
63
  let params;
64
64
  if (localClassNames.length === 1) {
65
65
  params = [
66
- AST.builders.path(`this.${data.__styles__}.${localClassNames[0]}`),
66
+ AST.builders.path(`this.styles.${localClassNames[0]}`),
67
67
  AST.builders.string(` ${classAttributeValue}`),
68
68
  ];
69
69
  }
70
70
  else {
71
71
  params = [
72
- AST.builders.sexpr('local-class', [
73
- AST.builders.path(`this.${data.__styles__}`),
72
+ AST.builders.sexpr('local', [
73
+ AST.builders.path('this.styles'),
74
74
  ...localClassNames.map(AST.builders.string),
75
75
  ]),
76
76
  AST.builders.string(` ${classAttributeValue}`),
@@ -83,7 +83,7 @@ function mergeClassAndLocalClassAttributes(file, data) {
83
83
  });
84
84
  return AST.print(ast);
85
85
  }
86
- function removeLocalClassHelpers(file, data) {
86
+ function removeLocalClassHelpers(file) {
87
87
  /*
88
88
  The {{local-class}} helper from ember-css-modules allows
89
89
  1 positional argument. The argument's value is presumed
@@ -131,11 +131,29 @@ function removeLocalClassHelpers(file, data) {
131
131
  }
132
132
  const localClassNames = param.value.trim().split(/\s+/);
133
133
  if (localClassNames.length === 1) {
134
- node.value = AST.builders.mustache(AST.builders.path(`this.${data.__styles__}.${localClassNames[0]}`));
134
+ node.value = AST.builders.mustache(AST.builders.path(`this.styles.${localClassNames[0]}`));
135
135
  return;
136
136
  }
137
- node.value = AST.builders.mustache(AST.builders.path('local-class'), [
138
- AST.builders.path(`this.${data.__styles__}`),
137
+ node.value = AST.builders.mustache(AST.builders.path('local'), [
138
+ AST.builders.path('this.styles'),
139
+ ...localClassNames.map(AST.builders.string),
140
+ ]);
141
+ },
142
+ MustacheStatement(node) {
143
+ if (node.path.type !== 'PathExpression' ||
144
+ node.path.original !== 'local-class') {
145
+ return;
146
+ }
147
+ const param = node.params[0];
148
+ if (param.type !== 'StringLiteral') {
149
+ return;
150
+ }
151
+ const localClassNames = param.value.trim().split(/\s+/);
152
+ if (localClassNames.length === 1) {
153
+ return AST.builders.mustache(AST.builders.path(`this.styles.${localClassNames[0]}`));
154
+ }
155
+ return AST.builders.mustache(AST.builders.path('local'), [
156
+ AST.builders.path('this.styles'),
139
157
  ...localClassNames.map(AST.builders.string),
140
158
  ]);
141
159
  },
@@ -154,17 +172,17 @@ function removeLocalClassHelpers(file, data) {
154
172
  }
155
173
  const localClassNames = param.value.trim().split(/\s+/);
156
174
  if (localClassNames.length === 1) {
157
- return AST.builders.path(`this.${data.__styles__}.${localClassNames[0]}`);
175
+ return AST.builders.path(`this.styles.${localClassNames[0]}`);
158
176
  }
159
- return AST.builders.sexpr(AST.builders.path('local-class'), [
160
- AST.builders.path(`this.${data.__styles__}`),
177
+ return AST.builders.sexpr(AST.builders.path('local'), [
178
+ AST.builders.path('this.styles'),
161
179
  ...localClassNames.map(AST.builders.string),
162
180
  ]);
163
181
  },
164
182
  });
165
183
  return AST.print(ast);
166
184
  }
167
- function removeLocalClassAttributes(file, data) {
185
+ function removeLocalClassAttributes(file) {
168
186
  function transformParam(param) {
169
187
  // @ts-ignore: Assume that types from external packages are correct
170
188
  switch (param.type) {
@@ -213,15 +231,15 @@ function removeLocalClassAttributes(file, data) {
213
231
  // @ts-ignore: Assume that types from external packages are correct
214
232
  if (hasPathExpression(part.params)) {
215
233
  return AST.builders.mustache(AST.builders.path('get'), [
216
- AST.builders.path(`this.${data.__styles__}`),
234
+ AST.builders.path('this.styles'),
217
235
  // @ts-ignore: Assume that types from external packages are correct
218
236
  AST.builders.sexpr(part.path.original, part.params),
219
237
  ]);
220
238
  }
221
239
  // @ts-ignore: Assume that types from external packages are correct
222
240
  const params = part.params.map(transformParam);
223
- return AST.builders.mustache('local-class', [
224
- AST.builders.path(`this.${data.__styles__}`),
241
+ return AST.builders.mustache('local', [
242
+ AST.builders.path('this.styles'),
225
243
  ...params,
226
244
  ]);
227
245
  }
@@ -229,15 +247,15 @@ function removeLocalClassAttributes(file, data) {
229
247
  case 'unless': {
230
248
  // @ts-ignore: Assume that types from external packages are correct
231
249
  const params = part.params.map(transformParam);
232
- return AST.builders.mustache('local-class', [
233
- AST.builders.path(`this.${data.__styles__}`),
250
+ return AST.builders.mustache('local', [
251
+ AST.builders.path('this.styles'),
234
252
  // @ts-ignore: Assume that types from external packages are correct
235
253
  AST.builders.sexpr(part.path.original, params),
236
254
  ]);
237
255
  }
238
256
  default: {
239
257
  return AST.builders.mustache(AST.builders.path('get'), [
240
- AST.builders.path(`this.${data.__styles__}`),
258
+ AST.builders.path('this.styles'),
241
259
  // @ts-ignore: Assume that types from external packages are correct
242
260
  part.path,
243
261
  ]);
@@ -252,10 +270,10 @@ function removeLocalClassAttributes(file, data) {
252
270
  }
253
271
  const localClassNames = value.split(/\s+/);
254
272
  if (localClassNames.length === 1) {
255
- return AST.builders.mustache(AST.builders.path(`this.${data.__styles__}.${localClassNames[0]}`));
273
+ return AST.builders.mustache(AST.builders.path(`this.styles.${localClassNames[0]}`));
256
274
  }
257
- return AST.builders.mustache(AST.builders.path('local-class'), [
258
- AST.builders.path(`this.${data.__styles__}`),
275
+ return AST.builders.mustache(AST.builders.path('local'), [
276
+ AST.builders.path('this.styles'),
259
277
  ...localClassNames.map(AST.builders.string),
260
278
  ]);
261
279
  }
@@ -315,11 +333,11 @@ function removeLocalClassAttributes(file, data) {
315
333
  if (newValue.type === 'StringLiteral') {
316
334
  node.value = AST.builders.path(
317
335
  // @ts-ignore: Assume that types from external packages are correct
318
- `this.${data.__styles__}.${newValue.value}`);
336
+ `this.styles.${newValue.value}`);
319
337
  return;
320
338
  }
321
- node.value = AST.builders.sexpr('local-class', [
322
- AST.builders.path(`this.${data.__styles__}`),
339
+ node.value = AST.builders.sexpr('local', [
340
+ AST.builders.path('this.styles'),
323
341
  // @ts-ignore: Assume that types from external packages are correct
324
342
  newValue,
325
343
  ]);
@@ -329,17 +347,14 @@ function removeLocalClassAttributes(file, data) {
329
347
  }
330
348
  export function updateTemplate(entityName, { customizations, options }) {
331
349
  const { getFilePath } = customizations;
332
- const { __styles__, projectRoot } = options;
350
+ const { projectRoot } = options;
333
351
  const filePath = getFilePath(entityName);
334
- const data = {
335
- __styles__,
336
- };
337
352
  try {
338
353
  let file = readFileSync(join(projectRoot, filePath), 'utf8');
339
354
  file = sanitizeClassAndLocalClassAttributes(file);
340
- file = mergeClassAndLocalClassAttributes(file, data);
341
- file = removeLocalClassHelpers(file, data);
342
- file = removeLocalClassAttributes(file, data);
355
+ file = mergeClassAndLocalClassAttributes(file);
356
+ file = removeLocalClassHelpers(file);
357
+ file = removeLocalClassAttributes(file);
343
358
  const fileMap = new Map([[filePath, file]]);
344
359
  createFiles(fileMap, options);
345
360
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ember-codemod-remove-ember-css-modules",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "Codemod to replace ember-css-modules with embroider-css-modules",
5
5
  "keywords": [
6
6
  "codemod",
@@ -26,26 +26,26 @@
26
26
  "dist"
27
27
  ],
28
28
  "dependencies": {
29
- "@codemod-utils/ast-javascript": "^1.0.0",
30
- "@codemod-utils/ast-template": "^1.0.0",
31
- "@codemod-utils/blueprints": "^1.0.0",
32
- "@codemod-utils/ember-cli-string": "^1.0.0",
33
- "@codemod-utils/files": "^1.0.0",
34
- "@codemod-utils/json": "^1.0.0",
29
+ "@codemod-utils/ast-javascript": "^1.2.0",
30
+ "@codemod-utils/ast-template": "^1.1.0",
31
+ "@codemod-utils/blueprints": "^1.1.0",
32
+ "@codemod-utils/ember-cli-string": "^1.1.0",
33
+ "@codemod-utils/files": "^1.1.0",
34
+ "@codemod-utils/json": "^1.1.1",
35
35
  "yargs": "^17.7.2"
36
36
  },
37
37
  "devDependencies": {
38
- "@codemod-utils/tests": "^1.0.0",
38
+ "@codemod-utils/tests": "^1.1.1",
39
39
  "@sondr3/minitest": "^0.1.2",
40
- "@types/node": "^18.17.15",
41
- "@types/yargs": "^17.0.24",
42
- "concurrently": "^8.2.1",
43
- "eslint": "^8.49.0",
40
+ "@types/node": "^18.18.7",
41
+ "@types/yargs": "^17.0.29",
42
+ "concurrently": "^8.2.2",
43
+ "eslint": "^8.52.0",
44
44
  "prettier": "^3.0.3",
45
45
  "typescript": "^5.2.2",
46
+ "@shared-configs/typescript": "0.0.0",
46
47
  "@shared-configs/eslint-config-node": "0.0.0",
47
- "@shared-configs/prettier": "0.0.0",
48
- "@shared-configs/typescript": "0.0.0"
48
+ "@shared-configs/prettier": "0.0.0"
49
49
  },
50
50
  "engines": {
51
51
  "node": "18.* || >= 20"