vscode-css-languageservice
Version:
Language service for CSS, LESS and SCSS
602 lines • 24.5 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
return extendStatics(d, b);
}
return function (d, b) {
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
import * as lessScanner from './lessScanner';
import { TokenType } from './cssScanner';
import * as cssParser from './cssParser';
import * as nodes from './cssNodes';
import { ParseError } from './cssErrors';
/// <summary>
/// A parser for LESS
/// http://lesscss.org/
/// </summary>
var LESSParser = /** @class */ (function (_super) {
__extends(LESSParser, _super);
function LESSParser() {
return _super.call(this, new lessScanner.LESSScanner()) || this;
}
LESSParser.prototype._parseStylesheetStatement = function () {
if (this.peek(TokenType.AtKeyword)) {
return this._parseVariableDeclaration()
|| this._parsePlugin()
|| _super.prototype._parseStylesheetAtStatement.call(this);
}
return this._tryParseMixinDeclaration()
|| this._tryParseMixinReference(true)
|| this._parseRuleset(true);
};
LESSParser.prototype._parseImport = function () {
if (!this.peekKeyword('@import') && !this.peekKeyword('@import-once') /* deprecated in less 1.4.1 */) {
return null;
}
var node = this.create(nodes.Import);
this.consumeToken();
// less 1.4.1: @import (css) "lib"
if (this.accept(TokenType.ParenthesisL)) {
if (!this.accept(TokenType.Ident)) {
return this.finish(node, ParseError.IdentifierExpected, [TokenType.SemiColon]);
}
do {
if (!this.accept(TokenType.Comma)) {
break;
}
} while (this.accept(TokenType.Ident));
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.SemiColon]);
}
}
if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) {
return this.finish(node, ParseError.URIOrStringExpected, [TokenType.SemiColon]);
}
if (!this.peek(TokenType.SemiColon) && !this.peek(TokenType.EOF)) {
node.setMedialist(this._parseMediaQueryList());
}
return this.finish(node);
};
LESSParser.prototype._parsePlugin = function () {
if (!this.peekKeyword('@plugin')) {
return null;
}
var node = this.createNode(nodes.NodeType.Plugin);
this.consumeToken(); // @import
if (!node.addChild(this._parseStringLiteral())) {
return this.finish(node, ParseError.StringLiteralExpected);
}
if (!this.accept(TokenType.SemiColon)) {
return this.finish(node, ParseError.SemiColonExpected);
}
return this.finish(node);
};
LESSParser.prototype._parseMediaQuery = function (resyncStopToken) {
var node = _super.prototype._parseMediaQuery.call(this, resyncStopToken);
if (!node) {
var node_1 = this.create(nodes.MediaQuery);
if (node_1.addChild(this._parseVariable())) {
return this.finish(node_1);
}
return null;
}
return node;
};
LESSParser.prototype._parseMediaDeclaration = function (isNested) {
if (isNested === void 0) { isNested = false; }
return this._tryParseRuleset(isNested)
|| this._tryToParseDeclaration()
|| this._tryParseMixinDeclaration()
|| this._tryParseMixinReference()
|| this._parseDetachedRuleSetMixin()
|| this._parseStylesheetStatement();
};
LESSParser.prototype._parseMediaFeatureName = function () {
return this._parseIdent() || this._parseVariable();
};
LESSParser.prototype._parseVariableDeclaration = function (panic) {
if (panic === void 0) { panic = []; }
var node = this.create(nodes.VariableDeclaration);
var mark = this.mark();
if (!node.setVariable(this._parseVariable())) {
return null;
}
if (this.accept(TokenType.Colon)) {
node.colonPosition = this.prevToken.offset;
if (!node.setValue(this._parseDetachedRuleSet() || this._parseExpr())) {
return this.finish(node, ParseError.VariableValueExpected, [], panic);
}
node.addChild(this._parsePrio());
}
else {
this.restoreAtMark(mark);
return null; // at keyword, but no ':', not a variable declaration but some at keyword
}
if (this.peek(TokenType.SemiColon)) {
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
}
return this.finish(node);
};
LESSParser.prototype._parseDetachedRuleSet = function () {
if (!this.peek(TokenType.CurlyL)) {
return null;
}
var content = this.create(nodes.BodyDeclaration);
this._parseBody(content, this._parseDetachedRuleSetBody.bind(this));
return this.finish(content);
};
LESSParser.prototype._parseDetachedRuleSetBody = function () {
return this._tryParseKeyframeSelector() || this._tryParseRuleset(true) || _super.prototype._parseRuleSetDeclaration.call(this);
};
LESSParser.prototype._parseVariable = function () {
if (!this.peekDelim('@') && !this.peek(TokenType.AtKeyword)) {
return null;
}
var node = this.create(nodes.Variable);
var mark = this.mark();
while (this.acceptDelim('@')) {
if (this.hasWhitespace()) {
this.restoreAtMark(mark);
return null;
}
}
if (!this.accept(TokenType.AtKeyword)) {
this.restoreAtMark(mark);
return null;
}
return node;
};
LESSParser.prototype._parseTerm = function () {
var term = _super.prototype._parseTerm.call(this);
if (term) {
return term;
}
term = this.create(nodes.Term);
if (term.setExpression(this._parseVariable()) ||
term.setExpression(this._parseEscaped())) {
return this.finish(term);
}
return null;
};
LESSParser.prototype._parseEscaped = function () {
if (this.peek(TokenType.EscapedJavaScript) ||
this.peek(TokenType.BadEscapedJavaScript)) {
var node = this.createNode(nodes.NodeType.EscapedValue);
this.consumeToken();
return this.finish(node);
}
if (this.peekDelim('~')) {
var node = this.createNode(nodes.NodeType.EscapedValue);
this.consumeToken();
return this.finish(node, this.accept(TokenType.String) ? null : ParseError.TermExpected);
}
return null;
};
LESSParser.prototype._parseOperator = function () {
var node = this._parseGuardOperator();
if (node) {
return node;
}
else {
return _super.prototype._parseOperator.call(this);
}
};
LESSParser.prototype._parseGuardOperator = function () {
if (this.peekDelim('>')) {
var node = this.createNode(nodes.NodeType.Operator);
this.consumeToken();
this.acceptDelim('=');
return node;
}
else if (this.peekDelim('=')) {
var node = this.createNode(nodes.NodeType.Operator);
this.consumeToken();
this.acceptDelim('<');
return node;
}
else if (this.peekDelim('<')) {
var node = this.createNode(nodes.NodeType.Operator);
this.consumeToken();
this.acceptDelim('=');
return node;
}
return null;
};
LESSParser.prototype._parseRuleSetDeclaration = function () {
if (this.peek(TokenType.AtKeyword)) {
return this._parseKeyframe()
|| this._parseMedia(true)
|| this._parseImport()
|| this._parseSupports(true) // @supports
|| this._parseDetachedRuleSetMixin() // less detached ruleset mixin
|| this._parseVariableDeclaration(); // Variable declarations
}
return this._tryParseMixinDeclaration()
|| this._tryParseRuleset(true) // nested ruleset
|| this._tryParseMixinReference() // less mixin reference
|| this._parseExtend() // less extend declaration
|| _super.prototype._parseRuleSetDeclaration.call(this); // try css ruleset declaration as the last option
};
LESSParser.prototype._parseKeyframeIdent = function () {
return this._parseIdent([nodes.ReferenceType.Keyframe]) || this._parseVariable();
};
LESSParser.prototype._parseKeyframeSelector = function () {
return this._parseDetachedRuleSetMixin() // less detached ruleset mixin
|| _super.prototype._parseKeyframeSelector.call(this);
};
LESSParser.prototype._parseSimpleSelectorBody = function () {
return this._parseSelectorCombinator() || _super.prototype._parseSimpleSelectorBody.call(this);
};
LESSParser.prototype._parseSelector = function (isNested) {
// CSS Guards
var node = this.create(nodes.Selector);
var hasContent = false;
if (isNested) {
// nested selectors can start with a combinator
hasContent = node.addChild(this._parseCombinator());
}
while (node.addChild(this._parseSimpleSelector())) {
hasContent = true;
var mark = this.mark();
if (node.addChild(this._parseGuard()) && this.peek(TokenType.CurlyL)) {
break;
}
this.restoreAtMark(mark);
node.addChild(this._parseCombinator()); // optional
}
return hasContent ? this.finish(node) : null;
};
LESSParser.prototype._parseSelectorCombinator = function () {
if (this.peekDelim('&')) {
var node = this.createNode(nodes.NodeType.SelectorCombinator);
this.consumeToken();
while (!this.hasWhitespace() && (this.acceptDelim('-') || this.accept(TokenType.Num) || this.accept(TokenType.Dimension) || node.addChild(this._parseIdent()) || this.acceptDelim('&'))) {
// support &-foo
}
return this.finish(node);
}
return null;
};
LESSParser.prototype._parseSelectorIdent = function () {
if (!this.peekInterpolatedIdent()) {
return null;
}
var node = this.createNode(nodes.NodeType.SelectorInterpolation);
var hasContent = this._acceptInterpolatedIdent(node);
return hasContent ? this.finish(node) : null;
};
LESSParser.prototype._parsePropertyIdentifier = function () {
if (!this.peekInterpolatedIdent()) {
return null;
}
var node = this.create(nodes.Identifier);
node.isCustomProperty = this.peekRegExp(TokenType.Ident, /^--/);
var hasContent = this._acceptInterpolatedIdent(node);
if (hasContent && !this.hasWhitespace()) {
this.acceptDelim('+');
if (!this.hasWhitespace()) {
this.acceptIdent('_');
}
}
return hasContent ? this.finish(node) : null;
};
LESSParser.prototype.peekInterpolatedIdent = function () {
return this.peek(TokenType.Ident) || this.peekDelim('@') || this.peekDelim('-');
};
LESSParser.prototype._acceptInterpolatedIdent = function (node) {
var _this = this;
var hasContent = false;
var delimWithInterpolation = function () {
if (!_this.acceptDelim('-')) {
return null;
}
if (!_this.hasWhitespace() && _this.acceptDelim('-')) {
}
if (!_this.hasWhitespace()) {
return _this._parseInterpolation();
}
return null;
};
while (this.accept(TokenType.Ident) || node.addChild(this._parseInterpolation() || this.try(delimWithInterpolation))) {
hasContent = true;
if (!this.hasWhitespace() && this.acceptDelim('-')) {
// '-' is a valid char inside a ident (special treatment here to support @{foo}-@{bar})
}
if (this.hasWhitespace()) {
break;
}
}
return hasContent;
};
LESSParser.prototype._parseInterpolation = function () {
// @{name}
var mark = this.mark();
if (this.peekDelim('@')) {
var node = this.createNode(nodes.NodeType.Interpolation);
this.consumeToken();
if (this.hasWhitespace() || !this.accept(TokenType.CurlyL)) {
this.restoreAtMark(mark);
return null;
}
if (!node.addChild(this._parseIdent())) {
return this.finish(node, ParseError.IdentifierExpected);
}
if (!this.accept(TokenType.CurlyR)) {
return this.finish(node, ParseError.RightCurlyExpected);
}
return this.finish(node);
}
return null;
};
LESSParser.prototype._tryParseMixinDeclaration = function () {
var mark = this.mark();
var node = this.create(nodes.MixinDeclaration);
if (!node.setIdentifier(this._parseMixinDeclarationIdentifier()) || !this.accept(TokenType.ParenthesisL)) {
this.restoreAtMark(mark);
return null;
}
if (node.getParameters().addChild(this._parseMixinParameter())) {
while (this.accept(TokenType.Comma) || this.accept(TokenType.SemiColon)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseMixinParameter())) {
this.markError(node, ParseError.IdentifierExpected, [], [TokenType.ParenthesisR]);
}
}
}
if (!this.accept(TokenType.ParenthesisR)) {
this.restoreAtMark(mark);
return null;
}
node.setGuard(this._parseGuard());
if (!this.peek(TokenType.CurlyL)) {
this.restoreAtMark(mark);
return null;
}
return this._parseBody(node, this._parseMixInBodyDeclaration.bind(this));
};
LESSParser.prototype._parseMixInBodyDeclaration = function () {
return this._parseFontFace() || this._parseRuleSetDeclaration();
};
LESSParser.prototype._parseMixinDeclarationIdentifier = function () {
var identifier;
if (this.peekDelim('#') || this.peekDelim('.')) {
identifier = this.create(nodes.Identifier);
this.consumeToken(); // # or .
if (this.hasWhitespace() || !identifier.addChild(this._parseIdent())) {
return null;
}
}
else if (this.peek(TokenType.Hash)) {
identifier = this.create(nodes.Identifier);
this.consumeToken(); // TokenType.Hash
}
else {
return null;
}
identifier.referenceTypes = [nodes.ReferenceType.Mixin];
return this.finish(identifier);
};
LESSParser.prototype._parsePseudo = function () {
if (!this.peek(TokenType.Colon)) {
return null;
}
var mark = this.mark();
var node = this.create(nodes.ExtendsReference);
this.consumeToken(); // :
if (this.acceptIdent('extend')) {
return this._completeExtends(node);
}
this.restoreAtMark(mark);
return _super.prototype._parsePseudo.call(this);
};
LESSParser.prototype._parseExtend = function () {
if (!this.peekDelim('&')) {
return null;
}
var mark = this.mark();
var node = this.create(nodes.ExtendsReference);
this.consumeToken(); // &
if (this.hasWhitespace() || !this.accept(TokenType.Colon) || !this.acceptIdent('extend')) {
this.restoreAtMark(mark);
return null;
}
return this._completeExtends(node);
};
LESSParser.prototype._completeExtends = function (node) {
if (!this.accept(TokenType.ParenthesisL)) {
return this.finish(node, ParseError.LeftParenthesisExpected);
}
var selectors = node.getSelectors();
if (!selectors.addChild(this._parseSelector(true))) {
return this.finish(node, ParseError.SelectorExpected);
}
while (this.accept(TokenType.Comma)) {
if (!selectors.addChild(this._parseSelector(true))) {
return this.finish(node, ParseError.SelectorExpected);
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
return this.finish(node);
};
LESSParser.prototype._parseDetachedRuleSetMixin = function () {
if (!this.peek(TokenType.AtKeyword)) {
return null;
}
var mark = this.mark();
var node = this.create(nodes.MixinReference);
if (!node.addChild(this._parseVariable()) || !this.accept(TokenType.ParenthesisL)) {
this.restoreAtMark(mark);
return null;
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
return this.finish(node);
};
LESSParser.prototype._tryParseMixinReference = function (atRoot) {
if (atRoot === void 0) { atRoot = false; }
var mark = this.mark();
var node = this.create(nodes.MixinReference);
var identifier = this._parseMixinDeclarationIdentifier();
while (identifier) {
this.acceptDelim('>');
var nextId = this._parseMixinDeclarationIdentifier();
if (nextId) {
node.getNamespaces().addChild(identifier);
identifier = nextId;
}
else {
break;
}
}
if (!node.setIdentifier(identifier)) {
this.restoreAtMark(mark);
return null;
}
var hasArguments = false;
if (!this.hasWhitespace() && this.accept(TokenType.ParenthesisL)) {
hasArguments = true;
if (node.getArguments().addChild(this._parseMixinArgument())) {
while (this.accept(TokenType.Comma) || this.accept(TokenType.SemiColon)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getArguments().addChild(this._parseMixinArgument())) {
return this.finish(node, ParseError.ExpressionExpected);
}
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
identifier.referenceTypes = [nodes.ReferenceType.Mixin];
}
else {
identifier.referenceTypes = [nodes.ReferenceType.Mixin, nodes.ReferenceType.Rule];
}
node.addChild(this._parsePrio());
if (!hasArguments && !this.peek(TokenType.SemiColon) && !this.peek(TokenType.CurlyR) && !this.peek(TokenType.EOF)) {
this.restoreAtMark(mark);
return null;
}
return this.finish(node);
};
LESSParser.prototype._parseMixinArgument = function () {
// [variableName ':'] expression | variableName '...'
var node = this.create(nodes.FunctionArgument);
var pos = this.mark();
var argument = this._parseVariable();
if (argument) {
if (!this.accept(TokenType.Colon)) {
this.restoreAtMark(pos);
}
else {
node.setIdentifier(argument);
}
}
if (node.setValue(this._parseDetachedRuleSet() || this._parseExpr(true))) {
return this.finish(node);
}
this.restoreAtMark(pos);
return null;
};
LESSParser.prototype._parseMixinParameter = function () {
var node = this.create(nodes.FunctionParameter);
// special rest variable: @rest...
if (this.peekKeyword('@rest')) {
var restNode = this.create(nodes.Node);
this.consumeToken();
if (!this.accept(lessScanner.Ellipsis)) {
return this.finish(node, ParseError.DotExpected, [], [TokenType.Comma, TokenType.ParenthesisR]);
}
node.setIdentifier(this.finish(restNode));
return this.finish(node);
}
// special let args: ...
if (this.peek(lessScanner.Ellipsis)) {
var varargsNode = this.create(nodes.Node);
this.consumeToken();
node.setIdentifier(this.finish(varargsNode));
return this.finish(node);
}
var hasContent = false;
// default variable declaration: @param: 12 or @name
if (node.setIdentifier(this._parseVariable())) {
this.accept(TokenType.Colon);
hasContent = true;
}
if (!node.setDefaultValue(this._parseExpr(true)) && !hasContent) {
return null;
}
return this.finish(node);
};
LESSParser.prototype._parseGuard = function () {
if (!this.peekIdent('when')) {
return null;
}
var node = this.create(nodes.LessGuard);
this.consumeToken(); // when
node.isNegated = this.acceptIdent('not');
if (!node.getConditions().addChild(this._parseGuardCondition())) {
return this.finish(node, ParseError.ConditionExpected);
}
while (this.acceptIdent('and') || this.accept(TokenType.Comma)) {
if (!node.getConditions().addChild(this._parseGuardCondition())) {
return this.finish(node, ParseError.ConditionExpected);
}
}
return this.finish(node);
};
LESSParser.prototype._parseGuardCondition = function () {
if (!this.peek(TokenType.ParenthesisL)) {
return null;
}
var node = this.create(nodes.GuardCondition);
this.consumeToken(); // ParenthesisL
if (!node.addChild(this._parseExpr())) {
// empty (?)
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
return this.finish(node);
};
LESSParser.prototype._parseFunctionIdentifier = function () {
if (this.peekDelim('%')) {
var node = this.create(nodes.Identifier);
node.referenceTypes = [nodes.ReferenceType.Function];
this.consumeToken();
return this.finish(node);
}
return _super.prototype._parseFunctionIdentifier.call(this);
};
LESSParser.prototype._parseURLArgument = function () {
var pos = this.mark();
var node = _super.prototype._parseURLArgument.call(this);
if (!node || !this.peek(TokenType.ParenthesisR)) {
this.restoreAtMark(pos);
var node_2 = this.create(nodes.Node);
node_2.addChild(this._parseBinaryExpr());
return this.finish(node_2);
}
return node;
};
return LESSParser;
}(cssParser.Parser));
export { LESSParser };
//# sourceMappingURL=lessParser.js.map