css-zero-lexer
Version:
Friendly and forgiving CSS lexer/parser with lots of tests. Memory-efficient and Web Worker compatible.
326 lines (316 loc) • 9.03 kB
JavaScript
import Lexx, { NodeTypes, NodeTypeKeys } from "../src/index";
import { isEqual } from "lodash";
export const resolve = (css: string, token: Array<number>) => {
if (!token || (token.length - 1) % 2 !== 0)
return "Invalid 'token' variable: " + token;
return [
NodeTypeKeys[token[0]],
...Array.apply(null, Array((token.length - 1) / 2)).map((w, pairIndex) => {
const i = 1 + pairIndex * 2;
return css.substring(token[i], token[i + 1]);
})
];
};
const resolveNodes = (css, tokens) => {
return tokens.map(token => resolve(css, token));
};
const resolveNodeNumber = (css: string, token: Array<number>) => {
return [NodeTypeKeys[token[0]], token.slice(1)];
};
const resolveNodesNumbers = (css, tokens) => {
if (!tokens || !tokens.map) return `Not structured. Was ` + tokens;
return tokens.map(token => resolveNodeNumber(css, token));
};
var cases = [
{
desc: "comment",
css: "/* comment */",
lex: [[NodeTypes.COMMENT_NODE, 2, 11]]
},
{
desc: "property",
css: "text",
lex: [[NodeTypes.PROPERTY_NODE, 0, 4]]
},
{
desc: "property, whitespace",
css: "text ", // we exclude whitespace
lex: [[NodeTypes.PROPERTY_NODE, 0, 4]]
},
{
desc: "comment and property",
css: "/*comment*/text",
lex: [[NodeTypes.COMMENT_NODE, 2, 9], [NodeTypes.PROPERTY_NODE, 11, 15]]
},
{
desc: "property and comment and property",
css: "prop/*comment*/text",
lex: [
[],
[],
[]
]
},
{
desc: "comment and whitespace and property",
css: "/*comment*/ text",
lex: [[NodeTypes.COMMENT_NODE, 2, 9], [NodeTypes.PROPERTY_NODE, 12, 16]]
},
{
desc: "whitespace, comment, property",
css: " /*comment*/ text",
lex: [[NodeTypes.COMMENT_NODE, 3, 10], [NodeTypes.PROPERTY_NODE, 13, 17]]
},
{
desc: "Whitespace, property, comment",
css: " text /*comment*/",
lex: [[NodeTypes.PROPERTY_NODE, 1, 5], [NodeTypes.COMMENT_NODE, 8, 15]]
},
{
desc: "Property then comment then whitespace",
css: " text /*comment*/ ",
lex: [[NodeTypes.PROPERTY_NODE, 1, 5], [NodeTypes.COMMENT_NODE, 8, 15]]
},
{
desc: "whitespace, one property",
css: " text;",
lex: [[NodeTypes.PROPERTY_NODE, 1, 5], [NodeTypes.CLOSE_PROPERTY]]
},
{
desc: "Whitespace then two properties",
css: " text; text;",
lex: [
[],
[],
[],
[]
]
},
{
desc: "Two properties with whitespace",
css: " text; text; ",
lex: [
[],
[],
[],
[]
]
},
{
desc: "One property, one selector",
css: " text; text{",
lex: [
[],
[],
[],
[]
]
},
{
desc: "Two properties, the second has a comment in the middle",
css: " text; te/* comment */xt",
lex: [
[],
[],
[],
[],
[]
]
},
{
desc: "Whitespace, one property, one selector",
css: " text; text{}",
lex: [
[],
[],
[],
[],
[]
]
},
{
desc: "Whitespace, comment, whitespace, and two properties",
css: " /*comment*/ text; text;",
lex: [
[],
[],
[],
[],
[]
]
},
{
desc: "Comment with property",
css: "/* comment */text",
lex: [[NodeTypes.COMMENT_NODE, 2, 11], [NodeTypes.PROPERTY_NODE, 13, 17]]
},
{
desc: "Selector with property with ;",
css: "text { prop; }",
lex: [
[],
[],
[],
[],
[]
]
},
{
desc: "Selector with property without ;",
css: "text { prop }",
lex: [
[],
[],
[],
[],
[]
]
},
{
desc: "Selector with properties",
css: "text { prop; prop2: value }",
lex: [
[],
[],
[],
[],
[],
[],
[]
]
},
{
desc: "Sass nested properties",
css: "text { prop; prop2 { value } }",
lex: [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[]
]
},
{
desc: "CSS comma selectors",
css: "text selector1, text selector2 { prop; prop2 { value } }",
lex: [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[]
]
},
{
desc: "CSS comma selectors with comment",
css:
"text selector1, text selector2 { prop; prop2 { value } /* comment */ }",
lex: [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[]
]
},
{
desc: "@media test",
css: "@media (max-width: 300px) { sel { prop } }",
lex: [
[],
[],
[],
[],
[],
[],
[],
[]
]
},
{
desc: "@media with preceding selector and following prop",
css: "sel { prop: value } @media (max-width: 300px) { sel { prop } } prop",
lex: [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[],
[]
]
},
{
desc: "@media with inline comment",
css: "@media (max-width: 300px /* mobile */) { sel { prop } }",
lex: [
[],
[],
[],
[],
[],
[],
[],
[],
[],
[]
]
}
];
describe("lexes", async () =>
cases.forEach((eachCase, i) => {
let tokens;
try {
tokens = Lexx(eachCase.css);
} catch (e) {
console.log(e);
}
test(`${eachCase.desc} ${eachCase.css} [${
tokens ? resolveNodes(eachCase.css, tokens).join(" | ") : ""
}]`, async () => {
let result;
try {
result = Lexx(eachCase.css);
} catch (e) {
console.log(e);
}
if (!isEqual(tokens, eachCase.lex)) {
console.log(resolveNodesNumbers(eachCase.css, result));
try {
result = Lex(eachCase.css);
} catch (e) {}
if (result) {
console.log("Result:", result, " from ", eachCase.css);
console.log(resolveNodes(eachCase.css, result));
}
}
expect(result).toEqual(eachCase.lex);
});
}));