/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import * as scssScanner from './scssScanner'; import { TokenType } from './cssScanner'; import * as cssParser from './cssParser'; import * as nodes from './cssNodes'; import { SCSSParseError } from './scssErrors'; import { ParseError } from './cssErrors'; /// /// A parser for scss /// http://sass-lang.com/documentation/file.SASS_REFERENCE.html /// export class SCSSParser extends cssParser.Parser { constructor() { super(new scssScanner.SCSSScanner()); } _parseStylesheetStatement(isNested = false) { if (this.peek(TokenType.AtKeyword)) { return this._parseWarnAndDebug() // @warn, @debug and @error statements || this._parseControlStatement() // @if, @while, @for, @each || this._parseMixinDeclaration() // @mixin || this._parseMixinContent() // @content || this._parseMixinReference() // @include || this._parseFunctionDeclaration() // @function || this._parseForward() // @forward || this._parseUse() // @use || this._parseRuleset(isNested) // @at-rule || super._parseStylesheetAtStatement(isNested); } return this._parseRuleset(true) || this._parseVariableDeclaration(); } _parseImport() { if (!this.peekKeyword('@import')) { return null; } const node = this.create(nodes.Import); this.consumeToken(); if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { return this.finish(node, ParseError.URIOrStringExpected); } while (this.accept(TokenType.Comma)) { if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) { return this.finish(node, ParseError.URIOrStringExpected); } } return this._completeParseImport(node); } // scss variables: $font-size: 12px; _parseVariableDeclaration(panic = []) { if (!this.peek(scssScanner.VariableName)) { return null; } const node = this.create(nodes.VariableDeclaration); if (!node.setVariable(this._parseVariable())) { return null; } if (!this.accept(TokenType.Colon)) { return this.finish(node, ParseError.ColonExpected); } if (this.prevToken) { node.colonPosition = this.prevToken.offset; } if (!node.setValue(this._parseExpr())) { return this.finish(node, ParseError.VariableValueExpected, [], panic); } while (this.peek(TokenType.Exclamation)) { if (node.addChild(this._tryParsePrio())) { // !important } else { this.consumeToken(); if (!this.peekRegExp(TokenType.Ident, /^(default|global)$/)) { return this.finish(node, ParseError.UnknownKeyword); } this.consumeToken(); } } 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); } _parseMediaCondition() { return this._parseInterpolation() || super._parseMediaCondition(); } _parseMediaFeatureRangeOperator() { return this.accept(scssScanner.SmallerEqualsOperator) || this.accept(scssScanner.GreaterEqualsOperator) || super._parseMediaFeatureRangeOperator(); } _parseMediaFeatureName() { return this._parseModuleMember() || this._parseFunction() // function before ident || this._parseIdent() || this._parseVariable(); } _parseKeyframeSelector() { return this._tryParseKeyframeSelector() || this._parseControlStatement(this._parseKeyframeSelector.bind(this)) || this._parseWarnAndDebug() // @warn, @debug and @error statements || this._parseMixinReference() // @include || this._parseFunctionDeclaration() // @function || this._parseVariableDeclaration() || this._parseMixinContent(); } _parseVariable() { if (!this.peek(scssScanner.VariableName)) { return null; } const node = this.create(nodes.Variable); this.consumeToken(); return node; } _parseModuleMember() { const pos = this.mark(); const node = this.create(nodes.Module); if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Module]))) { return null; } if (this.hasWhitespace() || !this.acceptDelim('.') || this.hasWhitespace()) { this.restoreAtMark(pos); return null; } if (!node.addChild(this._parseVariable() || this._parseFunction())) { return this.finish(node, ParseError.IdentifierOrVariableExpected); } return node; } _parseIdent(referenceTypes) { if (!this.peek(TokenType.Ident) && !this.peek(scssScanner.InterpolationFunction) && !this.peekDelim('-')) { return null; } const node = this.create(nodes.Identifier); node.referenceTypes = referenceTypes; node.isCustomProperty = this.peekRegExp(TokenType.Ident, /^--/); let hasContent = false; const indentInterpolation = () => { const pos = this.mark(); if (this.acceptDelim('-')) { if (!this.hasWhitespace()) { this.acceptDelim('-'); } if (this.hasWhitespace()) { this.restoreAtMark(pos); return null; } } return this._parseInterpolation(); }; while (this.accept(TokenType.Ident) || node.addChild(indentInterpolation()) || (hasContent && this.acceptRegexp(/^[\w-]/))) { hasContent = true; if (this.hasWhitespace()) { break; } } return hasContent ? this.finish(node) : null; } _parseTermExpression() { return this._parseModuleMember() || this._parseVariable() || this._parseNestingSelector() || //this._tryParsePrio() || super._parseTermExpression(); } _parseInterpolation() { if (this.peek(scssScanner.InterpolationFunction)) { const node = this.create(nodes.Interpolation); this.consumeToken(); if (!node.addChild(this._parseExpr()) && !this._parseNestingSelector()) { if (this.accept(TokenType.CurlyR)) { return this.finish(node); } return this.finish(node, ParseError.ExpressionExpected); } if (!this.accept(TokenType.CurlyR)) { return this.finish(node, ParseError.RightCurlyExpected); } return this.finish(node); } return null; } _parseOperator() { if (this.peek(scssScanner.EqualsOperator) || this.peek(scssScanner.NotEqualsOperator) || this.peek(scssScanner.GreaterEqualsOperator) || this.peek(scssScanner.SmallerEqualsOperator) || this.peekDelim('>') || this.peekDelim('<') || this.peekIdent('and') || this.peekIdent('or') || this.peekDelim('%')) { const node = this.createNode(nodes.NodeType.Operator); this.consumeToken(); return this.finish(node); } return super._parseOperator(); } _parseUnaryOperator() { if (this.peekIdent('not')) { const node = this.create(nodes.Node); this.consumeToken(); return this.finish(node); } return super._parseUnaryOperator(); } _parseRuleSetDeclaration() { if (this.peek(TokenType.AtKeyword)) { return this._parseKeyframe() // nested @keyframe || this._parseImport() // nested @import || this._parseMedia(true) // nested @media || this._parseFontFace() // nested @font-face || this._parseWarnAndDebug() // @warn, @debug and @error statements || this._parseControlStatement() // @if, @while, @for, @each || this._parseFunctionDeclaration() // @function || this._parseExtends() // @extends || this._parseMixinReference() // @include || this._parseMixinContent() // @content || this._parseMixinDeclaration() // nested @mixin || this._parseRuleset(true) // @at-rule || this._parseSupports(true) // @supports || this._parseLayer() // @layer || this._parsePropertyAtRule() // @property || this._parseRuleSetDeclarationAtStatement(); } return this._parseVariableDeclaration() // variable declaration || this._tryParseRuleset(true) // nested ruleset || this._parseDeclaration(); // try css ruleset declaration as last so in the error case, the ast will contain a declaration } _parseDeclaration(stopTokens) { const custonProperty = this._tryParseCustomPropertyDeclaration(stopTokens); if (custonProperty) { return custonProperty; } const node = this.create(nodes.Declaration); if (!node.setProperty(this._parseProperty())) { return null; } if (!this.accept(TokenType.Colon)) { return this.finish(node, ParseError.ColonExpected, [TokenType.Colon], stopTokens || [TokenType.SemiColon]); } if (this.prevToken) { node.colonPosition = this.prevToken.offset; } let hasContent = false; if (node.setValue(this._parseExpr())) { hasContent = true; node.addChild(this._parsePrio()); } if (this.peek(TokenType.CurlyL)) { node.setNestedProperties(this._parseNestedProperties()); } else { if (!hasContent) { return this.finish(node, ParseError.PropertyValueExpected); } } 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); } _parseNestedProperties() { const node = this.create(nodes.NestedProperties); return this._parseBody(node, this._parseDeclaration.bind(this)); } _parseExtends() { if (this.peekKeyword('@extend')) { const node = this.create(nodes.ExtendsReference); this.consumeToken(); if (!node.getSelectors().addChild(this._parseSimpleSelector())) { return this.finish(node, ParseError.SelectorExpected); } while (this.accept(TokenType.Comma)) { node.getSelectors().addChild(this._parseSimpleSelector()); } if (this.accept(TokenType.Exclamation)) { if (!this.acceptIdent('optional')) { return this.finish(node, ParseError.UnknownKeyword); } } return this.finish(node); } return null; } _parseSimpleSelectorBody() { return this._parseSelectorPlaceholder() || super._parseSimpleSelectorBody(); } _parseNestingSelector() { if (this.peekDelim('&')) { const 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-1 } return this.finish(node); } return null; } _parseSelectorPlaceholder() { if (this.peekDelim('%')) { const node = this.createNode(nodes.NodeType.SelectorPlaceholder); this.consumeToken(); this._parseIdent(); return this.finish(node); } else if (this.peekKeyword('@at-root')) { const node = this.createNode(nodes.NodeType.SelectorPlaceholder); this.consumeToken(); if (this.accept(TokenType.ParenthesisL)) { if (!this.acceptIdent('with') && !this.acceptIdent('without')) { return this.finish(node, ParseError.IdentifierExpected); } if (!this.accept(TokenType.Colon)) { return this.finish(node, ParseError.ColonExpected); } if (!node.addChild(this._parseIdent())) { return this.finish(node, ParseError.IdentifierExpected); } if (!this.accept(TokenType.ParenthesisR)) { return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]); } } return this.finish(node); } return null; } _parseElementName() { const pos = this.mark(); const node = super._parseElementName(); if (node && !this.hasWhitespace() && this.peek(TokenType.ParenthesisL)) { // for #49589 this.restoreAtMark(pos); return null; } return node; } _tryParsePseudoIdentifier() { return this._parseInterpolation() || super._tryParsePseudoIdentifier(); // for #49589 } _parseWarnAndDebug() { if (!this.peekKeyword('@debug') && !this.peekKeyword('@warn') && !this.peekKeyword('@error')) { return null; } const node = this.createNode(nodes.NodeType.Debug); this.consumeToken(); // @debug, @warn or @error node.addChild(this._parseExpr()); // optional return this.finish(node); } _parseControlStatement(parseStatement = this._parseRuleSetDeclaration.bind(this)) { if (!this.peek(TokenType.AtKeyword)) { return null; } return this._parseIfStatement(parseStatement) || this._parseForStatement(parseStatement) || this._parseEachStatement(parseStatement) || this._parseWhileStatement(parseStatement); } _parseIfStatement(parseStatement) { if (!this.peekKeyword('@if')) { return null; } return this._internalParseIfStatement(parseStatement); } _internalParseIfStatement(parseStatement) { const node = this.create(nodes.IfStatement); this.consumeToken(); // @if or if if (!node.setExpression(this._parseExpr(true))) { return this.finish(node, ParseError.ExpressionExpected); } this._parseBody(node, parseStatement); if (this.acceptKeyword('@else')) { if (this.peekIdent('if')) { node.setElseClause(this._internalParseIfStatement(parseStatement)); } else if (this.peek(TokenType.CurlyL)) { const elseNode = this.create(nodes.ElseStatement); this._parseBody(elseNode, parseStatement); node.setElseClause(elseNode); } } return this.finish(node); } _parseForStatement(parseStatement) { if (!this.peekKeyword('@for')) { return null; } const node = this.create(nodes.ForStatement); this.consumeToken(); // @for if (!node.setVariable(this._parseVariable())) { return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]); } if (!this.acceptIdent('from')) { return this.finish(node, SCSSParseError.FromExpected, [TokenType.CurlyR]); } if (!node.addChild(this._parseBinaryExpr())) { return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); } if (!this.acceptIdent('to') && !this.acceptIdent('through')) { return this.finish(node, SCSSParseError.ThroughOrToExpected, [TokenType.CurlyR]); } if (!node.addChild(this._parseBinaryExpr())) { return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); } return this._parseBody(node, parseStatement); } _parseEachStatement(parseStatement) { if (!this.peekKeyword('@each')) { return null; } const node = this.create(nodes.EachStatement); this.consumeToken(); // @each const variables = node.getVariables(); if (!variables.addChild(this._parseVariable())) { return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]); } while (this.accept(TokenType.Comma)) { if (!variables.addChild(this._parseVariable())) { return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]); } } this.finish(variables); if (!this.acceptIdent('in')) { return this.finish(node, SCSSParseError.InExpected, [TokenType.CurlyR]); } if (!node.addChild(this._parseExpr())) { return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); } return this._parseBody(node, parseStatement); } _parseWhileStatement(parseStatement) { if (!this.peekKeyword('@while')) { return null; } const node = this.create(nodes.WhileStatement); this.consumeToken(); // @while if (!node.addChild(this._parseBinaryExpr())) { return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]); } return this._parseBody(node, parseStatement); } _parseFunctionBodyDeclaration() { return this._parseVariableDeclaration() || this._parseReturnStatement() || this._parseWarnAndDebug() || this._parseControlStatement(this._parseFunctionBodyDeclaration.bind(this)); } _parseFunctionDeclaration() { if (!this.peekKeyword('@function')) { return null; } const node = this.create(nodes.FunctionDeclaration); this.consumeToken(); // @function if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Function]))) { return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); } if (!this.accept(TokenType.ParenthesisL)) { return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.CurlyR]); } if (node.getParameters().addChild(this._parseParameterDeclaration())) { while (this.accept(TokenType.Comma)) { if (this.peek(TokenType.ParenthesisR)) { break; } if (!node.getParameters().addChild(this._parseParameterDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } } } if (!this.accept(TokenType.ParenthesisR)) { return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]); } return this._parseBody(node, this._parseFunctionBodyDeclaration.bind(this)); } _parseReturnStatement() { if (!this.peekKeyword('@return')) { return null; } const node = this.createNode(nodes.NodeType.ReturnStatement); this.consumeToken(); // @function if (!node.addChild(this._parseExpr())) { return this.finish(node, ParseError.ExpressionExpected); } return this.finish(node); } _parseMixinDeclaration() { if (!this.peekKeyword('@mixin')) { return null; } const node = this.create(nodes.MixinDeclaration); this.consumeToken(); if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Mixin]))) { return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); } if (this.accept(TokenType.ParenthesisL)) { if (node.getParameters().addChild(this._parseParameterDeclaration())) { while (this.accept(TokenType.Comma)) { if (this.peek(TokenType.ParenthesisR)) { break; } if (!node.getParameters().addChild(this._parseParameterDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } } } if (!this.accept(TokenType.ParenthesisR)) { return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]); } } return this._parseBody(node, this._parseRuleSetDeclaration.bind(this)); } _parseParameterDeclaration() { const node = this.create(nodes.FunctionParameter); if (!node.setIdentifier(this._parseVariable())) { return null; } if (this.accept(scssScanner.Ellipsis)) { // ok } if (this.accept(TokenType.Colon)) { if (!node.setDefaultValue(this._parseExpr(true))) { return this.finish(node, ParseError.VariableValueExpected, [], [TokenType.Comma, TokenType.ParenthesisR]); } } return this.finish(node); } _parseMixinContent() { if (!this.peekKeyword('@content')) { return null; } const node = this.create(nodes.MixinContentReference); this.consumeToken(); if (this.accept(TokenType.ParenthesisL)) { if (node.getArguments().addChild(this._parseFunctionArgument())) { while (this.accept(TokenType.Comma)) { if (this.peek(TokenType.ParenthesisR)) { break; } if (!node.getArguments().addChild(this._parseFunctionArgument())) { return this.finish(node, ParseError.ExpressionExpected); } } } if (!this.accept(TokenType.ParenthesisR)) { return this.finish(node, ParseError.RightParenthesisExpected); } } return this.finish(node); } _parseMixinReference() { if (!this.peekKeyword('@include')) { return null; } const node = this.create(nodes.MixinReference); this.consumeToken(); // Could be module or mixin identifier, set as mixin as default. const firstIdent = this._parseIdent([nodes.ReferenceType.Mixin]); if (!node.setIdentifier(firstIdent)) { return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); } // Is a module accessor. if (!this.hasWhitespace() && this.acceptDelim('.') && !this.hasWhitespace()) { const secondIdent = this._parseIdent([nodes.ReferenceType.Mixin]); if (!secondIdent) { return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]); } const moduleToken = this.create(nodes.Module); // Re-purpose first matched ident as identifier for module token. firstIdent.referenceTypes = [nodes.ReferenceType.Module]; moduleToken.setIdentifier(firstIdent); // Override identifier with second ident. node.setIdentifier(secondIdent); node.addChild(moduleToken); } if (this.accept(TokenType.ParenthesisL)) { if (node.getArguments().addChild(this._parseFunctionArgument())) { while (this.accept(TokenType.Comma)) { if (this.peek(TokenType.ParenthesisR)) { break; } if (!node.getArguments().addChild(this._parseFunctionArgument())) { return this.finish(node, ParseError.ExpressionExpected); } } } if (!this.accept(TokenType.ParenthesisR)) { return this.finish(node, ParseError.RightParenthesisExpected); } } if (this.peekIdent('using') || this.peek(TokenType.CurlyL)) { node.setContent(this._parseMixinContentDeclaration()); } return this.finish(node); } _parseMixinContentDeclaration() { const node = this.create(nodes.MixinContentDeclaration); if (this.acceptIdent('using')) { if (!this.accept(TokenType.ParenthesisL)) { return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.CurlyL]); } if (node.getParameters().addChild(this._parseParameterDeclaration())) { while (this.accept(TokenType.Comma)) { if (this.peek(TokenType.ParenthesisR)) { break; } if (!node.getParameters().addChild(this._parseParameterDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } } } if (!this.accept(TokenType.ParenthesisR)) { return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyL]); } } if (this.peek(TokenType.CurlyL)) { this._parseBody(node, this._parseMixinReferenceBodyStatement.bind(this)); } return this.finish(node); } _parseMixinReferenceBodyStatement() { return this._tryParseKeyframeSelector() || this._parseRuleSetDeclaration(); } _parseFunctionArgument() { // [variableName ':'] expression | variableName '...' const node = this.create(nodes.FunctionArgument); const pos = this.mark(); const argument = this._parseVariable(); if (argument) { if (!this.accept(TokenType.Colon)) { if (this.accept(scssScanner.Ellipsis)) { // optional node.setValue(argument); return this.finish(node); } else { this.restoreAtMark(pos); } } else { node.setIdentifier(argument); } } if (node.setValue(this._parseExpr(true))) { this.accept(scssScanner.Ellipsis); // #43746 node.addChild(this._parsePrio()); // #9859 return this.finish(node); } else if (node.setValue(this._tryParsePrio())) { return this.finish(node); } return null; } _parseURLArgument() { const pos = this.mark(); const node = super._parseURLArgument(); if (!node || !this.peek(TokenType.ParenthesisR)) { this.restoreAtMark(pos); const node = this.create(nodes.Node); node.addChild(this._parseBinaryExpr()); return this.finish(node); } return node; } _parseOperation() { if (!this.peek(TokenType.ParenthesisL)) { return null; } const node = this.create(nodes.Node); this.consumeToken(); while (node.addChild(this._parseListElement())) { this.accept(TokenType.Comma); // optional } if (!this.accept(TokenType.ParenthesisR)) { return this.finish(node, ParseError.RightParenthesisExpected); } return this.finish(node); } _parseListElement() { const node = this.create(nodes.ListEntry); const child = this._parseBinaryExpr(); if (!child) { return null; } if (this.accept(TokenType.Colon)) { node.setKey(child); if (!node.setValue(this._parseBinaryExpr())) { return this.finish(node, ParseError.ExpressionExpected); } } else { node.setValue(child); } return this.finish(node); } _parseUse() { if (!this.peekKeyword('@use')) { return null; } const node = this.create(nodes.Use); this.consumeToken(); // @use if (!node.addChild(this._parseStringLiteral())) { return this.finish(node, ParseError.StringLiteralExpected); } if (!this.peek(TokenType.SemiColon) && !this.peek(TokenType.EOF)) { if (!this.peekRegExp(TokenType.Ident, /as|with/)) { return this.finish(node, ParseError.UnknownKeyword); } if (this.acceptIdent('as') && (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Module])) && !this.acceptDelim('*'))) { return this.finish(node, ParseError.IdentifierOrWildcardExpected); } if (this.acceptIdent('with')) { if (!this.accept(TokenType.ParenthesisL)) { return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.ParenthesisR]); } // First variable statement, no comma. if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } while (this.accept(TokenType.Comma)) { if (this.peek(TokenType.ParenthesisR)) { break; } if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } } if (!this.accept(TokenType.ParenthesisR)) { return this.finish(node, ParseError.RightParenthesisExpected); } } } if (!this.accept(TokenType.SemiColon) && !this.accept(TokenType.EOF)) { return this.finish(node, ParseError.SemiColonExpected); } return this.finish(node); } _parseModuleConfigDeclaration() { const node = this.create(nodes.ModuleConfiguration); if (!node.setIdentifier(this._parseVariable())) { return null; } if (!this.accept(TokenType.Colon) || !node.setValue(this._parseExpr(true))) { return this.finish(node, ParseError.VariableValueExpected, [], [TokenType.Comma, TokenType.ParenthesisR]); } if (this.accept(TokenType.Exclamation)) { if (this.hasWhitespace() || !this.acceptIdent('default')) { return this.finish(node, ParseError.UnknownKeyword); } } return this.finish(node); } _parseForward() { if (!this.peekKeyword('@forward')) { return null; } const node = this.create(nodes.Forward); this.consumeToken(); if (!node.addChild(this._parseStringLiteral())) { return this.finish(node, ParseError.StringLiteralExpected); } if (this.acceptIdent('as')) { const identifier = this._parseIdent([nodes.ReferenceType.Forward]); if (!node.setIdentifier(identifier)) { return this.finish(node, ParseError.IdentifierExpected); } // Wildcard must be the next character after the identifier string. if (this.hasWhitespace() || !this.acceptDelim('*')) { return this.finish(node, ParseError.WildcardExpected); } } if (this.acceptIdent('with')) { if (!this.accept(TokenType.ParenthesisL)) { return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.ParenthesisR]); } // First variable statement, no comma. if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } while (this.accept(TokenType.Comma)) { if (this.peek(TokenType.ParenthesisR)) { break; } if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) { return this.finish(node, ParseError.VariableNameExpected); } } if (!this.accept(TokenType.ParenthesisR)) { return this.finish(node, ParseError.RightParenthesisExpected); } } else if (this.peekIdent('hide') || this.peekIdent('show')) { if (!node.addChild(this._parseForwardVisibility())) { return this.finish(node, ParseError.IdentifierOrVariableExpected); } } if (!this.accept(TokenType.SemiColon) && !this.accept(TokenType.EOF)) { return this.finish(node, ParseError.SemiColonExpected); } return this.finish(node); } _parseForwardVisibility() { const node = this.create(nodes.ForwardVisibility); // Assume to be "hide" or "show". node.setIdentifier(this._parseIdent()); while (node.addChild(this._parseVariable() || this._parseIdent())) { // Consume all variables and idents ahead. this.accept(TokenType.Comma); } // More than just identifier return node.getChildren().length > 1 ? node : null; } _parseSupportsCondition() { return this._parseInterpolation() || super._parseSupportsCondition(); } }