ember-codemod-remove-global-styles 0.1.0 → 0.2.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 +5 -1
- package/dist/src/index.js +2 -1
- package/dist/src/steps/analyze-project/analyze-components.js +22 -0
- package/dist/src/steps/analyze-project/analyze-routes.js +22 -0
- package/dist/src/steps/analyze-project/get-entity-data.js +36 -0
- package/dist/src/steps/analyze-project/index.js +2 -0
- package/dist/src/steps/analyze-project.js +5 -1
- package/dist/src/steps/create-css-module-files/get-file.js +11 -0
- package/dist/src/steps/create-css-module-files/index.js +2 -0
- package/dist/src/steps/create-css-module-files/log-errors.js +8 -0
- package/dist/src/steps/create-css-module-files.js +15 -48
- package/dist/src/steps/index.js +1 -0
- package/dist/src/steps/update-project.js +44 -0
- package/dist/src/utils/css/add-local-classes/index.js +1 -0
- package/dist/src/utils/css/add-local-classes/processor.js +110 -0
- package/dist/src/utils/css/add-local-classes.js +28 -0
- package/dist/src/utils/css/get-class-to-styles/extract-classes.js +4 -0
- package/dist/src/utils/css/get-class-to-styles/extract-root-class.js +7 -0
- package/dist/src/utils/css/get-class-to-styles/extract-selectors.js +3 -0
- package/dist/src/utils/css/get-class-to-styles/index.js +3 -0
- package/dist/src/utils/css/get-class-to-styles.js +5 -15
- package/dist/src/utils/css/get-classes/index.js +1 -0
- package/dist/src/utils/css/get-classes/processor.js +69 -0
- package/dist/src/utils/css/get-classes.js +6 -61
- package/dist/src/utils/css/index.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -9,7 +9,11 @@ _Codemod to localize global styles_
|
|
|
9
9
|
|
|
10
10
|
### Arguments
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
You must pass `--src` to indicate the location of your global stylesheet.
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
ember-codemod-remove-global-styles --src app/assets/app.css
|
|
16
|
+
```
|
|
13
17
|
|
|
14
18
|
<details>
|
|
15
19
|
|
package/dist/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { analyzeProject, createCssModuleFiles, createOptions, } from './steps/index.js';
|
|
1
|
+
import { analyzeProject, createCssModuleFiles, 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
5
|
createCssModuleFiles(project, options);
|
|
6
|
+
updateProject(project, options);
|
|
6
7
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { findFiles } from '@codemod-utils/files';
|
|
4
|
+
import { getEntityData } from './get-entity-data.js';
|
|
5
|
+
export function analyzeComponents(classToStyles, options) {
|
|
6
|
+
const { projectRoot } = options;
|
|
7
|
+
const filePaths = findFiles('app/components/**/*.{gjs,gts,hbs}', {
|
|
8
|
+
projectRoot,
|
|
9
|
+
});
|
|
10
|
+
const components = new Map();
|
|
11
|
+
filePaths.forEach((filePath) => {
|
|
12
|
+
const file = readFileSync(join(projectRoot, filePath), 'utf8');
|
|
13
|
+
const entityData = getEntityData(file, {
|
|
14
|
+
classToStyles,
|
|
15
|
+
isHbs: filePath.endsWith('.hbs'),
|
|
16
|
+
});
|
|
17
|
+
if (entityData.localStyles.length > 0) {
|
|
18
|
+
components.set(filePath, entityData);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return components;
|
|
22
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { findFiles } from '@codemod-utils/files';
|
|
4
|
+
import { getEntityData } from './get-entity-data.js';
|
|
5
|
+
export function analyzeRoutes(classToStyles, options) {
|
|
6
|
+
const { projectRoot } = options;
|
|
7
|
+
const filePaths = findFiles('app/templates/**/*.{gjs,gts,hbs}', {
|
|
8
|
+
projectRoot,
|
|
9
|
+
});
|
|
10
|
+
const routes = new Map();
|
|
11
|
+
filePaths.forEach((filePath) => {
|
|
12
|
+
const file = readFileSync(join(projectRoot, filePath), 'utf8');
|
|
13
|
+
const entityData = getEntityData(file, {
|
|
14
|
+
classToStyles,
|
|
15
|
+
isHbs: filePath.endsWith('.hbs'),
|
|
16
|
+
});
|
|
17
|
+
if (entityData.localStyles.length > 0) {
|
|
18
|
+
routes.set(filePath, entityData);
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
return routes;
|
|
22
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { findTemplateTags } from '@codemod-utils/ast-template-tag';
|
|
2
|
+
import { getClasses } from '../../utils/css/index.js';
|
|
3
|
+
function getLocalStyles(classes, data) {
|
|
4
|
+
const classesSet = new Set(classes);
|
|
5
|
+
const localStyles = classes.reduce((accumulator, className) => {
|
|
6
|
+
const styles = data.classToStyles.get(className) ?? [];
|
|
7
|
+
const filteredStyles = styles.filter(({ classes }) => {
|
|
8
|
+
return classes.every((className) => classesSet.has(className));
|
|
9
|
+
});
|
|
10
|
+
accumulator.push(...filteredStyles);
|
|
11
|
+
return accumulator;
|
|
12
|
+
}, []);
|
|
13
|
+
return localStyles;
|
|
14
|
+
}
|
|
15
|
+
export function getEntityData(file, data) {
|
|
16
|
+
const classes = [];
|
|
17
|
+
const errors = [];
|
|
18
|
+
if (data.isHbs) {
|
|
19
|
+
const output = getClasses(file);
|
|
20
|
+
classes.push(...output.classes);
|
|
21
|
+
errors.push(...output.errors);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
const templateTags = findTemplateTags(file);
|
|
25
|
+
templateTags.forEach(({ contents }) => {
|
|
26
|
+
const output = getClasses(contents);
|
|
27
|
+
classes.push(...output.classes);
|
|
28
|
+
errors.push(...output.errors);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
classes,
|
|
33
|
+
errors,
|
|
34
|
+
localStyles: getLocalStyles(classes, data),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { getClassToStyles } from '../utils/css/index.js';
|
|
4
|
+
import { analyzeComponents, analyzeRoutes } from './analyze-project/index.js';
|
|
4
5
|
export function analyzeProject(options) {
|
|
5
6
|
const { projectRoot, src } = options;
|
|
6
7
|
const stylesheet = readFileSync(join(projectRoot, src), 'utf8');
|
|
7
8
|
const classToStyles = getClassToStyles(stylesheet);
|
|
9
|
+
const components = analyzeComponents(classToStyles, options);
|
|
10
|
+
const routes = analyzeRoutes(classToStyles, options);
|
|
8
11
|
return {
|
|
9
|
-
|
|
12
|
+
components,
|
|
13
|
+
routes,
|
|
10
14
|
};
|
|
11
15
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
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,54 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { createFiles, findFiles } from '@codemod-utils/files';
|
|
5
|
-
import { getClasses, getModuleFilePath, printStyles, } from '../utils/css/index.js';
|
|
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';
|
|
6
4
|
export function createCssModuleFiles(project, options) {
|
|
7
|
-
const { projectRoot } = options;
|
|
8
|
-
const filePaths = findFiles('app/{components,templates}/**/*.{gjs,gts,hbs}', {
|
|
9
|
-
projectRoot,
|
|
10
|
-
});
|
|
11
5
|
const fileMap = new Map();
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
const templateTags = findTemplateTags(file);
|
|
23
|
-
templateTags.forEach(({ contents }) => {
|
|
24
|
-
const output = getClasses(contents);
|
|
25
|
-
classes.push(...output.classes);
|
|
26
|
-
errors.push(...output.errors);
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
const classesSet = new Set(classes);
|
|
30
|
-
const localStyles = classes.reduce((accumulator, className) => {
|
|
31
|
-
const styles = project.classToStyles.get(className) ?? [];
|
|
32
|
-
const filteredStyles = styles.filter(({ classes }) => {
|
|
33
|
-
return classes.every((className) => classesSet.has(className));
|
|
34
|
-
});
|
|
35
|
-
accumulator.push(...filteredStyles);
|
|
36
|
-
return accumulator;
|
|
37
|
-
}, []);
|
|
38
|
-
if (localStyles.length === 0) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
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) => {
|
|
41
14
|
const cssModuleFilePath = getModuleFilePath(filePath);
|
|
42
|
-
let cssModuleFile =
|
|
43
|
-
|
|
44
|
-
: '';
|
|
45
|
-
cssModuleFile += `${printStyles(localStyles)}\n`;
|
|
15
|
+
let cssModuleFile = getFile(cssModuleFilePath, options);
|
|
16
|
+
cssModuleFile += `${printStyles(data.localStyles)}\n`;
|
|
46
17
|
fileMap.set(cssModuleFilePath, cssModuleFile);
|
|
47
|
-
|
|
48
|
-
console.warn(`WARNING: ${cssModuleFilePath} may be incorrect.`);
|
|
49
|
-
console.warn(errors.map((error) => `- ${error}`).join('\n'));
|
|
50
|
-
console.log();
|
|
51
|
-
}
|
|
18
|
+
logErrors(data.errors, { cssModuleFilePath });
|
|
52
19
|
});
|
|
53
|
-
createFiles(fileMap,
|
|
20
|
+
createFiles(fileMap, options);
|
|
54
21
|
}
|
package/dist/src/steps/index.js
CHANGED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
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';
|
|
6
|
+
export function updateProject(project, options) {
|
|
7
|
+
function getFile(filePath) {
|
|
8
|
+
return readFileSync(join(options.projectRoot, filePath), 'utf8');
|
|
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);
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './processor.js';
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { AST } from '@codemod-utils/ast-template';
|
|
2
|
+
export class Processor {
|
|
3
|
+
classToStyles;
|
|
4
|
+
constructor(args) {
|
|
5
|
+
this.classToStyles = args.classToStyles;
|
|
6
|
+
}
|
|
7
|
+
isLocal(className) {
|
|
8
|
+
return this.classToStyles.has(className);
|
|
9
|
+
}
|
|
10
|
+
processConcatStatement(nodeValue) {
|
|
11
|
+
const parts = nodeValue.parts
|
|
12
|
+
.map((part) => {
|
|
13
|
+
switch (part.type) {
|
|
14
|
+
case 'MustacheStatement': {
|
|
15
|
+
return [
|
|
16
|
+
this.processMustacheStatement(part),
|
|
17
|
+
AST.builders.text(' '),
|
|
18
|
+
];
|
|
19
|
+
}
|
|
20
|
+
case 'TextNode': {
|
|
21
|
+
return [this.processTextNode(part), AST.builders.text(' ')];
|
|
22
|
+
}
|
|
23
|
+
default: {
|
|
24
|
+
return part;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
.flat();
|
|
29
|
+
return AST.builders.concat(parts);
|
|
30
|
+
}
|
|
31
|
+
processMustacheStatement(nodeValue) {
|
|
32
|
+
switch (nodeValue.path.type) {
|
|
33
|
+
case 'PathExpression': {
|
|
34
|
+
switch (nodeValue.path.original) {
|
|
35
|
+
case 'if':
|
|
36
|
+
case 'unless': {
|
|
37
|
+
if (nodeValue.params[1]?.type === 'StringLiteral') {
|
|
38
|
+
// @ts-expect-error: Incorrect type
|
|
39
|
+
nodeValue.params[1] = this.processStringLiteral(nodeValue.params[1]);
|
|
40
|
+
}
|
|
41
|
+
if (nodeValue.params[2]?.type === 'StringLiteral') {
|
|
42
|
+
// @ts-expect-error: Incorrect type
|
|
43
|
+
nodeValue.params[2] = this.processStringLiteral(nodeValue.params[2]);
|
|
44
|
+
}
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case 'StringLiteral': {
|
|
51
|
+
// @ts-expect-error: Incorrect type
|
|
52
|
+
nodeValue.path = this.processStringLiteral(nodeValue.path);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return nodeValue;
|
|
57
|
+
}
|
|
58
|
+
processStringLiteral(nodeValue) {
|
|
59
|
+
const classNames = nodeValue.original.split(/\s+/).filter(Boolean);
|
|
60
|
+
if (classNames.length === 0) {
|
|
61
|
+
return AST.builders.text('');
|
|
62
|
+
}
|
|
63
|
+
if (classNames.length === 1) {
|
|
64
|
+
const className = classNames[0];
|
|
65
|
+
return this.isLocal(className)
|
|
66
|
+
? AST.builders.path(`styles.${className}`)
|
|
67
|
+
: nodeValue;
|
|
68
|
+
}
|
|
69
|
+
const hasLocalClass = classNames.some(this.isLocal.bind(this));
|
|
70
|
+
if (!hasLocalClass) {
|
|
71
|
+
return nodeValue;
|
|
72
|
+
}
|
|
73
|
+
const parts = classNames
|
|
74
|
+
.map((className) => {
|
|
75
|
+
return this.isLocal(className)
|
|
76
|
+
? [AST.builders.path(`styles.${className}`), AST.builders.string(' ')]
|
|
77
|
+
: [AST.builders.string(`${className} `), AST.builders.string(' ')];
|
|
78
|
+
})
|
|
79
|
+
.flat();
|
|
80
|
+
// Remove space at the end
|
|
81
|
+
parts.splice(-1);
|
|
82
|
+
return AST.builders.sexpr(AST.builders.path('concat'), parts);
|
|
83
|
+
}
|
|
84
|
+
processTextNode(nodeValue) {
|
|
85
|
+
const classNames = nodeValue.chars.split(/\s+/).filter(Boolean);
|
|
86
|
+
if (classNames.length === 0) {
|
|
87
|
+
return AST.builders.text('');
|
|
88
|
+
}
|
|
89
|
+
if (classNames.length === 1) {
|
|
90
|
+
const className = classNames[0];
|
|
91
|
+
return this.isLocal(className)
|
|
92
|
+
? AST.builders.mustache(`styles.${className}`)
|
|
93
|
+
: nodeValue;
|
|
94
|
+
}
|
|
95
|
+
const hasLocalClass = classNames.some(this.isLocal.bind(this));
|
|
96
|
+
if (!hasLocalClass) {
|
|
97
|
+
return nodeValue;
|
|
98
|
+
}
|
|
99
|
+
const parts = classNames
|
|
100
|
+
.map((className) => {
|
|
101
|
+
return this.isLocal(className)
|
|
102
|
+
? [AST.builders.path(`styles.${className}`), AST.builders.string(' ')]
|
|
103
|
+
: [AST.builders.string(className), AST.builders.string(' ')];
|
|
104
|
+
})
|
|
105
|
+
.flat();
|
|
106
|
+
// Remove space at the end
|
|
107
|
+
parts.splice(-1);
|
|
108
|
+
return AST.builders.mustache(AST.builders.path('concat'), parts);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { AST } from '@codemod-utils/ast-template';
|
|
2
|
+
import { Processor } from './add-local-classes/index.js';
|
|
3
|
+
export function addLocalClasses(file, data) {
|
|
4
|
+
const processor = new Processor(data);
|
|
5
|
+
const traverse = AST.traverse();
|
|
6
|
+
const ast = traverse(file, {
|
|
7
|
+
AttrNode(node) {
|
|
8
|
+
if (node.name !== 'class') {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
switch (node.value.type) {
|
|
12
|
+
case 'ConcatStatement': {
|
|
13
|
+
node.value = processor.processConcatStatement(node.value);
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
case 'MustacheStatement': {
|
|
17
|
+
node.value = processor.processMustacheStatement(node.value);
|
|
18
|
+
break;
|
|
19
|
+
}
|
|
20
|
+
case 'TextNode': {
|
|
21
|
+
node.value = processor.processTextNode(node.value);
|
|
22
|
+
break;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
return AST.print(ast);
|
|
28
|
+
}
|
|
@@ -1,28 +1,18 @@
|
|
|
1
1
|
import postcss from 'postcss';
|
|
2
|
-
|
|
3
|
-
const matches = Array.from(selector.matchAll(/\.([\w-]+)/g));
|
|
4
|
-
return matches.map((results) => results[1]);
|
|
5
|
-
}
|
|
6
|
-
function getRootClass(selector) {
|
|
7
|
-
const matches = selector.match(/^\.([\w-]+).*$/);
|
|
8
|
-
if (matches === null) {
|
|
9
|
-
return undefined;
|
|
10
|
-
}
|
|
11
|
-
return matches[1];
|
|
12
|
-
}
|
|
2
|
+
import { extractClasses, extractRootClass, extractSelectors, } from './get-class-to-styles/index.js';
|
|
13
3
|
export function getClassToStyles(file) {
|
|
14
4
|
const classToStyles = new Map();
|
|
15
5
|
function processRule(node) {
|
|
16
|
-
const
|
|
6
|
+
const selectors = extractSelectors(node.selector);
|
|
17
7
|
const clone = node.clone();
|
|
18
|
-
|
|
19
|
-
const containerClass =
|
|
8
|
+
selectors.forEach((selector) => {
|
|
9
|
+
const containerClass = extractRootClass(selector);
|
|
20
10
|
if (containerClass === undefined) {
|
|
21
11
|
return;
|
|
22
12
|
}
|
|
23
13
|
clone.selector = selector;
|
|
24
14
|
const data = {
|
|
25
|
-
classes:
|
|
15
|
+
classes: extractClasses(selector),
|
|
26
16
|
location: {
|
|
27
17
|
end: node.source.end,
|
|
28
18
|
start: node.source.start,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './processor.js';
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
function extractClasses(value) {
|
|
2
|
+
return value.split(/\s+/).filter(Boolean);
|
|
3
|
+
}
|
|
4
|
+
export class Processor {
|
|
5
|
+
classes = new Set();
|
|
6
|
+
errors = [];
|
|
7
|
+
print() {
|
|
8
|
+
const { classes, errors } = this;
|
|
9
|
+
return {
|
|
10
|
+
classes: Array.from(classes),
|
|
11
|
+
errors,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
processConcatStatement(nodeValue) {
|
|
15
|
+
nodeValue.parts.forEach((part) => {
|
|
16
|
+
switch (part.type) {
|
|
17
|
+
case 'MustacheStatement': {
|
|
18
|
+
this.processMustacheStatement(part);
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
case 'TextNode': {
|
|
22
|
+
this.processTextNode(part);
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
processMustacheStatement(nodeValue) {
|
|
29
|
+
switch (nodeValue.path.type) {
|
|
30
|
+
case 'PathExpression': {
|
|
31
|
+
switch (nodeValue.path.original) {
|
|
32
|
+
case 'if':
|
|
33
|
+
case 'unless': {
|
|
34
|
+
if (nodeValue.params[1]?.type === 'StringLiteral') {
|
|
35
|
+
this.processStringLiteral(nodeValue.params[1]);
|
|
36
|
+
}
|
|
37
|
+
if (nodeValue.params[2]?.type === 'StringLiteral') {
|
|
38
|
+
this.processStringLiteral(nodeValue.params[2]);
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
default: {
|
|
43
|
+
const isLocalClass = nodeValue.path.original.startsWith('styles.');
|
|
44
|
+
if (!isLocalClass) {
|
|
45
|
+
this.errors.push(`Could not analyze {{${nodeValue.path.original}}} in template, line ${nodeValue.loc.start.line}.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
case 'StringLiteral': {
|
|
52
|
+
this.processStringLiteral(nodeValue.path);
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
processStringLiteral(nodeValue) {
|
|
58
|
+
const classNames = extractClasses(nodeValue.original);
|
|
59
|
+
classNames.forEach((className) => {
|
|
60
|
+
this.classes.add(className);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
processTextNode(nodeValue) {
|
|
64
|
+
const classNames = extractClasses(nodeValue.chars);
|
|
65
|
+
classNames.forEach((className) => {
|
|
66
|
+
this.classes.add(className);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -1,48 +1,7 @@
|
|
|
1
1
|
import { AST } from '@codemod-utils/ast-template';
|
|
2
|
+
import { Processor } from './get-classes/index.js';
|
|
2
3
|
export function getClasses(file) {
|
|
3
|
-
const
|
|
4
|
-
const errors = [];
|
|
5
|
-
function processMustacheStatement(nodeValue) {
|
|
6
|
-
switch (nodeValue.path.type) {
|
|
7
|
-
case 'PathExpression': {
|
|
8
|
-
switch (nodeValue.path.original) {
|
|
9
|
-
case 'if':
|
|
10
|
-
case 'unless': {
|
|
11
|
-
if (nodeValue.params[1]?.type === 'StringLiteral') {
|
|
12
|
-
processStringLiteral(nodeValue.params[1]);
|
|
13
|
-
}
|
|
14
|
-
if (nodeValue.params[2]?.type === 'StringLiteral') {
|
|
15
|
-
processStringLiteral(nodeValue.params[2]);
|
|
16
|
-
}
|
|
17
|
-
break;
|
|
18
|
-
}
|
|
19
|
-
default: {
|
|
20
|
-
const isLocalClass = nodeValue.path.original.startsWith('styles.');
|
|
21
|
-
if (!isLocalClass) {
|
|
22
|
-
errors.push(`Could not analyze {{${nodeValue.path.original}}} in template, line ${nodeValue.loc.start.line}.`);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
break;
|
|
27
|
-
}
|
|
28
|
-
case 'StringLiteral': {
|
|
29
|
-
processStringLiteral(nodeValue.path);
|
|
30
|
-
break;
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
function processStringLiteral(nodeValue) {
|
|
35
|
-
const classNames = nodeValue.original.split(/\s+/).filter(Boolean);
|
|
36
|
-
classNames.forEach((className) => {
|
|
37
|
-
classes.add(className);
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
function processTextNode(nodeValue) {
|
|
41
|
-
const classNames = nodeValue.chars.split(/\s+/).filter(Boolean);
|
|
42
|
-
classNames.forEach((className) => {
|
|
43
|
-
classes.add(className);
|
|
44
|
-
});
|
|
45
|
-
}
|
|
4
|
+
const processor = new Processor();
|
|
46
5
|
const traverse = AST.traverse();
|
|
47
6
|
traverse(file, {
|
|
48
7
|
AttrNode(node) {
|
|
@@ -51,33 +10,19 @@ export function getClasses(file) {
|
|
|
51
10
|
}
|
|
52
11
|
switch (node.value.type) {
|
|
53
12
|
case 'ConcatStatement': {
|
|
54
|
-
node.value
|
|
55
|
-
switch (part.type) {
|
|
56
|
-
case 'MustacheStatement': {
|
|
57
|
-
processMustacheStatement(part);
|
|
58
|
-
break;
|
|
59
|
-
}
|
|
60
|
-
case 'TextNode': {
|
|
61
|
-
processTextNode(part);
|
|
62
|
-
break;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
});
|
|
13
|
+
processor.processConcatStatement(node.value);
|
|
66
14
|
break;
|
|
67
15
|
}
|
|
68
16
|
case 'MustacheStatement': {
|
|
69
|
-
processMustacheStatement(node.value);
|
|
17
|
+
processor.processMustacheStatement(node.value);
|
|
70
18
|
break;
|
|
71
19
|
}
|
|
72
20
|
case 'TextNode': {
|
|
73
|
-
processTextNode(node.value);
|
|
21
|
+
processor.processTextNode(node.value);
|
|
74
22
|
break;
|
|
75
23
|
}
|
|
76
24
|
}
|
|
77
25
|
},
|
|
78
26
|
});
|
|
79
|
-
return
|
|
80
|
-
classes: Array.from(classes),
|
|
81
|
-
errors,
|
|
82
|
-
};
|
|
27
|
+
return processor.print();
|
|
83
28
|
}
|