Skip to content

Commit 41119a0

Browse files
committed
- Add document.lang
- Infer syntax from source code - Support for more extensions
1 parent eacae3e commit 41119a0

File tree

14 files changed

+454
-232
lines changed

14 files changed

+454
-232
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ PostCSS Syntax
1111
title="Philosopher’s stone, logo of PostCSS"
1212
src="http://postcss.github.io/postcss/logo.svg">
1313

14-
postcss-syntax can automatically switch the required [PostCSS](https://github.com/postcss/postcss) syntax by file extensions
14+
postcss-syntax can automatically switch the required [PostCSS](https://github.com/postcss/postcss) syntax by file extension/source
1515

1616
## Getting Started
1717

get-lang.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
"use strict";
2+
3+
// https://github.com/Microsoft/vscode/blob/master/extensions/markdown-basics/package.json
4+
// https://github.com/Microsoft/vscode/blob/master/extensions/html/package.json
5+
// https://github.com/Microsoft/vscode/blob/master/extensions/javascript/package.json
6+
// https://github.com/Microsoft/vscode/blob/master/extensions/typescript-basics/package.json
7+
// https://github.com/Microsoft/vscode/blob/master/extensions/xml/package.json
8+
// https://github.com/Microsoft/vscode/blob/master/extensions/css/package.json
9+
// https://github.com/Microsoft/vscode/blob/master/extensions/less/package.json
10+
// https://github.com/vuejs/vetur/blob/master/package.json
11+
12+
const languages = {
13+
sass: /^sass$/i,
14+
scss: /^scss$/i,
15+
less: /^less$/i,
16+
sugarss: /^s(?:ugar)?ss$/i,
17+
stylus: /^styl(?:us)?$/i,
18+
// WXSS(WeiXin Style Sheets) See: https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html
19+
// acss(AntFinancial Style Sheet) See: https://docs.alipay.com/mini/framework/acss
20+
// `*.pcss`, `*.postcss`
21+
css: /^(?:wx|\w*c)ss$/i,
22+
};
23+
24+
const extracts = {
25+
jsx: /^(?:m?[jt]sx?|es\d*|pac)$/i,
26+
// *.xslt? https://msdn.microsoft.com/en-us/library/ms764661(v=vs.85).aspx
27+
// *.Vue Single-File Component (SFC) Spec https://vue-loader.vuejs.org/spec.html
28+
// *.ux quickapp https://doc.quickapp.cn/framework/source-file.html
29+
html: /^(?:\w*html?|xht|mdoc|jsp|aspx?|volt|ejs|php|vue|ux)$/i,
30+
markdown: /^(?:m(?:ark)?d(?:ow)?n|mk?d)$/i,
31+
xsl: /^xslt?$/i,
32+
xml: /^(?:xml|xsd|ascx|atom|axml|bpmn|config|cpt|csl|csproj|csproj|user|dita|ditamap|dtd|dtml|fsproj|fxml|iml|isml|jmx|launch|menu|mxml|nuspec|opml|owl|proj|props|pt|publishsettings|pubxml|pubxml|user|rdf|rng|rss|shproj|storyboard|svg|targets|tld|tmx|vbproj|vbproj|user|vcxproj|vcxproj|filters|wsdl|wxi|wxl|wxs|xaml|xbl|xib|xlf|xliff|xpdl|xul|xoml)$/i,
33+
};
34+
35+
function sourceType (source) {
36+
source = source && source.trim();
37+
if (!source) {
38+
return;
39+
}
40+
let extract;
41+
if ((/^#!([^\r\n]+)/.test(source) && /(?:^|\s+)(?:ts-)?node(?:\.\w+)?(?:\s+|$)$/.test(RegExp.$1)) || /^("|')use strict\1;*\s*(\r?\n|$)/.test(source) || /^import(?:\s+[^;]+\s+from)?\s+("|')[^'"]+?\1;*\s*(\r?\n|$)/.test(source) || /^(?:(?:var|let|const)\s+[^;]+\s*=)?\s*(?:require|import)\(.+\)/.test(source)) {
42+
// https://en.wikipedia.org/wiki/Shebang_(Unix)
43+
// or start with strict mode
44+
// or start with import code
45+
extract = "jsx";
46+
} else if (/^<(?:!DOCTYPE\s+)?html(\s+[^<>]*)?>/i.test(source)) {
47+
extract = "html";
48+
} else if (/^<\?xml(\s+[^<>]*)?\?>/i.test(source)) {
49+
// https://msdn.microsoft.com/en-us/library/ms764661(v=vs.85).aspx
50+
if (/<xsl:\w+\b[^<>]*>/.test(source) || /<\/xsl:\w+>/i.test(source)) {
51+
extract = "xsl";
52+
} else {
53+
extract = "xml";
54+
}
55+
} else if (/^#+\s+\S+/.test(source) || /^\S+[^\r\n]*\r?\n=+(\r?\n|$)/.test(source)) {
56+
extract = "markdown";
57+
} else if (/<(\w+)(?:\s+[^<>]*)?>[\s\S]*?<\/\1>/.test(source)) {
58+
extract = "html";
59+
} else {
60+
return;
61+
}
62+
return {
63+
extract,
64+
};
65+
}
66+
67+
function extType (extName, languages) {
68+
for (const langName in languages) {
69+
if (languages[langName].test(extName)) {
70+
return langName;
71+
}
72+
}
73+
}
74+
75+
function fileType (file) {
76+
if (file && /\.(\w+)(?:[?#].*?)?$/.test(file)) {
77+
const extName = RegExp.$1;
78+
const extract = extType(extName, extracts);
79+
if (extract) {
80+
return {
81+
extract,
82+
};
83+
}
84+
const lang = extType(extName, languages);
85+
if (lang) {
86+
return {
87+
lang,
88+
};
89+
}
90+
}
91+
}
92+
93+
function getLang (opts, source) {
94+
const file = opts.from;
95+
const rules = opts.syntax.config.rules;
96+
return (rules && rules.find(
97+
rule => rule.test.test ? rule.test.test(file) : rule.test(file, source)
98+
)) || fileType(file) || sourceType(source) || {
99+
lang: "css",
100+
};
101+
}
102+
103+
module.exports = getLang;

index.js

Lines changed: 9 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,20 @@
11
"use strict";
2-
32
const stringify = require("./stringify");
43
const parse = require("./parse");
4+
55
const defaultConfig = {
6-
rules: [
7-
{
8-
test: /\.less$/i,
9-
lang: "less",
10-
},
11-
{
12-
test: /\.sass$/i,
13-
lang: "sass",
14-
},
15-
{
16-
test: /\.scss$/i,
17-
lang: "scss",
18-
},
19-
{
20-
test: /\.s(?:ugar)?ss$/i,
21-
lang: "sugarss",
22-
},
23-
{
24-
test: /\.styl(?:us)?$/i,
25-
lang: "stylus",
26-
},
27-
{
28-
// WXSS(WeiXin Style Sheets) See: https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html
29-
// acss(AntFinancial Style Sheet) See: https://docs.alipay.com/mini/framework/acss
30-
// `*.pcss`, `*.postcss`
31-
test: /\.(?:wx|\w*c)ss$/i,
32-
lang: "css",
33-
},
34-
{
35-
// *.xslt? https://msdn.microsoft.com/en-us/library/ms764661(v=vs.85).aspx
36-
// *.vue https://vue-loader.vuejs.org/spec.html
37-
// *.ux https://doc.quickapp.cn/framework/source-file.html
38-
// `*.xml` Just for fault tolerance, XML is not supported except XSLT
39-
// `*.htm`, `*.*htm`
40-
// `*.html`, `*.*html`
41-
test: /\.(?:\w*html?|x(?:ht|ml|slt?)|markdown|md|php|vue|ux)$/i,
42-
extract: "html",
43-
},
44-
{
45-
test: /\.(?:markdown|md)$/i,
46-
extract: "markdown",
47-
},
48-
{
49-
test: /\.(?:m?[jt]sx?|es\d*|pac)$/i,
50-
extract: "jsx",
51-
},
52-
],
536
postcss: "css",
547
stylus: "css",
8+
xml: "html",
9+
xsl: "html",
5510
};
5611

12+
try {
13+
require.resolve("postcss-jsx");
14+
} catch (ex) {
15+
defaultConfig.jsx = "styled";
16+
}
17+
5718
function initSyntax (syntax) {
5819
syntax.stringify = stringify.bind(syntax);
5920
syntax.parse = parse.bind(syntax);

package.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "postcss-syntax",
3-
"version": "0.28.0",
3+
"version": "0.30.0",
44
"description": "Automatically switch PostCSS syntax by file extensions",
55
"repository": {
66
"type": "git",
@@ -39,12 +39,13 @@
3939
"mocha": "^5.2.0",
4040
"nyc": "^12.0.2",
4141
"postcss": "^6.0.22",
42-
"postcss-html": ">=0.28.0",
43-
"postcss-jsx": ">=0.28.0",
42+
"postcss-html": ">=0.30.0",
43+
"postcss-jsx": ">=0.30.0",
4444
"postcss-less": "^2.0.0",
45-
"postcss-markdown": ">=0.28.0",
45+
"postcss-markdown": ">=0.30.0",
4646
"postcss-safe-parser": "^3.0.1",
4747
"postcss-scss": "^1.0.5",
48+
"proxyquire": "^2.0.1",
4849
"sugarss": "^1.0.1"
4950
}
5051
}

parse.js

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
const parser = require("./parser");
44
const processor = require("./processor");
5+
const getLang = require("./get-lang");
56

67
function parse (source, opts) {
78
if (!opts) {
@@ -10,13 +11,19 @@ function parse (source, opts) {
1011
if (!opts.syntax) {
1112
opts.syntax = this;
1213
}
13-
let rules = opts.syntax.config.rules;
14-
const file = opts.from ? opts.from.replace(/^(\w+:\/\/.*?\.\w+)(?:[?#].*)?$/, "$1") : "";
15-
rules = rules && rules.filter(
16-
rule => rule.test.test(file)
17-
);
14+
1815
source = source.toString();
19-
return processor(source, rules, opts) || parser(source, rules, opts);
16+
const syntax = getLang(opts, source);
17+
const syntaxOpts = Object.assign({}, opts, syntax.opts);
18+
let root;
19+
if (syntax.extract) {
20+
root = processor(source, syntax.extract, syntaxOpts);
21+
root.source.lang = syntax.extract;
22+
} else {
23+
root = parser(source, syntax.lang, syntaxOpts);
24+
root.source.lang = syntax.lang;
25+
}
26+
return root;
2027
}
2128

2229
module.exports = parse;

parser.js

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,8 @@
33
const getSyntax = require("./get-syntax");
44
const patch = require("./patch-postcss");
55

6-
const extToLang = {
7-
"sugarss": /^s(?:ugar)?ss$/i,
8-
"stylus": /^styl(?:us)?$/i,
9-
// WXSS(WeiXin Style Sheets) See: https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html
10-
// acss(AntFinancial Style Sheet) See: https://docs.alipay.com/mini/framework/acss
11-
// `*.pcss`, `*.postcss`
12-
"css": /^(?:wx|\w*c)ss$/i,
13-
"html": /^(?:[sx]?html?|[sx]ht|vue|ux|php)$/i,
14-
"markdown": /^(?:markdown|md)$/i,
15-
"jsx": /^(?:m?[jt]sx?|es\d*|pac)$/i,
16-
};
17-
18-
function parser (source, rules, opts) {
6+
function parser (source, lang, opts) {
197
patch();
20-
let lang = rules.find(rule => rule.lang);
21-
if (lang) {
22-
lang = lang.lang;
23-
} else if (opts.from && /\.(\w+)$/.test(opts.from)) {
24-
lang = RegExp.$1.toLowerCase();
25-
if (!/^(?:css|less|sass|scss)$/i.test(lang)) {
26-
for (const langName in extToLang) {
27-
if (extToLang[langName].test(lang)) {
28-
lang = langName;
29-
break;
30-
}
31-
}
32-
}
33-
} else {
34-
lang = "css";
35-
}
368

379
const syntax = getSyntax(lang, opts);
3810
const root = syntax.parse(source, opts);

processor.js

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,27 @@
22

33
const parseStyle = require("./parse-style");
44

5-
function processor (source, rules, opts) {
6-
rules = rules && rules.filter(rule => rule.extract).map(rule => {
7-
if (typeof rule.extract === "string") {
8-
rule.extract = rule.extract.toLowerCase().replace(/^(postcss-)?/i, "postcss-");
9-
rule.extract = require(rule.extract + "/extract");
10-
}
11-
return rule;
12-
});
5+
function getSyntax (config, syntax) {
6+
if (typeof syntax !== "string") {
7+
return syntax;
8+
}
9+
let syntaxConfig = config[syntax];
1310

14-
if (!rules || !rules.length) {
15-
return;
11+
if (syntaxConfig) {
12+
syntaxConfig = getSyntax(config, syntaxConfig);
13+
} else {
14+
syntaxConfig = {
15+
extract: require(syntax.toLowerCase().replace(/^(postcss-)?/i, "postcss-") + "/extract"),
16+
};
17+
config[syntax] = syntaxConfig;
1618
}
17-
const styles = rules.reduce(
18-
(styles, rule) => (
19-
rule.extract(source, Object.assign({}, opts, rule.opts), styles) || styles
20-
),
21-
[]
22-
);
2319

20+
return syntaxConfig;
21+
}
22+
23+
function processor (source, lang, opts) {
24+
const syntax = getSyntax(opts.syntax.config, lang);
25+
const styles = (syntax.extract || syntax)(source, opts) || [];
2426
return parseStyle(source, opts, styles);
2527
}
2628

syntax.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
const stringify = require("./stringify");
33
const parseStyle = require("./parse-style");
44

5-
module.exports = (extract, defaultConfig) => {
6-
defaultConfig = defaultConfig || {
5+
module.exports = (extract, lang) => {
6+
const defaultConfig = {
77
postcss: "css",
88
stylus: "css",
9+
xml: "html",
10+
xsl: "html",
911
};
1012
function parse (source, opts) {
1113
source = source.toString();
@@ -15,7 +17,9 @@ module.exports = (extract, defaultConfig) => {
1517
if (!opts.syntax) {
1618
opts.syntax = this;
1719
}
18-
return parseStyle(source, opts, extract(source, opts));
20+
const document = parseStyle(source, opts, extract(source, opts));
21+
document.source.lang = lang;
22+
return document;
1923
}
2024

2125
function initSyntax (syntax) {

test/custom.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"use strict";
2+
const expect = require("chai").expect;
3+
const Module = require("module");
4+
const _findPath = Module._findPath;
5+
let syntax;
6+
7+
describe("custom language", () => {
8+
before(() => {
9+
Module._findPath = (request, paths, isMain) => {
10+
if (request === "postcss-jsx") {
11+
return null;
12+
}
13+
return _findPath.apply(Module, [request, paths, isMain]);
14+
};
15+
16+
delete require.cache[require.resolve("../")];
17+
syntax = require("../");
18+
});
19+
after(() => {
20+
Module._findPath = Module._findPath;
21+
});
22+
it("custom.postcss", () => {
23+
const code = "a { display: block; }";
24+
const document = syntax({
25+
rules: [
26+
{
27+
// custom language for file extension
28+
test: () => true,
29+
lang: "postcss",
30+
},
31+
],
32+
postcss: "css",
33+
}).parse(code, {
34+
from: "custom.postcss",
35+
});
36+
expect(document.source).to.haveOwnProperty("lang", "postcss");
37+
expect(document.toString()).to.equal(code);
38+
});
39+
});

test/index.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,6 @@ describe("api", () => {
3030
it("loader return null", () => {
3131
const root = syntax({
3232
rules: [
33-
{
34-
test: /.*/,
35-
extract: () => null,
36-
},
3733
{
3834
test: /.*/,
3935
extract: "html",

0 commit comments

Comments
 (0)