Skip to content

Commit

Permalink
Add sass/less custom at-rules
Browse files Browse the repository at this point in the history
  • Loading branch information
lahmatiy committed Dec 14, 2021
1 parent c256b39 commit 4dbc30c
Show file tree
Hide file tree
Showing 5 changed files with 247 additions and 141 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Since the plugin focuses on CSS syntax validation it warns on a syntax which is

By default the plugin exams styles as pure CSS. To specify that a preprocessor's syntax is used, you must specify an array with the names of these extensions. Currently supported:

- `sass` – declaration values with Sass syntax will be ignored as well as custom at-rules introduced by Saas (e.g. `@if`, `@else`, `@mixin` etc)
- `sass` – declaration values with Sass syntax will be ignored as well as custom at-rules introduced by Saas (e.g. `@if`, `@else`, `@mixin` etc). For now Sass at-rules are allowed with any prelude, but it might be replaced for real syntax definitions in future releases
- `less` – declaration values with Sass syntax will be ignored as well as `@plugin` at-rule introduced by Less

Using both syntax extensions is also possible:
Expand Down
139 changes: 2 additions & 137 deletions lib/syntax-extension/index.js
Original file line number Diff line number Diff line change
@@ -1,137 +1,2 @@
import { tokenTypes as TYPE } from 'css-tree';
import * as LessVariableReference from './less/LessVariableReference.js';
import * as LessVariable from './less/LessVariable.js';
import * as LessEscaping from './less/LessEscaping.js';
import * as LessNamespace from './less/LessNamespace.js';
import * as SassVariable from './sass/SassVariable.js';
import * as SassInterpolation from './sass/SassInterpolation.js';
import * as SassNamespace from './sass/SassNamespace.js';

const NUMBERSIGN = 0x0023; // U+0023 NUMBER SIGN (#)
const DOLLARSIGN = 0x0024; // U+0024 DOLLAR SIGN ($)
const PERCENTAGESIGN = 0x0025; // U+0025 PERCENTAGE SIGN (%)
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';
}
}

export function less(syntaxConfig) {
// new node types
syntaxConfig.node.LessVariableReference = LessVariableReference;
syntaxConfig.node.LessVariable = LessVariable;
syntaxConfig.node.LessEscaping = LessEscaping;
syntaxConfig.node.LessNamespace = LessNamespace;

// extend parser value parser
const originalGetNode = syntaxConfig.scope.Value.getNode;
syntaxConfig.scope.Value.getNode = function(context) {
let node = null;

switch (this.tokenType) {
case TYPE.AtKeyword: // less: @var
node = this.LessVariable();
break;

case TYPE.Hash: {
let sc = 0;
let tokenType = 0;

// deprecated
do {
tokenType = this.lookupType(++sc);
if (tokenType !== TYPE.WhiteSpace && tokenType !== TYPE.Comment) {
break;
}
} while (tokenType !== TYPE.EOF);

if (this.isDelim(FULLSTOP, sc) || /* preferred */
this.isDelim(GREATERTHANSIGN, sc) /* deprecated */) {
node = this.LessNamespace();
}

break;
}

case TYPE.Delim:
switch (this.source.charCodeAt(this.tokenStart)) {
case COMMERCIALAT: // less: @@var
if (this.lookupType(1) === TYPE.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();
}

return originalGetNode.call(this, context);
};

return syntaxConfig;
}

export function sass(syntaxConfig) {
// new node types
syntaxConfig.node.SassVariable = SassVariable;
syntaxConfig.node.SassInterpolation = SassInterpolation;
syntaxConfig.node.SassNamespace = SassNamespace;

// extend parser value parser
const originalGetNode = syntaxConfig.scope.Value.getNode;
syntaxConfig.scope.Value.getNode = function(context) {
let node = null;

switch (this.tokenType) {
case TYPE.Ident:
if (this.isDelim(FULLSTOP, 1)) {
node = this.SassNamespace();
}
break;

case TYPE.Delim:
switch (this.source.charCodeAt(this.tokenStart)) {
case DOLLARSIGN: // sass: $var
node = this.SassVariable();
break;

case NUMBERSIGN: // sass: #{ }
if (this.lookupType(1) === TYPE.LeftCurlyBracket) {
node = this.SassInterpolation(this.scope.Value, this.readSequence);
}
break;

case PERCENTAGESIGN: // sass: 5 % 4
node = this.Operator();
break;
}
break;
}

// currently we can't validate values that contain less/sass extensions
if (node !== null) {
throw new PreprocessorExtensionError();
}

return originalGetNode.call(this, context);
};

return syntaxConfig;
};
export { default as less } from './less/index.js';
export { default as sass } from './sass/index.js';
88 changes: 88 additions & 0 deletions lib/syntax-extension/less/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { tokenTypes as TYPE } from 'css-tree';
import * as LessVariableReference from './LessVariableReference.js';
import * as LessVariable from './LessVariable.js';
import * as LessEscaping from './LessEscaping.js';
import * as LessNamespace from './LessNamespace.js';

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';
}
}

export default 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 value parser
const originalGetNode = syntaxConfig.scope.Value.getNode;
syntaxConfig.scope.Value.getNode = function(context) {
let node = null;

switch (this.tokenType) {
case TYPE.AtKeyword: // less: @var
node = this.LessVariable();
break;

case TYPE.Hash: {
let sc = 0;
let tokenType = 0;

// deprecated
do {
tokenType = this.lookupType(++sc);
if (tokenType !== TYPE.WhiteSpace && tokenType !== TYPE.Comment) {
break;
}
} while (tokenType !== TYPE.EOF);

if (this.isDelim(FULLSTOP, sc) || /* preferred */
this.isDelim(GREATERTHANSIGN, sc) /* deprecated */) {
node = this.LessNamespace();
}

break;
}

case TYPE.Delim:
switch (this.source.charCodeAt(this.tokenStart)) {
case COMMERCIALAT: // less: @@var
if (this.lookupType(1) === TYPE.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();
}

return originalGetNode.call(this, context);
};

return syntaxConfig;
}
120 changes: 120 additions & 0 deletions lib/syntax-extension/sass/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { tokenTypes as TYPE } from 'css-tree';
import * as SassVariable from './SassVariable.js';
import * as SassInterpolation from './SassInterpolation.js';
import * as SassNamespace from './SassNamespace.js';

const NUMBERSIGN = 0x0023; // U+0023 NUMBER SIGN (#)
const DOLLARSIGN = 0x0024; // U+0024 DOLLAR SIGN ($)
const PERCENTAGESIGN = 0x0025; // U+0025 PERCENTAGE SIGN (%)
const FULLSTOP = 0x002E; // U+002E FULL STOP (.)

// custom error
class PreprocessorExtensionError {
constructor() {
this.type = 'PreprocessorExtensionError';
}
}

export default function sass(syntaxConfig) {
// new node types
syntaxConfig.node.SassVariable = SassVariable;
syntaxConfig.node.SassInterpolation = SassInterpolation;
syntaxConfig.node.SassNamespace = SassNamespace;

// custom at-rules
syntaxConfig.atrules['at-root'] = {
prelude: '<any-value>'
};
syntaxConfig.atrules.content = {
prelude: '<any-value>'
};
syntaxConfig.atrules.debug = {
prelude: '<any-value>'
};
syntaxConfig.atrules.each = {
prelude: '<any-value>'
};
syntaxConfig.atrules.else = {
prelude: '<any-value>'
};
syntaxConfig.atrules.error = {
prelude: '<any-value>'
};
syntaxConfig.atrules.extend = {
prelude: '<any-value>'
};
syntaxConfig.atrules.for = {
prelude: '<any-value>'
};
syntaxConfig.atrules.forward = {
prelude: '<any-value>'
};
syntaxConfig.atrules.function = {
prelude: '<any-value>'
};
syntaxConfig.atrules.if = {
prelude: '<any-value>'
};
syntaxConfig.atrules.import = {
prelude: syntaxConfig.atrules.import.prelude + '| <string>#' // FIXME: fix prelude extension in css-tree
};
syntaxConfig.atrules.include = {
prelude: '<any-value>'
};
syntaxConfig.atrules.mixin = {
prelude: '<any-value>'
};
syntaxConfig.atrules.return = {
prelude: '<any-value>'
};
syntaxConfig.atrules.use = {
prelude: '<any-value>'
};
syntaxConfig.atrules.warn = {
prelude: '<any-value>'
};
syntaxConfig.atrules.while = {
prelude: '<any-value>'
};

// extend parser value parser
const originalGetNode = syntaxConfig.scope.Value.getNode;
syntaxConfig.scope.Value.getNode = function(context) {
let node = null;

switch (this.tokenType) {
case TYPE.Ident:
if (this.isDelim(FULLSTOP, 1)) {
node = this.SassNamespace();
}
break;

case TYPE.Delim:
switch (this.source.charCodeAt(this.tokenStart)) {
case DOLLARSIGN: // sass: $var
node = this.SassVariable();
break;

case NUMBERSIGN: // sass: #{ }
if (this.lookupType(1) === TYPE.LeftCurlyBracket) {
node = this.SassInterpolation(this.scope.Value, this.readSequence);
}
break;

case PERCENTAGESIGN: // sass: 5 % 4
node = this.Operator();
break;
}
break;
}

// currently we can't validate values that contain less/sass extensions
if (node !== null) {
throw new PreprocessorExtensionError();
}

return originalGetNode.call(this, context);
};

return syntaxConfig;
};
Loading

0 comments on commit 4dbc30c

Please sign in to comment.