@carlosjeurissen/stylelint-csstree-validator
Version:
Stylelint plugin to validate CSS syntax
105 lines (81 loc) • 3.17 kB
JavaScript
;
const cssTree = require('css-tree');
const LessVariableReference = require('./LessVariableReference.cjs');
const LessVariable = require('./LessVariable.cjs');
const LessEscaping = require('./LessEscaping.cjs');
const LessNamespace = require('./LessNamespace.cjs');
const FULLSTOP = 0x002E; // U+002E FULL STOP (.)
const GREATERTHANSIGN = 0x003E; // U+003E GREATER-THAN SIGN (>)
const COMMERCIALAT = 0x0040; // U+0040 COMMERCIAL AT (@)
const TILDE = 0x007E; // U+007E TILDE (~)
// custom error
class PreprocessorExtensionError {
constructor() {
this.type = 'PreprocessorExtensionError';
}
}
function throwOnSyntaxExtension() {
let node = null;
switch (this.tokenType) {
case cssTree.tokenTypes.AtKeyword: // less: @var
node = this.LessVariable();
break;
case cssTree.tokenTypes.Hash: {
let sc = 0;
let tokenType = 0;
// deprecated
do {
tokenType = this.lookupType(++sc);
if (tokenType !== cssTree.tokenTypes.WhiteSpace && tokenType !== cssTree.tokenTypes.Comment) {
break;
}
} while (tokenType !== cssTree.tokenTypes.EOF);
if (this.isDelim(FULLSTOP, sc) || /* preferred */
this.isDelim(GREATERTHANSIGN, sc) /* deprecated */) {
node = this.LessNamespace();
}
break;
}
case cssTree.tokenTypes.Delim:
switch (this.source.charCodeAt(this.tokenStart)) {
case COMMERCIALAT: // less: @@var
if (this.lookupType(1) === cssTree.tokenTypes.AtKeyword) {
node = this.LessVariableReference();
}
break;
case TILDE: // less: ~"asd" | ~'asd'
node = this.LessEscaping();
break;
}
break;
}
// currently we can't validate values that contain less/sass extensions
if (node !== null) {
throw new PreprocessorExtensionError();
}
}
function less(syntaxConfig) {
// new node types
syntaxConfig.node.LessVariableReference = LessVariableReference;
syntaxConfig.node.LessVariable = LessVariable;
syntaxConfig.node.LessEscaping = LessEscaping;
syntaxConfig.node.LessNamespace = LessNamespace;
// custom at-rules
syntaxConfig.atrules.plugin = {
prelude: '<string>'
};
// extend parser's at-rule preluder parser
const originalAttrulePreludeGetNode = syntaxConfig.scope.AtrulePrelude.getNode;
syntaxConfig.scope.AtrulePrelude.getNode = function(context) {
throwOnSyntaxExtension.call(this);
return originalAttrulePreludeGetNode.call(this, context);
};
// extend parser's value parser
const originalValueGetNode = syntaxConfig.scope.Value.getNode;
syntaxConfig.scope.Value.getNode = function(context) {
throwOnSyntaxExtension.call(this);
return originalValueGetNode.call(this, context);
};
return syntaxConfig;
}
module.exports = less;