Skip to content
This repository was archived by the owner on Feb 16, 2021. It is now read-only.

Commit 347775b

Browse files
authored
Merge pull request #6 from ezavile/feature/generate-ts-file
feature/generate-ts-file
2 parents fa0404f + a59b9cc commit 347775b

17 files changed

+2150
-883
lines changed

.gitignore

+7
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,10 @@ node_modules/
1212

1313
dist/
1414
.vscode/
15+
16+
# Coverage directory used by tools like istanbul
17+
coverage
18+
.nyc_output
19+
coverage.*
20+
21+
.env

.travis.yml

+3
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ node_js:
1111
- '7'
1212
before_script:
1313
- npm prune
14+
script:
15+
- npm run test
1416
after_success:
1517
- npm run semantic-release
18+
- npm run report-coverage
1619
branches:
1720
except:
1821
- /^v\d+\.\d+\.\d+$/

.yarnrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
save-prefix false

package.json

+52-12
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,18 @@
44
"description": "PostCSS plugin to create a typescript file by each CSS file",
55
"main": "dist/postcss-typescript-css",
66
"scripts": {
7+
"clean": "rimraf dist coverage *.log",
78
"commit": "git-cz",
8-
"clean": "rimraf dist",
9+
"copyFakePostcssFile": "cpx \"src/spec/postcss/*\" dist/spec/postcss/",
910
"compile": "npm run clean && tsc",
1011
"compile:watch": "tsc --watch",
1112
"lint": "npm run tslint \"src/**/*.ts\"",
13+
"test": "nyc ava",
14+
"test:watch": "ava --watch",
1215
"tslint": "tslint --project tsconfig.json",
16+
"precommit": "npm test",
17+
"pretest": "npm run lint && npm run clean && tsc && npm run copyFakePostcssFile",
18+
"report-coverage": "codecov -f coverage/lcov.info",
1319
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
1420
},
1521
"repository": "[email protected]:ezavile/postcss-typescript-css.git",
@@ -25,20 +31,54 @@
2531
"url": "https://github.com/ezavile/postcss-typescript-css/issues"
2632
},
2733
"homepage": "https://github.com/ezavile/postcss-typescript-css",
34+
"ava": {
35+
"files": [
36+
"dist/spec/*.spec.js"
37+
],
38+
"source": [
39+
"dist/**/*.js"
40+
]
41+
},
42+
"nyc": {
43+
"lines": 100,
44+
"statements": 100,
45+
"functions": 100,
46+
"branches": 100,
47+
"include": [
48+
"dist/**/*.js"
49+
],
50+
"exclude": [
51+
"dist/**/*.spec.js",
52+
"dist/namespace/*.js"
53+
],
54+
"reporter": [
55+
"lcov",
56+
"text"
57+
],
58+
"cache": true,
59+
"all": true,
60+
"check-coverage": true
61+
},
2862
"devDependencies": {
29-
"@types/core-js": "^0.9.41",
30-
"@types/node": "^7.0.12",
31-
"commitizen": "^2.9.6",
32-
"core-js": "^2.4.1",
33-
"cz-conventional-changelog": "^2.0.0",
34-
"rimraf": "^2.6.1",
35-
"semantic-release": "^6.3.2",
36-
"tslint": "^5.1.0",
37-
"typescript": "^2.2.2"
63+
"@types/core-js": "0.9.41",
64+
"@types/node": "7.0.12",
65+
"ava": "0.19.1",
66+
"codecov": "2.1.0",
67+
"commitizen": "2.9.6",
68+
"core-js": "2.4.1",
69+
"cpx": "1.5.0",
70+
"cz-conventional-changelog": "2.0.0",
71+
"husky": "0.13.3",
72+
"nyc": "10.2.0",
73+
"rimraf": "2.6.1",
74+
"semantic-release": "6.3.2",
75+
"tslint": "5.1.0",
76+
"typescript": "2.2.2"
3877
},
3978
"dependencies": {
40-
"global": "^4.3.2",
41-
"semantic-release-cli": "^3.0.3"
79+
"camelcase": "4.1.0",
80+
"postcss": "5.2.17",
81+
"postcss-selector-parser": "2.2.3"
4282
},
4383
"config": {
4484
"commitizen": {

src/buildFile.ts

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const camelCase = require('camelcase');
2+
const path = require('path');
3+
4+
import { PostcssTypescriptCss } from './namespace/PostcssTypescriptCss';
5+
6+
const build = (file: PostcssTypescriptCss.Options) => {
7+
const filename = path.basename(file.cssFileName, '.postcss');
8+
return (
9+
`export const ${filename}Style = {
10+
${
11+
file.content instanceof Array ? (
12+
file.content.map((c: string) => {
13+
return ` ${camelCase(c)}: '${c}'\n`;
14+
})
15+
) : (
16+
Object.keys(file.content).map((c: string) => {
17+
return ` ${camelCase(c)}: '${(<{[key: string]: string }>file.content)[c]}'\n`;
18+
})
19+
)
20+
},
21+
};
22+
`
23+
);
24+
};
25+
26+
export default build;

src/namespace/PostcssTypescriptCss.ts

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export namespace PostcssTypescriptCss {
2+
export interface Options {
3+
cssFileName: string;
4+
content: { [key: string]: string } | string[];
5+
}
6+
}

src/postcss-typescript-css.ts

+40-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,42 @@
1-
const postcssTypescriptCss = () => {
2-
return 'Welcome to postcss-typescript-css';
3-
};
1+
import * as postcss from 'postcss';
2+
const parser = require('postcss-selector-parser');
3+
4+
import { PostcssTypescriptCss } from './namespace/PostcssTypescriptCss';
5+
import saveFile from './saveFile';
6+
7+
const postcssTypescriptCss = postcss.plugin<PostcssTypescriptCss.Options>('postcss-typescript-css', (opts) => {
8+
return (css, result) => {
9+
return new Promise((resolve, reject) => {
10+
if (opts) {
11+
if (!opts.cssFileName) reject('The property cssFileName can not be null');
12+
if (!opts.content) reject('The property content can not be null');
13+
} else {
14+
const classes = new Set();
15+
css.walk((node: postcss.Rule) => {
16+
if (node.type === 'rule') {
17+
parser((selectors: postcss.Rule) => {
18+
selectors.walk((selector: any) => {
19+
if (selector.type === 'class') {
20+
classes.add(selector.value);
21+
}
22+
});
23+
}).process(node.selector).result;
24+
}
25+
});
26+
opts = {
27+
cssFileName: css.source.input.file,
28+
content: [...classes],
29+
};
30+
}
31+
saveFile(opts)
32+
.then(() => {
33+
resolve();
34+
})
35+
.catch((err) => {
36+
reject(err);
37+
});
38+
});
39+
};
40+
});
441

542
export = postcssTypescriptCss;

src/saveFile.ts

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { writeFileSync } from 'fs';
2+
const path = require('path');
3+
4+
import { PostcssTypescriptCss } from './namespace/PostcssTypescriptCss';
5+
import build from './buildFile';
6+
7+
const saveFile = (file: PostcssTypescriptCss.Options) => {
8+
return new Promise((resolve, reject) => {
9+
try {
10+
const dirname = path.dirname(file.cssFileName);
11+
const filename = path.basename(file.cssFileName, '.postcss');
12+
writeFileSync(`${dirname}/${filename}.ts`, build(file));
13+
resolve(true);
14+
} catch (err) {
15+
reject(err.toString());
16+
}
17+
});
18+
};
19+
20+
export default saveFile;

src/spec/buildFile.spec.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import test from 'ava';
2+
import build from './../buildFile';
3+
4+
test('should create a string with the classes as a properties', t => {
5+
const template = build({
6+
cssFileName: 'fakeComponent',
7+
content: ['.FakeComponent', '.FakeComponent-descendentName', '.FakeComponent--modifierName']
8+
});
9+
t.true(template.includes('fakeComponentStyle'));
10+
t.true(template.includes('fakeComponent:'));
11+
t.true(template.includes('fakeComponentDescendentName:'));
12+
t.true(template.includes('fakeComponentModifierName:'));
13+
});
14+
15+
test('should create a string with transformed classes', t => {
16+
const template = build({
17+
cssFileName: 'fakeComponentModules',
18+
content: {
19+
fakeComponentModules: '_FakeComponentModules_h7423_1',
20+
fakeComponentModulesDescendentName: '_FakeComponentModules-descendentName_h7423_1',
21+
fakeComponentModulesModifierName: '_FakeComponentModules--modifierName_h7423_1'
22+
}
23+
});
24+
t.true(template.includes('fakeComponentModulesStyle'));
25+
t.true(template.includes('fakeComponentModules:'));
26+
t.true(template.includes('fakeComponentModulesDescendentName:'));
27+
t.true(template.includes('fakeComponentModulesModifierName:'));
28+
});
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import test, { TestContext } from 'ava';
2+
import { readFileSync } from 'fs';
3+
const path = require('path');
4+
import * as postcss from 'postcss';
5+
6+
import { PostcssTypescriptCss } from '../namespace/PostcssTypescriptCss';
7+
import * as plugin from '../postcss-typescript-css';
8+
9+
function run(t: TestContext, input: {css: string, from: string}, opts?: PostcssTypescriptCss.Options) {
10+
return postcss([ plugin(opts) ]).process(input.css, { from: input.from })
11+
.then((result) => {
12+
let fakeComponentTS;
13+
if (opts) {
14+
fakeComponentTS = readFileSync(path.join(__dirname, 'postcss/fakeComponentModules.ts'), 'utf8');
15+
t.true(fakeComponentTS.includes('fakeComponentModulesStyle'));
16+
t.true(fakeComponentTS.includes('fakeComponentModules:'));
17+
t.true(fakeComponentTS.includes('fakeComponentModulesDescendentName:'));
18+
t.true(fakeComponentTS.includes('fakeComponentModulesModifierName:'));
19+
} else {
20+
fakeComponentTS = readFileSync(path.join(__dirname, 'postcss/fakeComponent.ts'), 'utf8');
21+
t.true(fakeComponentTS.includes('fakeComponentStyle'));
22+
t.true(fakeComponentTS.includes('fakeComponent:'));
23+
t.true(fakeComponentTS.includes('fakeComponentDescendentName:'));
24+
t.true(fakeComponentTS.includes('fakeComponentModifierName:'));
25+
}
26+
})
27+
.catch((err: string) => {
28+
if (opts) {
29+
if (!opts.cssFileName) {
30+
t.is(err, 'The property cssFileName can not be null');
31+
}
32+
if (!opts.content) {
33+
t.is(err, 'The property content can not be null');
34+
}
35+
} else {
36+
t.true(err.includes('TypeError: Path must be a string'));
37+
}
38+
});
39+
}
40+
41+
test('should create a ts file', t => {
42+
const cssFile = path.join(__dirname, 'postcss/fakeComponent.postcss');
43+
const cssContent = readFileSync(cssFile, 'utf8');
44+
return run(t, { css: cssContent, from: cssFile });
45+
});
46+
47+
test('should create a ts file with postcss-modules configuration', t => {
48+
const cssFileName = path.join(__dirname, 'postcss/fakeComponentModules.postcss');
49+
const content = JSON.parse(readFileSync(path.join(__dirname, 'postcss/fakeComponentModules.json'), 'utf8'));
50+
return run(t, { css: '', from: '' }, { cssFileName, content });
51+
});
52+
53+
test('throws if cssFileName is null', t => {
54+
return run(t, { css: '', from: '' }, { cssFileName: null, content: ['.class'] });
55+
});
56+
57+
test('throws if content is null', t => {
58+
const cssFileName = path.join(__dirname, 'postcss/fakeComponentModules.postcss');
59+
return run(t, { css: '', from: '' }, { cssFileName, content: null });
60+
});
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.FakeComponent {
2+
color: green;
3+
}
4+
.FakeComponent-descendentName {
5+
color: yellow;
6+
}
7+
.FakeComponent--modifierName {
8+
color: red;
9+
}
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
.FakeComponent2 {
2+
color: green;
3+
}
4+
.FakeComponent2-descendentName {
5+
color: yellow;
6+
}
7+
.FakeComponent2--modifierName {
8+
color: red;
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"FakeComponentModules":"_FakeComponentModules_h7423_1","FakeComponentModules-descendentName":"_FakeComponentModules-descendentName_h7423_1", "FakeComponentModules--modifierName":"_FakeComponentModules--modifierName_h7423_1"}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
._FakeComponentModules_h7423_1 {
2+
color: red;
3+
}
4+
5+
._FakeComponentModules-descendentName_h7423_1 {
6+
color: yellow;
7+
}
8+
9+
._FakeComponentModules--modifierName_h7423_1 {
10+
color: green;
11+
}

src/spec/saveFile.spec.ts

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import test from 'ava';
2+
import saveFile from './../saveFile';
3+
const path = require('path');
4+
5+
6+
test('should create a new ts file', async (t) => {
7+
const cssFileName = path.join(__dirname, 'postcss/fakeComponent2.postcss');
8+
t.is(await saveFile({
9+
cssFileName,
10+
content: ['.FakeComponent2', '.FakeComponent2-descendentName', '.FakeComponent2--modifierName']
11+
}), true);
12+
const tsFile = path.join(__dirname, 'postcss/fakeComponent2.ts');
13+
const tsFileName = path.basename(tsFile, '.ts');
14+
t.is(tsFileName, 'fakeComponent2');
15+
});
16+
17+
test('should create a new ts file with transformed classes', async (t) => {
18+
const cssFileName = path.join(__dirname, 'postcss/fakeComponentModules.postcss');
19+
t.is(await saveFile({
20+
cssFileName,
21+
content: {
22+
fakeComponentModules: '_FakeComponentModules_h7423_1',
23+
fakeComponentModulesDescendentName: '_FakeComponentModules-descendentName_h7423_1',
24+
fakeComponentModulesModifierName: '_FakeComponentModules--modifierName_h7423_1'
25+
}
26+
}), true);
27+
const tsFile = path.join(__dirname, 'postcss/fakeComponentModules.ts');
28+
const tsFileName = path.basename(tsFile, '.ts');
29+
t.is(tsFileName, 'fakeComponentModules');
30+
});
31+
32+
test('throws if there is not a css file name', async (t) => {
33+
saveFile({
34+
cssFileName: null,
35+
content: ['.FakeComponent']
36+
}).catch((err: string) => {
37+
t.true(err.includes('TypeError: Path must be a string'));
38+
});
39+
});
40+
41+
test('throws if there is not a content', async (t) => {
42+
const cssFileName = path.join(__dirname, 'postcss/fakeComponent2.postcss');
43+
saveFile({
44+
cssFileName,
45+
content: null
46+
}).catch((err: string) => {
47+
t.true(err.includes('TypeError: Cannot convert undefined or null to object'));
48+
});
49+
});

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"compilerOptions": {
33
"outDir": "dist",
44
"rootDir": "src",
5-
"sourceMap": false,
5+
"sourceMap": true,
66
"noImplicitAny": true,
77
"declaration": false,
88
"module": "commonjs",

0 commit comments

Comments
 (0)