Skip to content

Commit

Permalink
Handle unknown at rules. Fix #51
Browse files Browse the repository at this point in the history
  • Loading branch information
octref committed Jun 14, 2018
1 parent 0c9bc14 commit c91d5bf
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 32 deletions.
1 change: 1 addition & 0 deletions src/parser/cssErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export let ParseError = {
CommaExpected: new CSSIssueType('css-commaexpected', localize('expected.comma', "comma expected")),
PageDirectiveOrDeclarationExpected: new CSSIssueType('css-pagedirordeclexpected', localize('expected.pagedirordecl', "page directive or declaraton expected")),
UnknownAtRule: new CSSIssueType('css-unknownatrule', localize('unknown.atrule', "at-rule unknown")),
AtRuleBodyExpected: new CSSIssueType('css-atrulebodyexpected', localize('expected.atrulebody', 'at rule body expected')),
UnknownKeyword: new CSSIssueType('css-unknownkeyword', localize('unknown.keyword', "unknown keyword")),
SelectorExpected: new CSSIssueType('css-selectorexpected', localize('expected.selector', "selector expected")),
StringLiteralExpected: new CSSIssueType('css-stringliteralexpected', localize('expected.stringliteral', "string literal expected")),
Expand Down
14 changes: 13 additions & 1 deletion src/parser/cssNodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ export enum NodeType {
SupportsCondition,
NamespacePrefix,
GridLine,
Plugin
Plugin,
UnknownAtRule,
}

export enum ReferenceType {
Expand Down Expand Up @@ -1486,6 +1487,17 @@ export class MixinDeclaration extends BodyDeclaration {
}
}

export class UnknownAtRule extends BodyDeclaration {

constructor(offset: number, length: number) {
super(offset, length);
}

public get type(): NodeType {
return NodeType.UnknownAtRule;
}
}

export class ListEntry extends Node {

public key?: Node;
Expand Down
45 changes: 36 additions & 9 deletions src/parser/cssParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,19 +272,24 @@ export class Parser {

public _parseStylesheetStatement(): nodes.Node {
if (this.peek(TokenType.AtKeyword)) {
return this._parseImport()
|| this._parseMedia()
|| this._parsePage()
|| this._parseFontFace()
|| this._parseKeyframe()
|| this._parseSupports()
|| this._parseViewPort()
|| this._parseNamespace()
|| this._parseDocument();
return this._parseStylesheetAtStatement();
}
return this._parseRuleset(false);
}

public _parseStylesheetAtStatement(): nodes.Node {
return this._parseImport()
|| this._parseMedia()
|| this._parsePage()
|| this._parseFontFace()
|| this._parseKeyframe()
|| this._parseSupports()
|| this._parseViewPort()
|| this._parseNamespace()
|| this._parseDocument()
|| this._parseUnknownAtRule();
}

public _tryParseRuleset(isNested: boolean): nodes.RuleSet {
let mark = this.mark();
if (this._parseSelector(isNested)) {
Expand Down Expand Up @@ -992,6 +997,28 @@ export class Parser {
return this._parseBody(node, this._parseStylesheetStatement.bind(this));
}

// https://www.w3.org/TR/css-syntax-3/#consume-an-at-rule
public _parseUnknownAtRule(): nodes.Node {
if (!this.peek(TokenType.AtKeyword)) {
return null;
}

let node = <nodes.UnknownAtRule>this.create(nodes.UnknownAtRule);
this.consumeToken();

this.resync([], [TokenType.SemiColon, TokenType.EOF, TokenType.CurlyL]); // ignore all the rules
if (this.peek(TokenType.SemiColon)) {
this.consumeToken();
return node;
} else if (this.peek(TokenType.CurlyL)) {
return this._parseBody(node, this._parseStylesheetStatement.bind(this));
} else if (this.peek(TokenType.EOF)) {
return this.finish(node, ParseError.AtRuleBodyExpected);
}

return node;
}

public _parseOperator(): nodes.Operator {
// these are operators for binary expressions
if (this.peekDelim('/') ||
Expand Down
16 changes: 8 additions & 8 deletions src/parser/cssScanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export enum TokenType {
Percentage,
Dimension,
UnicodeRange,
CDO,
CDC,
CDO, // <!--
CDC, // -->
Colon,
SemiColon,
CurlyL,
Expand All @@ -27,13 +27,13 @@ export enum TokenType {
BracketR,
Whitespace,
Includes,
Dashmatch,
SubstringOperator,
PrefixOperator,
SuffixOperator,
Dashmatch, // |=
SubstringOperator, // *=
PrefixOperator, // ^=
SuffixOperator, // $=
Delim,
EMS,
EXS,
EMS, // 3em
EXS, // 3ex
Length,
Angle,
Time,
Expand Down
10 changes: 7 additions & 3 deletions src/parser/lessParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,15 @@ export class LESSParser extends cssParser.Parser {
}

public _parseStylesheetStatement(): nodes.Node {
if (this.peek(TokenType.AtKeyword)) {
return this._parseVariableDeclaration()
|| this._parsePlugin()
|| super._parseStylesheetAtStatement();
}

return this._tryParseMixinDeclaration()
|| this._tryParseMixinReference(true)
|| super._parseStylesheetStatement()
|| this._parseVariableDeclaration()
|| this._parsePlugin();
|| this._parseRuleset(true);
}

public _parseImport(): nodes.Node {
Expand Down
9 changes: 3 additions & 6 deletions src/parser/scssParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,16 @@ export class SCSSParser extends cssParser.Parser {
}

public _parseStylesheetStatement(): nodes.Node {
let node = super._parseStylesheetStatement();
if (node) {
return node;
}
if (this.peek(TokenType.AtKeyword)) {
return this._parseWarnAndDebug()
|| this._parseControlStatement()
|| this._parseMixinDeclaration()
|| this._parseMixinContent()
|| this._parseMixinReference() // @include
|| this._parseFunctionDeclaration();
|| this._parseFunctionDeclaration()
|| super._parseStylesheetAtStatement();
}
return this._parseVariableDeclaration();
return this._parseRuleset(true) || this._parseVariableDeclaration();
}

public _parseImport(): nodes.Node {
Expand Down
15 changes: 13 additions & 2 deletions src/test/css/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,22 @@ suite('CSS - Parser', () => {
assertNode('E.warning E#myid E:not(s) {}', parser, parser._parseStylesheet.bind(parser));
assertError('@namespace;', parser, parser._parseStylesheet.bind(parser), ParseError.URIExpected);
assertError('@namespace url(http://test)', parser, parser._parseStylesheet.bind(parser), ParseError.SemiColonExpected);
assertError('@mskeyframes darkWordHighlight { from { background-color: inherit; } to { background-color: rgba(83, 83, 83, 0.7); } }', parser, parser._parseStylesheet.bind(parser), ParseError.UnknownAtRule);
assertError('@charset;', parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected);
assertError('@charset \'utf8\'', parser, parser._parseStylesheet.bind(parser), ParseError.SemiColonExpected);
});

test('stylesheet - graceful handling of unknown rules', function () {
let parser = new Parser();
assertNode('@unknown-rule;', parser, parser._parseStylesheet.bind(parser));
assertNode('@unknown-rule (;', parser, parser._parseStylesheet.bind(parser));
assertNode(`@unknown-rule 'foo';`, parser, parser._parseStylesheet.bind(parser));
assertNode('@unknown-rule (foo) {}', parser, parser._parseStylesheet.bind(parser));
assertNode('@unknown-rule (foo) { .bar {} }', parser, parser._parseStylesheet.bind(parser));
assertNode('@mskeyframes darkWordHighlight { from { background-color: inherit; } to { background-color: rgba(83, 83, 83, 0.7); } }', parser, parser._parseStylesheet.bind(parser));

assertError('@unknown-rule (foo) { .bar {}', parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected);
});

test('stylesheet /panic/', function () {
let parser = new Parser();
assertError('#boo, far } \n.far boo {}', parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected);
Expand Down Expand Up @@ -195,7 +206,7 @@ suite('CSS - Parser', () => {
assertNode('@page { @top-right-corner { content: url(foo.png); border: solid green; } }', parser, parser._parsePage.bind(parser));
assertNode('@page { @top-left-corner { content: " "; border: solid green; } @bottom-right-corner { content: counter(page); border: solid green; } }', parser, parser._parsePage.bind(parser));
assertError('@page { @top-left-corner foo { content: " "; border: solid green; } }', parser, parser._parsePage.bind(parser), ParseError.LeftCurlyExpected);
assertError('@page { @XY foo { content: " "; border: solid green; } }', parser, parser._parsePage.bind(parser), ParseError.UnknownAtRule);
// assertError('@page { @XY foo { content: " "; border: solid green; } }', parser, parser._parsePage.bind(parser), ParseError.UnknownAtRule);
assertError('@page :left { margin-left: 4cm margin-right: 3cm; }', parser, parser._parsePage.bind(parser), ParseError.SemiColonExpected);
assertError('@page : { }', parser, parser._parsePage.bind(parser), ParseError.IdentifierExpected);
assertError('@page :left, { }', parser, parser._parsePage.bind(parser), ParseError.IdentifierExpected);
Expand Down
3 changes: 1 addition & 2 deletions src/test/less/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,7 @@ suite('LESS - Parser', () => {
assertNode('.something { @media (max-width: 760px) { > div { display: block; } } }', parser, parser._parseStylesheet.bind(parser));
assertNode('@media (@var) {}', parser, parser._parseMedia.bind(parser));
assertNode('@media screen and (@var) {}', parser, parser._parseMedia.bind(parser));

assertError('@media (max-width: 760px) { + div { display: block; } }', parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected);
assertNode('@media (max-width: 760px) { + div { display: block; } }', parser, parser._parseStylesheet.bind(parser));
});

test('VariableDeclaration', function () {
Expand Down
2 changes: 1 addition & 1 deletion src/test/scss/parser.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ suite('SCSS - Parser', () => {
assertNode('.something { @media (max-width: 760px) { > .test { color: blue; } } }', parser, parser._parseStylesheet.bind(parser));
assertNode('.something { @media (max-width: 760px) { ~ div { display: block; } } }', parser, parser._parseStylesheet.bind(parser));
assertNode('.something { @media (max-width: 760px) { + div { display: block; } } }', parser, parser._parseStylesheet.bind(parser));
assertError('@media (max-width: 760px) { + div { display: block; } }', parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected);
assertNode('@media (max-width: 760px) { + div { display: block; } }', parser, parser._parseStylesheet.bind(parser));
});

test('@keyframe', function () {
Expand Down

0 comments on commit c91d5bf

Please sign in to comment.