ember-codemod-remove-global-styles 0.2.0 → 0.3.0
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 +37 -2
- package/dist/bin/ember-codemod-remove-global-styles.js +12 -0
- package/dist/src/index.js +1 -2
- package/dist/src/steps/analyze-project/analyze-components.js +6 -3
- package/dist/src/steps/analyze-project/analyze-routes.js +6 -3
- package/dist/src/steps/create-options.js +6 -1
- package/dist/src/steps/index.js +0 -1
- package/dist/src/steps/update-project/create-stylesheets.js +39 -0
- package/dist/src/steps/update-project/index.js +3 -0
- package/dist/src/steps/update-project/update-classes.js +14 -0
- package/dist/src/steps/update-project/update-templates.js +14 -0
- package/dist/src/steps/update-project.js +4 -42
- package/dist/src/utils/css/add-local-classes/processor.js +43 -7
- package/dist/src/utils/css/add-local-classes.js +16 -0
- package/dist/src/utils/css/get-classes/processor.js +29 -0
- package/dist/src/utils/css/get-classes.js +15 -0
- package/dist/src/utils/css/get-module-file-path.js +20 -2
- package/dist/src/utils/update-project/index.js +2 -0
- package/dist/src/utils/update-project/update-class/add-styles-to-class.js +22 -0
- package/dist/src/utils/update-project/update-class/get-class-file.js +61 -0
- package/dist/src/utils/update-project/update-class/import-stylesheet.js +18 -0
- package/dist/src/utils/update-project/update-class/index.js +4 -0
- package/dist/src/utils/update-project/update-class/replace-template-only-component.js +39 -0
- package/dist/src/utils/update-project/update-class.js +29 -0
- package/dist/src/utils/update-project/update-template.js +19 -0
- package/package.json +3 -3
- package/dist/src/steps/create-css-module-files/get-file.js +0 -11
- package/dist/src/steps/create-css-module-files/index.js +0 -2
- package/dist/src/steps/create-css-module-files/log-errors.js +0 -8
- package/dist/src/steps/create-css-module-files.js +0 -21
package/README.md
CHANGED
|
@@ -12,17 +12,52 @@ _Codemod to localize global styles_
|
|
|
12
12
|
You must pass `--src` to indicate the location of your global stylesheet.
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
ember-codemod-remove-global-styles --src app/assets/app.css
|
|
15
|
+
pnpx ember-codemod-remove-global-styles --src app/assets/app.css
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
<details>
|
|
19
19
|
|
|
20
|
+
<summary>Optional: Limit types of files to consider</summary>
|
|
21
|
+
|
|
22
|
+
By default, the codemod considers components and routes. Pass `--convert` to consider a subset of these.
|
|
23
|
+
|
|
24
|
+
```sh
|
|
25
|
+
# Components only
|
|
26
|
+
pnpx ember-codemod-remove-global-styles --convert components
|
|
27
|
+
|
|
28
|
+
# Routes only
|
|
29
|
+
pnpx ember-codemod-remove-global-styles --convert routes
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
</details>
|
|
33
|
+
|
|
34
|
+
<details>
|
|
35
|
+
|
|
36
|
+
<summary>Optional: Limit folders to consider</summary>
|
|
37
|
+
|
|
38
|
+
By default, the codemod considers all files and folders for components and routes. Pass `--folder` to limit the search to 1 folder. (You may use glob patterns to specify multiple folders.)
|
|
39
|
+
|
|
40
|
+
```sh
|
|
41
|
+
# `ui` folder only
|
|
42
|
+
pnpx ember-codemod-remove-global-styles --folder ui
|
|
43
|
+
|
|
44
|
+
# `ui/form` folder only
|
|
45
|
+
pnpx ember-codemod-remove-global-styles --folder ui/form
|
|
46
|
+
|
|
47
|
+
# `route1` and `route2` folders only
|
|
48
|
+
pnpx ember-codemod-remove-global-styles --convert routes --folder "{route1,route2}"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
</details>
|
|
52
|
+
|
|
53
|
+
<details>
|
|
54
|
+
|
|
20
55
|
<summary>Optional: Specify the project root</summary>
|
|
21
56
|
|
|
22
57
|
Pass `--root` to run the codemod somewhere else (i.e. not in the current directory).
|
|
23
58
|
|
|
24
59
|
```sh
|
|
25
|
-
|
|
60
|
+
pnpx ember-codemod-remove-global-styles --root <path/to/your/project>
|
|
26
61
|
```
|
|
27
62
|
|
|
28
63
|
</details>
|
|
@@ -7,6 +7,15 @@ import { runCodemod } from '../src/index.js';
|
|
|
7
7
|
process.title = 'ember-codemod-remove-global-styles';
|
|
8
8
|
// Set codemod options
|
|
9
9
|
const argv = yargs(hideBin(process.argv))
|
|
10
|
+
.option('convert', {
|
|
11
|
+
choices: ['components', 'routes'],
|
|
12
|
+
describe: 'Which type of files to consider',
|
|
13
|
+
type: 'array',
|
|
14
|
+
})
|
|
15
|
+
.option('folder', {
|
|
16
|
+
describe: 'Which folder to consider',
|
|
17
|
+
type: 'string',
|
|
18
|
+
})
|
|
10
19
|
.option('root', {
|
|
11
20
|
describe: 'Where to run the codemod',
|
|
12
21
|
type: 'string',
|
|
@@ -17,7 +26,10 @@ const argv = yargs(hideBin(process.argv))
|
|
|
17
26
|
type: 'string',
|
|
18
27
|
})
|
|
19
28
|
.parseSync();
|
|
29
|
+
const DEFAULT_FOR_CONVERT = ['components', 'routes'];
|
|
20
30
|
const codemodOptions = {
|
|
31
|
+
convert: new Set(argv['convert'] ?? DEFAULT_FOR_CONVERT),
|
|
32
|
+
folder: argv['folder'] ?? '',
|
|
21
33
|
projectRoot: argv['root'] ?? process.cwd(),
|
|
22
34
|
src: argv['src'],
|
|
23
35
|
};
|
package/dist/src/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { analyzeProject,
|
|
1
|
+
import { analyzeProject, createOptions, updateProject } from './steps/index.js';
|
|
2
2
|
export function runCodemod(codemodOptions) {
|
|
3
3
|
const options = createOptions(codemodOptions);
|
|
4
4
|
const project = analyzeProject(options);
|
|
5
|
-
createCssModuleFiles(project, options);
|
|
6
5
|
updateProject(project, options);
|
|
7
6
|
}
|
|
@@ -3,11 +3,14 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { findFiles } from '@codemod-utils/files';
|
|
4
4
|
import { getEntityData } from './get-entity-data.js';
|
|
5
5
|
export function analyzeComponents(classToStyles, options) {
|
|
6
|
-
const { projectRoot } = options;
|
|
7
|
-
const
|
|
6
|
+
const { convert, folder, projectRoot } = options;
|
|
7
|
+
const components = new Map();
|
|
8
|
+
if (!convert.components) {
|
|
9
|
+
return components;
|
|
10
|
+
}
|
|
11
|
+
const filePaths = findFiles(join('app/components', folder, '**/*.{gjs,gts,hbs}'), {
|
|
8
12
|
projectRoot,
|
|
9
13
|
});
|
|
10
|
-
const components = new Map();
|
|
11
14
|
filePaths.forEach((filePath) => {
|
|
12
15
|
const file = readFileSync(join(projectRoot, filePath), 'utf8');
|
|
13
16
|
const entityData = getEntityData(file, {
|
|
@@ -3,11 +3,14 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { findFiles } from '@codemod-utils/files';
|
|
4
4
|
import { getEntityData } from './get-entity-data.js';
|
|
5
5
|
export function analyzeRoutes(classToStyles, options) {
|
|
6
|
-
const { projectRoot } = options;
|
|
7
|
-
const
|
|
6
|
+
const { convert, folder, projectRoot } = options;
|
|
7
|
+
const routes = new Map();
|
|
8
|
+
if (!convert.routes) {
|
|
9
|
+
return routes;
|
|
10
|
+
}
|
|
11
|
+
const filePaths = findFiles(join('app/templates', folder, '**/*.{gjs,gts,hbs}'), {
|
|
8
12
|
projectRoot,
|
|
9
13
|
});
|
|
10
|
-
const routes = new Map();
|
|
11
14
|
filePaths.forEach((filePath) => {
|
|
12
15
|
const file = readFileSync(join(projectRoot, filePath), 'utf8');
|
|
13
16
|
const entityData = getEntityData(file, {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export function createOptions(codemodOptions) {
|
|
2
|
-
const { projectRoot, src } = codemodOptions;
|
|
2
|
+
const { convert, folder, projectRoot, src } = codemodOptions;
|
|
3
3
|
return {
|
|
4
|
+
convert: {
|
|
5
|
+
components: convert.has('components'),
|
|
6
|
+
routes: convert.has('routes'),
|
|
7
|
+
},
|
|
8
|
+
folder,
|
|
4
9
|
projectRoot,
|
|
5
10
|
src,
|
|
6
11
|
};
|
package/dist/src/steps/index.js
CHANGED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { createFiles } from '@codemod-utils/files';
|
|
4
|
+
import { getModuleFilePath, printStyles } from '../../utils/css/index.js';
|
|
5
|
+
function getFile(filePath, options) {
|
|
6
|
+
const { projectRoot } = options;
|
|
7
|
+
try {
|
|
8
|
+
return readFileSync(join(projectRoot, filePath), 'utf8');
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function logErrors(cssModuleFilePath, errors) {
|
|
15
|
+
if (errors.length === 0) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
console.warn(`WARNING: ${cssModuleFilePath} may be incorrect.`);
|
|
19
|
+
console.warn(errors.map((error) => `- ${error}`).join('\n'));
|
|
20
|
+
console.log();
|
|
21
|
+
}
|
|
22
|
+
export function createStylesheets(project, options) {
|
|
23
|
+
const fileMap = new Map();
|
|
24
|
+
project.components.forEach((data, filePath) => {
|
|
25
|
+
const cssModuleFilePath = getModuleFilePath(filePath);
|
|
26
|
+
let cssModuleFile = getFile(cssModuleFilePath, options);
|
|
27
|
+
cssModuleFile += `${printStyles(data.localStyles)}\n`;
|
|
28
|
+
fileMap.set(cssModuleFilePath, cssModuleFile);
|
|
29
|
+
logErrors(cssModuleFilePath, data.errors);
|
|
30
|
+
});
|
|
31
|
+
project.routes.forEach((data, filePath) => {
|
|
32
|
+
const cssModuleFilePath = getModuleFilePath(filePath);
|
|
33
|
+
let cssModuleFile = getFile(cssModuleFilePath, options);
|
|
34
|
+
cssModuleFile += `${printStyles(data.localStyles)}\n`;
|
|
35
|
+
fileMap.set(cssModuleFilePath, cssModuleFile);
|
|
36
|
+
logErrors(cssModuleFilePath, data.errors);
|
|
37
|
+
});
|
|
38
|
+
createFiles(fileMap, options);
|
|
39
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createFiles } from '@codemod-utils/files';
|
|
2
|
+
import { updateClass } from '../../utils/update-project/index.js';
|
|
3
|
+
export function updateClasses(project, options) {
|
|
4
|
+
const fileMap = new Map();
|
|
5
|
+
project.components.forEach((_data, templateFilePath) => {
|
|
6
|
+
const { classFile, classFilePath } = updateClass(templateFilePath, options);
|
|
7
|
+
fileMap.set(classFilePath, classFile);
|
|
8
|
+
});
|
|
9
|
+
project.routes.forEach((_data, templateFilePath) => {
|
|
10
|
+
const { classFile, classFilePath } = updateClass(templateFilePath, options);
|
|
11
|
+
fileMap.set(classFilePath, classFile);
|
|
12
|
+
});
|
|
13
|
+
createFiles(fileMap, options);
|
|
14
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createFiles } from '@codemod-utils/files';
|
|
2
|
+
import { updateTemplate } from '../../utils/update-project/index.js';
|
|
3
|
+
export function updateTemplates(project, options) {
|
|
4
|
+
const fileMap = new Map();
|
|
5
|
+
project.components.forEach((_data, templateFilePath) => {
|
|
6
|
+
const templateFile = updateTemplate(templateFilePath, options);
|
|
7
|
+
fileMap.set(templateFilePath, templateFile);
|
|
8
|
+
});
|
|
9
|
+
project.routes.forEach((_data, templateFilePath) => {
|
|
10
|
+
const templateFile = updateTemplate(templateFilePath, options);
|
|
11
|
+
fileMap.set(templateFilePath, templateFile);
|
|
12
|
+
});
|
|
13
|
+
createFiles(fileMap, options);
|
|
14
|
+
}
|
|
@@ -1,44 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { updateTemplates } from '@codemod-utils/ast-template-tag';
|
|
4
|
-
import { createFiles } from '@codemod-utils/files';
|
|
5
|
-
import { addLocalClasses, getClassToStyles, getModuleFilePath, } from '../utils/css/index.js';
|
|
1
|
+
import { createStylesheets, updateClasses, updateTemplates, } from './update-project/index.js';
|
|
6
2
|
export function updateProject(project, options) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const fileMap = new Map();
|
|
11
|
-
project.components.forEach((_data, filePath) => {
|
|
12
|
-
const cssModuleFile = getFile(getModuleFilePath(filePath));
|
|
13
|
-
let file = getFile(filePath);
|
|
14
|
-
const data = {
|
|
15
|
-
classToStyles: getClassToStyles(cssModuleFile),
|
|
16
|
-
};
|
|
17
|
-
if (filePath.endsWith('.hbs')) {
|
|
18
|
-
file = addLocalClasses(file, data);
|
|
19
|
-
}
|
|
20
|
-
else {
|
|
21
|
-
file = updateTemplates(file, (code) => {
|
|
22
|
-
return addLocalClasses(code, data);
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
fileMap.set(filePath, file);
|
|
26
|
-
});
|
|
27
|
-
project.routes.forEach((_data, filePath) => {
|
|
28
|
-
const cssModuleFile = getFile(getModuleFilePath(filePath));
|
|
29
|
-
let file = getFile(filePath);
|
|
30
|
-
const data = {
|
|
31
|
-
classToStyles: getClassToStyles(cssModuleFile),
|
|
32
|
-
};
|
|
33
|
-
if (filePath.endsWith('.hbs')) {
|
|
34
|
-
file = addLocalClasses(file, data);
|
|
35
|
-
}
|
|
36
|
-
else {
|
|
37
|
-
file = updateTemplates(file, (code) => {
|
|
38
|
-
return addLocalClasses(code, data);
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
fileMap.set(filePath, file);
|
|
42
|
-
});
|
|
43
|
-
createFiles(fileMap, options);
|
|
3
|
+
createStylesheets(project, options);
|
|
4
|
+
updateClasses(project, options);
|
|
5
|
+
updateTemplates(project, options);
|
|
44
6
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { AST } from '@codemod-utils/ast-template';
|
|
2
2
|
export class Processor {
|
|
3
|
-
|
|
3
|
+
args;
|
|
4
4
|
constructor(args) {
|
|
5
|
-
this.
|
|
5
|
+
this.args = args;
|
|
6
|
+
}
|
|
7
|
+
getLocalClass(className) {
|
|
8
|
+
return this.args.isHbs ? `this.styles.${className}` : `styles.${className}`;
|
|
6
9
|
}
|
|
7
10
|
isLocal(className) {
|
|
8
|
-
return this.classToStyles.has(className);
|
|
11
|
+
return this.args.classToStyles.has(className);
|
|
9
12
|
}
|
|
10
13
|
processConcatStatement(nodeValue) {
|
|
11
14
|
const parts = nodeValue.parts
|
|
@@ -63,7 +66,7 @@ export class Processor {
|
|
|
63
66
|
if (classNames.length === 1) {
|
|
64
67
|
const className = classNames[0];
|
|
65
68
|
return this.isLocal(className)
|
|
66
|
-
? AST.builders.path(
|
|
69
|
+
? AST.builders.path(this.getLocalClass(className))
|
|
67
70
|
: nodeValue;
|
|
68
71
|
}
|
|
69
72
|
const hasLocalClass = classNames.some(this.isLocal.bind(this));
|
|
@@ -73,7 +76,10 @@ export class Processor {
|
|
|
73
76
|
const parts = classNames
|
|
74
77
|
.map((className) => {
|
|
75
78
|
return this.isLocal(className)
|
|
76
|
-
? [
|
|
79
|
+
? [
|
|
80
|
+
AST.builders.path(this.getLocalClass(className)),
|
|
81
|
+
AST.builders.string(' '),
|
|
82
|
+
]
|
|
77
83
|
: [AST.builders.string(`${className} `), AST.builders.string(' ')];
|
|
78
84
|
})
|
|
79
85
|
.flat();
|
|
@@ -81,6 +87,33 @@ export class Processor {
|
|
|
81
87
|
parts.splice(-1);
|
|
82
88
|
return AST.builders.sexpr(AST.builders.path('concat'), parts);
|
|
83
89
|
}
|
|
90
|
+
processSubExpression(nodeValue) {
|
|
91
|
+
switch (nodeValue.path.type) {
|
|
92
|
+
case 'PathExpression': {
|
|
93
|
+
switch (nodeValue.path.original) {
|
|
94
|
+
case 'if':
|
|
95
|
+
case 'unless': {
|
|
96
|
+
if (nodeValue.params[1]?.type === 'StringLiteral') {
|
|
97
|
+
// @ts-expect-error: Incorrect type
|
|
98
|
+
nodeValue.params[1] = this.processStringLiteral(nodeValue.params[1]);
|
|
99
|
+
}
|
|
100
|
+
if (nodeValue.params[2]?.type === 'StringLiteral') {
|
|
101
|
+
// @ts-expect-error: Incorrect type
|
|
102
|
+
nodeValue.params[2] = this.processStringLiteral(nodeValue.params[2]);
|
|
103
|
+
}
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'StringLiteral': {
|
|
110
|
+
// @ts-expect-error: Incorrect type
|
|
111
|
+
nodeValue.path = this.processStringLiteral(nodeValue.path);
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return nodeValue;
|
|
116
|
+
}
|
|
84
117
|
processTextNode(nodeValue) {
|
|
85
118
|
const classNames = nodeValue.chars.split(/\s+/).filter(Boolean);
|
|
86
119
|
if (classNames.length === 0) {
|
|
@@ -89,7 +122,7 @@ export class Processor {
|
|
|
89
122
|
if (classNames.length === 1) {
|
|
90
123
|
const className = classNames[0];
|
|
91
124
|
return this.isLocal(className)
|
|
92
|
-
? AST.builders.mustache(
|
|
125
|
+
? AST.builders.mustache(this.getLocalClass(className))
|
|
93
126
|
: nodeValue;
|
|
94
127
|
}
|
|
95
128
|
const hasLocalClass = classNames.some(this.isLocal.bind(this));
|
|
@@ -99,7 +132,10 @@ export class Processor {
|
|
|
99
132
|
const parts = classNames
|
|
100
133
|
.map((className) => {
|
|
101
134
|
return this.isLocal(className)
|
|
102
|
-
? [
|
|
135
|
+
? [
|
|
136
|
+
AST.builders.path(this.getLocalClass(className)),
|
|
137
|
+
AST.builders.string(' '),
|
|
138
|
+
]
|
|
103
139
|
: [AST.builders.string(className), AST.builders.string(' ')];
|
|
104
140
|
})
|
|
105
141
|
.flat();
|
|
@@ -23,6 +23,22 @@ export function addLocalClasses(file, data) {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
|
+
HashPair(node) {
|
|
27
|
+
if (node.key !== 'class') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
switch (node.value.type) {
|
|
31
|
+
case 'StringLiteral': {
|
|
32
|
+
// @ts-expect-error: Incorrect type
|
|
33
|
+
node.value = processor.processStringLiteral(node.value);
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
case 'SubExpression': {
|
|
37
|
+
node.value = processor.processSubExpression(node.value);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
26
42
|
});
|
|
27
43
|
return AST.print(ast);
|
|
28
44
|
}
|
|
@@ -60,6 +60,35 @@ export class Processor {
|
|
|
60
60
|
this.classes.add(className);
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
|
+
processSubExpression(nodeValue) {
|
|
64
|
+
switch (nodeValue.path.type) {
|
|
65
|
+
case 'PathExpression': {
|
|
66
|
+
switch (nodeValue.path.original) {
|
|
67
|
+
case 'if':
|
|
68
|
+
case 'unless': {
|
|
69
|
+
if (nodeValue.params[1]?.type === 'StringLiteral') {
|
|
70
|
+
this.processStringLiteral(nodeValue.params[1]);
|
|
71
|
+
}
|
|
72
|
+
if (nodeValue.params[2]?.type === 'StringLiteral') {
|
|
73
|
+
this.processStringLiteral(nodeValue.params[2]);
|
|
74
|
+
}
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
default: {
|
|
78
|
+
const isLocalClass = nodeValue.path.original.startsWith('styles.');
|
|
79
|
+
if (!isLocalClass) {
|
|
80
|
+
this.errors.push(`Could not analyze {{${nodeValue.path.original}}} in template, line ${nodeValue.loc.start.line}.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
case 'StringLiteral': {
|
|
87
|
+
this.processStringLiteral(nodeValue.path);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
63
92
|
processTextNode(nodeValue) {
|
|
64
93
|
const classNames = extractClasses(nodeValue.chars);
|
|
65
94
|
classNames.forEach((className) => {
|
|
@@ -23,6 +23,21 @@ export function getClasses(file) {
|
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
|
+
HashPair(node) {
|
|
27
|
+
if (node.key !== 'class') {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
switch (node.value.type) {
|
|
31
|
+
case 'StringLiteral': {
|
|
32
|
+
processor.processStringLiteral(node.value);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case 'SubExpression': {
|
|
36
|
+
processor.processSubExpression(node.value);
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
26
41
|
});
|
|
27
42
|
return processor.print();
|
|
28
43
|
}
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { join, relative } from 'node:path';
|
|
2
|
+
import { parseFilePath } from '@codemod-utils/files';
|
|
3
|
+
export function getModuleFilePath(templateFilePath) {
|
|
4
|
+
const { dir, ext, name } = parseFilePath(templateFilePath);
|
|
5
|
+
if (!['.gjs', '.gts', '.hbs'].includes(ext)) {
|
|
6
|
+
throw new RangeError('File extension is incorrect.');
|
|
7
|
+
}
|
|
8
|
+
const data = {
|
|
9
|
+
isComponentTemplate: dir.startsWith('app/components'),
|
|
10
|
+
isRouteTemplate: dir.startsWith('app/templates'),
|
|
11
|
+
isTemplateTag: ext === '.gjs' || ext === '.gts',
|
|
12
|
+
};
|
|
13
|
+
if (data.isTemplateTag) {
|
|
14
|
+
return join(dir, `${name}.module.css`);
|
|
15
|
+
}
|
|
16
|
+
if (data.isRouteTemplate) {
|
|
17
|
+
const controllersDir = join(dir, relative(dir, 'app/controllers'));
|
|
18
|
+
return join(controllersDir, `${name}.module.css`);
|
|
19
|
+
}
|
|
20
|
+
return join(dir, `${name}.module.css`);
|
|
3
21
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { AST } from '@codemod-utils/ast-javascript';
|
|
2
|
+
export function addStylesToClass(file, data) {
|
|
3
|
+
if (data.isTemplateTag) {
|
|
4
|
+
return file;
|
|
5
|
+
}
|
|
6
|
+
const traverse = AST.traverse(data.isTypeScript);
|
|
7
|
+
const ast = traverse(file, {
|
|
8
|
+
visitClassDeclaration(path) {
|
|
9
|
+
const { body } = path.node.body;
|
|
10
|
+
const nodesToAdd = [
|
|
11
|
+
AST.builders.classProperty(AST.builders.identifier('styles'), AST.builders.identifier('styles')),
|
|
12
|
+
];
|
|
13
|
+
if (body.length > 0) {
|
|
14
|
+
// @ts-expect-error: Incorrect type
|
|
15
|
+
nodesToAdd.push(AST.builders.noop());
|
|
16
|
+
}
|
|
17
|
+
body.unshift(...nodesToAdd);
|
|
18
|
+
return false;
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
return AST.print(ast);
|
|
22
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join, relative } from 'node:path';
|
|
3
|
+
import { findFiles, parseFilePath } from '@codemod-utils/files';
|
|
4
|
+
export function getClassFile(templateFilePath, options) {
|
|
5
|
+
const { projectRoot } = options;
|
|
6
|
+
const { dir, ext, name } = parseFilePath(templateFilePath);
|
|
7
|
+
const data = {
|
|
8
|
+
isRouteTemplate: dir.startsWith('app/templates'),
|
|
9
|
+
isTemplateTag: ext === '.gjs' || ext === '.gts',
|
|
10
|
+
};
|
|
11
|
+
if (data.isTemplateTag) {
|
|
12
|
+
const classFile = readFileSync(join(projectRoot, templateFilePath), 'utf8');
|
|
13
|
+
return {
|
|
14
|
+
classFile,
|
|
15
|
+
classFilePath: templateFilePath,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
if (data.isRouteTemplate) {
|
|
19
|
+
const controllersDir = join(dir, relative(dir, 'app/controllers'));
|
|
20
|
+
const classFilePaths = findFiles(join(controllersDir, `${name}.{js,ts}`), {
|
|
21
|
+
projectRoot,
|
|
22
|
+
});
|
|
23
|
+
if (classFilePaths.length === 0) {
|
|
24
|
+
const classFilePath = join(controllersDir, `${name}.js`);
|
|
25
|
+
return {
|
|
26
|
+
classFilePath,
|
|
27
|
+
classFile: [
|
|
28
|
+
`import Controller from '@ember/controller';`,
|
|
29
|
+
``,
|
|
30
|
+
`export default class extends Controller {}`,
|
|
31
|
+
``,
|
|
32
|
+
].join('\n'),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const classFilePath = classFilePaths[0];
|
|
36
|
+
return {
|
|
37
|
+
classFilePath,
|
|
38
|
+
classFile: readFileSync(join(projectRoot, classFilePath), 'utf8'),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
const classFilePaths = findFiles(join(dir, `${name}.{js,ts}`), {
|
|
42
|
+
projectRoot,
|
|
43
|
+
});
|
|
44
|
+
if (classFilePaths.length === 0) {
|
|
45
|
+
const classFilePath = join(dir, `${name}.js`);
|
|
46
|
+
return {
|
|
47
|
+
classFilePath,
|
|
48
|
+
classFile: [
|
|
49
|
+
`import Component from '@glimmer/component';`,
|
|
50
|
+
``,
|
|
51
|
+
`export default class extends Component {}`,
|
|
52
|
+
``,
|
|
53
|
+
].join('\n'),
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
const classFilePath = classFilePaths[0];
|
|
57
|
+
return {
|
|
58
|
+
classFilePath,
|
|
59
|
+
classFile: readFileSync(join(projectRoot, classFilePath), 'utf8'),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { AST } from '@codemod-utils/ast-javascript';
|
|
2
|
+
export function importStylesheet(file, data) {
|
|
3
|
+
const traverse = AST.traverse(data.isTypeScript);
|
|
4
|
+
let canSkip = false;
|
|
5
|
+
traverse(file, {
|
|
6
|
+
visitImportDeclaration(path) {
|
|
7
|
+
const importPath = path.node.source.value;
|
|
8
|
+
if (importPath === `./${data.fileName}.css` ||
|
|
9
|
+
importPath === `./${data.fileName}.module.css`) {
|
|
10
|
+
canSkip = true;
|
|
11
|
+
}
|
|
12
|
+
return false;
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
return canSkip
|
|
16
|
+
? file
|
|
17
|
+
: [`import styles from './${data.fileName}.module.css';`, file].join('\n');
|
|
18
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { AST } from '@codemod-utils/ast-javascript';
|
|
2
|
+
export function replaceTemplateOnlyComponent(file, data) {
|
|
3
|
+
if (data.isTemplateTag) {
|
|
4
|
+
return file;
|
|
5
|
+
}
|
|
6
|
+
const traverse = AST.traverse(data.isTypeScript);
|
|
7
|
+
const ast = traverse(file, {
|
|
8
|
+
visitCallExpression(path) {
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
10
|
+
if (path.value.callee.name !== 'templateOnlyComponent') {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
const superClass = AST.builders.identifier('Component');
|
|
14
|
+
if (data.isTypeScript) {
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
|
|
16
|
+
superClass.typeAnnotation = path.value.typeParameters;
|
|
17
|
+
}
|
|
18
|
+
return AST.builders.classExpression(null, AST.builders.classBody([
|
|
19
|
+
AST.builders.classProperty(AST.builders.identifier('styles'), AST.builders.identifier('styles')),
|
|
20
|
+
]), superClass);
|
|
21
|
+
},
|
|
22
|
+
visitImportDeclaration(path) {
|
|
23
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
24
|
+
if (path.value.source.value !== '@ember/component/template-only') {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
|
|
28
|
+
const defaultImport = path.value.specifiers.find((specifier) => specifier.type === 'ImportDefaultSpecifier');
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
30
|
+
if (defaultImport?.local?.name !== 'templateOnlyComponent') {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
return AST.builders.importDeclaration([
|
|
34
|
+
AST.builders.importDefaultSpecifier(AST.builders.identifier('Component')),
|
|
35
|
+
], AST.builders.literal('@glimmer/component'));
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
return AST.print(ast);
|
|
39
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { updateJavaScript } from '@codemod-utils/ast-template-tag';
|
|
2
|
+
import { parseFilePath } from '@codemod-utils/files';
|
|
3
|
+
import { addStylesToClass, getClassFile, importStylesheet, replaceTemplateOnlyComponent, } from './update-class/index.js';
|
|
4
|
+
export function updateClass(templateFilePath, options) {
|
|
5
|
+
// eslint-disable-next-line prefer-const
|
|
6
|
+
let { classFile, classFilePath } = getClassFile(templateFilePath, options);
|
|
7
|
+
const { ext, name } = parseFilePath(classFilePath);
|
|
8
|
+
const data = {
|
|
9
|
+
fileName: name,
|
|
10
|
+
isTemplateTag: ext === '.gjs' || ext === '.gts',
|
|
11
|
+
isTypeScript: ext === '.gts' || ext === '.ts',
|
|
12
|
+
};
|
|
13
|
+
if (data.isTemplateTag) {
|
|
14
|
+
classFile = updateJavaScript(classFile, (code) => {
|
|
15
|
+
return importStylesheet(code, data);
|
|
16
|
+
});
|
|
17
|
+
return {
|
|
18
|
+
classFile,
|
|
19
|
+
classFilePath,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
classFile = replaceTemplateOnlyComponent(classFile, data);
|
|
23
|
+
classFile = importStylesheet(classFile, data);
|
|
24
|
+
classFile = addStylesToClass(classFile, data);
|
|
25
|
+
return {
|
|
26
|
+
classFile,
|
|
27
|
+
classFilePath,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { updateTemplates } from '@codemod-utils/ast-template-tag';
|
|
4
|
+
import { addLocalClasses, getClassToStyles, getModuleFilePath, } from '../css/index.js';
|
|
5
|
+
export function updateTemplate(templateFilePath, options) {
|
|
6
|
+
const { projectRoot } = options;
|
|
7
|
+
const cssModuleFile = readFileSync(join(projectRoot, getModuleFilePath(templateFilePath)), 'utf8');
|
|
8
|
+
const templateFile = readFileSync(join(projectRoot, templateFilePath), 'utf8');
|
|
9
|
+
const data = {
|
|
10
|
+
classToStyles: getClassToStyles(cssModuleFile),
|
|
11
|
+
isHbs: templateFilePath.endsWith('.hbs'),
|
|
12
|
+
};
|
|
13
|
+
if (data.isHbs) {
|
|
14
|
+
return addLocalClasses(templateFile, data);
|
|
15
|
+
}
|
|
16
|
+
return updateTemplates(templateFile, (code) => {
|
|
17
|
+
return addLocalClasses(code, data);
|
|
18
|
+
});
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ember-codemod-remove-global-styles",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Codemod to localize global styles",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"codemod",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"eslint": "^9.39.1",
|
|
43
43
|
"prettier": "^3.6.2",
|
|
44
44
|
"typescript": "^5.9.3",
|
|
45
|
-
"@shared-configs/eslint-config-node": "0.0.0",
|
|
46
45
|
"@shared-configs/prettier": "0.0.0",
|
|
47
|
-
"@shared-configs/typescript": "0.0.0"
|
|
46
|
+
"@shared-configs/typescript": "0.0.0",
|
|
47
|
+
"@shared-configs/eslint-config-node": "0.0.0"
|
|
48
48
|
},
|
|
49
49
|
"engines": {
|
|
50
50
|
"node": "20.* || >= 22"
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
export function getFile(filePath, options) {
|
|
4
|
-
const { projectRoot } = options;
|
|
5
|
-
try {
|
|
6
|
-
return readFileSync(join(projectRoot, filePath), 'utf8');
|
|
7
|
-
}
|
|
8
|
-
catch {
|
|
9
|
-
return '';
|
|
10
|
-
}
|
|
11
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { createFiles } from '@codemod-utils/files';
|
|
2
|
-
import { getModuleFilePath, printStyles } from '../utils/css/index.js';
|
|
3
|
-
import { getFile, logErrors } from './create-css-module-files/index.js';
|
|
4
|
-
export function createCssModuleFiles(project, options) {
|
|
5
|
-
const fileMap = new Map();
|
|
6
|
-
project.components.forEach((data, filePath) => {
|
|
7
|
-
const cssModuleFilePath = getModuleFilePath(filePath);
|
|
8
|
-
let cssModuleFile = getFile(cssModuleFilePath, options);
|
|
9
|
-
cssModuleFile += `${printStyles(data.localStyles)}\n`;
|
|
10
|
-
fileMap.set(cssModuleFilePath, cssModuleFile);
|
|
11
|
-
logErrors(data.errors, { cssModuleFilePath });
|
|
12
|
-
});
|
|
13
|
-
project.routes.forEach((data, filePath) => {
|
|
14
|
-
const cssModuleFilePath = getModuleFilePath(filePath);
|
|
15
|
-
let cssModuleFile = getFile(cssModuleFilePath, options);
|
|
16
|
-
cssModuleFile += `${printStyles(data.localStyles)}\n`;
|
|
17
|
-
fileMap.set(cssModuleFilePath, cssModuleFile);
|
|
18
|
-
logErrors(data.errors, { cssModuleFilePath });
|
|
19
|
-
});
|
|
20
|
-
createFiles(fileMap, options);
|
|
21
|
-
}
|