Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ PostCSS Syntax
title="Philosopher’s stone, logo of PostCSS"
src="http://postcss.github.io/postcss/logo.svg">

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

## Getting Started

Expand Down
104 changes: 104 additions & 0 deletions get-lang.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"use strict";

// https://github.com/Microsoft/vscode/blob/master/extensions/markdown-basics/package.json
// https://github.com/Microsoft/vscode/blob/master/extensions/html/package.json
// https://github.com/Microsoft/vscode/blob/master/extensions/javascript/package.json
// https://github.com/Microsoft/vscode/blob/master/extensions/typescript-basics/package.json
// https://github.com/Microsoft/vscode/blob/master/extensions/xml/package.json
// https://github.com/Microsoft/vscode/blob/master/extensions/css/package.json
// https://github.com/Microsoft/vscode/blob/master/extensions/less/package.json
// https://github.com/vuejs/vetur/blob/master/package.json

const languages = {
sass: /^sass$/i,
scss: /^scss$/i,
less: /^less$/i,
sugarss: /^s(?:ugar)?ss$/i,
stylus: /^styl(?:us)?$/i,
// WXSS(WeiXin Style Sheets) See: https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html
// acss(AntFinancial Style Sheet) See: https://docs.alipay.com/mini/framework/acss
// `*.pcss`, `*.postcss`
css: /^(?:wx|\w*c)ss$/i,
};

const extracts = {
jsx: /^(?:m?[jt]sx?|es\d*|pac)$/i,
// *.xslt? XSLT https://msdn.microsoft.com/en-us/library/ms764661(v=vs.85).aspx
// *.vue VUE https://vue-loader.vuejs.org/spec.html
// *.ux quickapp https://doc.quickapp.cn/framework/source-file.html
// *.wpy WePY https://github.com/Tencent/wepy/blob/master/docs/md/doc.md#wpy文件说明
html: /^(?:\w*html?|xht|mdoc|jsp|aspx?|volt|ejs|php|vue|ux|wpy)$/i,
markdown: /^(?:m(?:ark)?d(?:ow)?n|mk?d)$/i,
xsl: /^xslt?$/i,
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,
};

function sourceType (source) {
source = source && source.trim();
if (!source) {
return;
}
let extract;
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)) {
// https://en.wikipedia.org/wiki/Shebang_(Unix)
// or start with strict mode
// or start with import code
extract = "jsx";
} else if (/^<(?:!DOCTYPE\s+)?html(\s+[^<>]*)?>/i.test(source)) {
extract = "html";
} else if (/^<\?xml(\s+[^<>]*)?\?>/i.test(source)) {
// https://msdn.microsoft.com/en-us/library/ms764661(v=vs.85).aspx
if (/<xsl:\w+\b[^<>]*>/.test(source) || /<\/xsl:\w+>/i.test(source)) {
extract = "xsl";
} else {
extract = "xml";
}
} else if (/^#+\s+\S+/.test(source) || /^\S+[^\r\n]*\r?\n=+(\r?\n|$)/.test(source)) {
extract = "markdown";
} else if (/<(\w+)(?:\s+[^<>]*)?>[\s\S]*?<\/\1>/.test(source)) {
extract = "html";
} else {
return;
}
return {
extract,
};
}

function extType (extName, languages) {
for (const langName in languages) {
if (languages[langName].test(extName)) {
return langName;
}
}
}

function fileType (file) {
if (file && /\.(\w+)(?:[?#].*?)?$/.test(file)) {
const extName = RegExp.$1;
const extract = extType(extName, extracts);
if (extract) {
return {
extract,
};
}
const lang = extType(extName, languages);
if (lang) {
return {
lang,
};
}
}
}

function getLang (opts, source) {
const file = opts.from;
const rules = opts.syntax.config.rules;
return (rules && rules.find(
rule => rule.test.test ? rule.test.test(file) : rule.test(file, source)
)) || fileType(file) || sourceType(source) || {
lang: "css",
};
}

module.exports = getLang;
58 changes: 10 additions & 48 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,59 +1,21 @@
"use strict";

const stringify = require("./stringify");
const parse = require("./parse");

const defaultConfig = {
rules: [
{
test: /\.less$/i,
lang: "less",
},
{
test: /\.sass$/i,
lang: "sass",
},
{
test: /\.scss$/i,
lang: "scss",
},
{
test: /\.s(?:ugar)?ss$/i,
lang: "sugarss",
},
{
test: /\.styl(?:us)?$/i,
lang: "stylus",
},
{
// WXSS(WeiXin Style Sheets) See: https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html
// acss(AntFinancial Style Sheet) See: https://docs.alipay.com/mini/framework/acss
// `*.pcss`, `*.postcss`
test: /\.(?:wx|\w*c)ss$/i,
lang: "css",
},
{
// *.xslt? https://msdn.microsoft.com/en-us/library/ms764661(v=vs.85).aspx
// *.vue https://vue-loader.vuejs.org/spec.html
// *.ux https://doc.quickapp.cn/framework/source-file.html
// `*.xml` Just for fault tolerance, XML is not supported except XSLT
// `*.htm`, `*.*htm`
// `*.html`, `*.*html`
test: /\.(?:\w*html?|x(?:ht|ml|slt?)|markdown|md|php|vue|ux)$/i,
extract: "html",
},
{
test: /\.(?:markdown|md)$/i,
extract: "markdown",
},
{
test: /\.(?:m?[jt]sx?|es\d*|pac)$/i,
extract: "jsx",
},
],
postcss: "css",
stylus: "css",
babel: "jsx",
xml: "html",
xsl: "html",
};

try {
require.resolve("postcss-jsx");
} catch (ex) {
defaultConfig.jsx = "styled";
}

function initSyntax (syntax) {
syntax.stringify = stringify.bind(syntax);
syntax.parse = parse.bind(syntax);
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "postcss-syntax",
"version": "0.28.0",
"version": "0.30.0",
"description": "Automatically switch PostCSS syntax by file extensions",
"repository": {
"type": "git",
Expand Down Expand Up @@ -39,12 +39,13 @@
"mocha": "^5.2.0",
"nyc": "^12.0.2",
"postcss": "^6.0.22",
"postcss-html": ">=0.28.0",
"postcss-jsx": ">=0.28.0",
"postcss-html": ">=0.30.0",
"postcss-jsx": ">=0.30.0",
"postcss-less": "^2.0.0",
"postcss-markdown": ">=0.28.0",
"postcss-markdown": ">=0.30.0",
"postcss-safe-parser": "^3.0.1",
"postcss-scss": "^1.0.5",
"proxyquire": "^2.0.1",
"sugarss": "^1.0.1"
}
}
19 changes: 13 additions & 6 deletions parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const parser = require("./parser");
const processor = require("./processor");
const getLang = require("./get-lang");

function parse (source, opts) {
if (!opts) {
Expand All @@ -10,13 +11,19 @@ function parse (source, opts) {
if (!opts.syntax) {
opts.syntax = this;
}
let rules = opts.syntax.config.rules;
const file = opts.from ? opts.from.replace(/^(\w+:\/\/.*?\.\w+)(?:[?#].*)?$/, "$1") : "";
rules = rules && rules.filter(
rule => rule.test.test(file)
);

source = source.toString();
return processor(source, rules, opts) || parser(source, rules, opts);
const syntax = getLang(opts, source);
const syntaxOpts = Object.assign({}, opts, syntax.opts);
let root;
if (syntax.extract) {
root = processor(source, syntax.extract, syntaxOpts);
root.source.lang = syntax.extract;
} else {
root = parser(source, syntax.lang, syntaxOpts);
root.source.lang = syntax.lang;
}
return root;
}

module.exports = parse;
30 changes: 1 addition & 29 deletions parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,8 @@
const getSyntax = require("./get-syntax");
const patch = require("./patch-postcss");

const extToLang = {
"sugarss": /^s(?:ugar)?ss$/i,
"stylus": /^styl(?:us)?$/i,
// WXSS(WeiXin Style Sheets) See: https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html
// acss(AntFinancial Style Sheet) See: https://docs.alipay.com/mini/framework/acss
// `*.pcss`, `*.postcss`
"css": /^(?:wx|\w*c)ss$/i,
"html": /^(?:[sx]?html?|[sx]ht|vue|ux|php)$/i,
"markdown": /^(?:markdown|md)$/i,
"jsx": /^(?:m?[jt]sx?|es\d*|pac)$/i,
};

function parser (source, rules, opts) {
function parser (source, lang, opts) {
patch();
let lang = rules.find(rule => rule.lang);
if (lang) {
lang = lang.lang;
} else if (opts.from && /\.(\w+)$/.test(opts.from)) {
lang = RegExp.$1.toLowerCase();
if (!/^(?:css|less|sass|scss)$/i.test(lang)) {
for (const langName in extToLang) {
if (extToLang[langName].test(lang)) {
lang = langName;
break;
}
}
}
} else {
lang = "css";
}

const syntax = getSyntax(lang, opts);
const root = syntax.parse(source, opts);
Expand Down
34 changes: 18 additions & 16 deletions processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,27 @@

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

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

if (!rules || !rules.length) {
return;
if (syntaxConfig) {
syntaxConfig = getSyntax(config, syntaxConfig);
} else {
syntaxConfig = {
extract: require(syntax.toLowerCase().replace(/^(postcss-)?/i, "postcss-") + "/extract"),
};
config[syntax] = syntaxConfig;
}
const styles = rules.reduce(
(styles, rule) => (
rule.extract(source, Object.assign({}, opts, rule.opts), styles) || styles
),
[]
);

return syntaxConfig;
}

function processor (source, lang, opts) {
const syntax = getSyntax(opts.syntax.config, lang);
const styles = (syntax.extract || syntax)(source, opts) || [];
return parseStyle(source, opts, styles);
}

Expand Down
11 changes: 8 additions & 3 deletions syntax.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
const stringify = require("./stringify");
const parseStyle = require("./parse-style");

module.exports = (extract, defaultConfig) => {
defaultConfig = defaultConfig || {
module.exports = (extract, lang) => {
const defaultConfig = {
postcss: "css",
stylus: "css",
babel: "jsx",
xml: "html",
xsl: "html",
};
function parse (source, opts) {
source = source.toString();
Expand All @@ -15,7 +18,9 @@ module.exports = (extract, defaultConfig) => {
if (!opts.syntax) {
opts.syntax = this;
}
return parseStyle(source, opts, extract(source, opts));
const document = parseStyle(source, opts, extract(source, opts));
document.source.lang = lang;
return document;
}

function initSyntax (syntax) {
Expand Down
39 changes: 39 additions & 0 deletions test/custom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use strict";
const expect = require("chai").expect;
const Module = require("module");
const _findPath = Module._findPath;
let syntax;

describe("custom language", () => {
before(() => {
Module._findPath = (request, paths, isMain) => {
if (request === "postcss-jsx") {
return null;
}
return _findPath.apply(Module, [request, paths, isMain]);
};

delete require.cache[require.resolve("../")];
syntax = require("../");
});
after(() => {
Module._findPath = Module._findPath;
});
it("custom.postcss", () => {
const code = "a { display: block; }";
const document = syntax({
rules: [
{
// custom language for file extension
test: () => true,
lang: "postcss",
},
],
postcss: "css",
}).parse(code, {
from: "custom.postcss",
});
expect(document.source).to.haveOwnProperty("lang", "postcss");
expect(document.toString()).to.equal(code);
});
});
Loading