Skip to content

Commit 389f082

Browse files
matthew-deanaeschli
authored andcommitted
support for functions & properties. part of PR #135, pushed by aeschli
1 parent 7c7ac61 commit 389f082

File tree

5 files changed

+137
-49
lines changed

5 files changed

+137
-49
lines changed

src/parser/cssNodes.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -608,7 +608,7 @@ export class Declaration extends AbstractDeclaration {
608608

609609
public property: Property;
610610
public value: Expression;
611-
public nestedProprties: NestedProperties;
611+
public nestedProperties: NestedProperties;
612612

613613
constructor(offset: number, length: number) {
614614
super(offset, length);
@@ -657,11 +657,11 @@ export class Declaration extends AbstractDeclaration {
657657
}
658658

659659
public setNestedProperties(value: NestedProperties): boolean {
660-
return this.setNode('nestedProprties', value);
660+
return this.setNode('nestedProperties', value);
661661
}
662662

663663
public getNestedProperties(): NestedProperties {
664-
return this.nestedProprties;
664+
return this.nestedProperties;
665665
}
666666
}
667667

@@ -1315,6 +1315,7 @@ export class VariableDeclaration extends AbstractDeclaration {
13151315

13161316
private variable: Variable;
13171317
private value: Node;
1318+
public needsSemicolon: boolean = true;
13181319

13191320
constructor(offset: number, length: number) {
13201321
super(offset, length);

src/parser/cssParser.ts

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -270,20 +270,20 @@ export class Parser {
270270
return this.finish(node);
271271
}
272272

273-
public _parseStylesheetStatement(): nodes.Node {
273+
public _parseStylesheetStatement(isNested: boolean = false): nodes.Node {
274274
if (this.peek(TokenType.AtKeyword)) {
275-
return this._parseStylesheetAtStatement();
275+
return this._parseStylesheetAtStatement(isNested);
276276
}
277-
return this._parseRuleset(false);
277+
return this._parseRuleset(isNested);
278278
}
279279

280-
public _parseStylesheetAtStatement(): nodes.Node {
280+
public _parseStylesheetAtStatement(isNested: boolean = false): nodes.Node {
281281
return this._parseImport()
282-
|| this._parseMedia()
282+
|| this._parseMedia(isNested)
283283
|| this._parsePage()
284284
|| this._parseFontFace()
285285
|| this._parseKeyframe()
286-
|| this._parseSupports()
286+
|| this._parseSupports(isNested)
287287
|| this._parseViewPort()
288288
|| this._parseNamespace()
289289
|| this._parseDocument()
@@ -361,7 +361,6 @@ export class Parser {
361361
case nodes.NodeType.MixinDeclaration:
362362
case nodes.NodeType.FunctionDeclaration:
363363
return false;
364-
case nodes.NodeType.VariableDeclaration:
365364
case nodes.NodeType.ExtendsReference:
366365
case nodes.NodeType.MixinContent:
367366
case nodes.NodeType.ReturnStatement:
@@ -371,6 +370,8 @@ export class Parser {
371370
case nodes.NodeType.AtApplyRule:
372371
case nodes.NodeType.CustomPropertyDeclaration:
373372
return true;
373+
case nodes.NodeType.VariableDeclaration:
374+
return (<nodes.VariableDeclaration>node).needsSemicolon;
374375
case nodes.NodeType.MixinReference:
375376
return !(<nodes.MixinReference>node).getContent();
376377
case nodes.NodeType.Declaration:
@@ -777,11 +778,11 @@ export class Parser {
777778
public _parseSupportsDeclaration(isNested = false): nodes.Node {
778779
if (isNested) {
779780
// if nested, the body can contain rulesets, but also declarations
780-
return this._tryParseRuleset(isNested)
781+
return this._tryParseRuleset(true)
781782
|| this._tryToParseDeclaration()
782-
|| this._parseStylesheetStatement();
783+
|| this._parseStylesheetStatement(true);
783784
}
784-
return this._parseStylesheetStatement();
785+
return this._parseStylesheetStatement(false);
785786
}
786787

787788
private _parseSupportsCondition(): nodes.Node {
@@ -844,9 +845,13 @@ export class Parser {
844845
}
845846

846847
public _parseMediaDeclaration(isNested = false): nodes.Node {
847-
return this._tryParseRuleset(isNested)
848-
|| this._tryToParseDeclaration()
849-
|| this._parseStylesheetStatement();
848+
if (isNested) {
849+
// if nested, the body can contain rulesets, but also declarations
850+
return this._tryParseRuleset(true)
851+
|| this._tryToParseDeclaration()
852+
|| this._parseStylesheetStatement(true);
853+
}
854+
return this._parseStylesheetStatement(false);
850855
}
851856

852857
public _parseMedia(isNested = false): nodes.Node {

src/parser/lessParser.ts

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,16 @@ export class LESSParser extends cssParser.Parser {
2020
super(new lessScanner.LESSScanner());
2121
}
2222

23-
public _parseStylesheetStatement(): nodes.Node {
23+
public _parseStylesheetStatement(isNested: boolean = false): nodes.Node {
2424
if (this.peek(TokenType.AtKeyword)) {
2525
return this._parseVariableDeclaration()
2626
|| this._parsePlugin()
27-
|| super._parseStylesheetAtStatement();
27+
|| super._parseStylesheetAtStatement(isNested);
2828
}
2929

3030
return this._tryParseMixinDeclaration()
31-
|| this._tryParseMixinReference(true)
31+
|| this._tryParseMixinReference()
32+
|| this._parseFunction()
3233
|| this._parseRuleset(true);
3334
}
3435

@@ -104,7 +105,7 @@ export class LESSParser extends cssParser.Parser {
104105
|| this._tryParseMixinDeclaration()
105106
|| this._tryParseMixinReference()
106107
|| this._parseDetachedRuleSetMixin()
107-
|| this._parseStylesheetStatement();
108+
|| this._parseStylesheetStatement(isNested);
108109
}
109110

110111
public _parseMediaFeatureName(): nodes.Node {
@@ -115,13 +116,15 @@ export class LESSParser extends cssParser.Parser {
115116
let node = <nodes.VariableDeclaration>this.create(nodes.VariableDeclaration);
116117

117118
let mark = this.mark();
118-
if (!node.setVariable(this._parseVariable())) {
119+
if (!this.peekDelim('@') && !this.peek(TokenType.AtKeyword) || !node.setVariable(this._parseVariable())) {
119120
return null;
120121
}
121122

122123
if (this.accept(TokenType.Colon)) {
123124
node.colonPosition = this.prevToken.offset;
124-
if (!node.setValue(this._parseDetachedRuleSet() || this._parseExpr())) {
125+
if (node.setValue(this._parseDetachedRuleSet())) {
126+
node.needsSemicolon = false;
127+
} else if (!node.setValue(this._parseExpr())) {
125128
return <nodes.VariableDeclaration>this.finish(node, ParseError.VariableValueExpected, [], panic);
126129
}
127130

@@ -150,23 +153,24 @@ export class LESSParser extends cssParser.Parser {
150153
}
151154

152155
public _parseDetachedRuleSetBody(): nodes.Node {
153-
return this._tryParseKeyframeSelector() || this._tryParseRuleset(true) || super._parseRuleSetDeclaration();
156+
return this._tryParseKeyframeSelector() || this._parseRuleSetDeclaration();
154157
}
155158

156159
public _parseVariable(): nodes.Variable {
157-
if (!this.peekDelim('@') && !this.peek(TokenType.AtKeyword)) {
160+
if (!this.peekDelim('@') && !this.peekDelim('$') && !this.peek(TokenType.AtKeyword)) {
158161
return null;
159162
}
160163

161164
let node = <nodes.Variable>this.create(nodes.Variable);
162165
let mark = this.mark();
163-
while (this.acceptDelim('@')) {
166+
167+
while (this.acceptDelim('@') || this.acceptDelim('$')) {
164168
if (this.hasWhitespace()) {
165169
this.restoreAtMark(mark);
166170
return null;
167171
}
168172
}
169-
if (!this.accept(TokenType.AtKeyword)) {
173+
if (!this.accept(TokenType.AtKeyword) && !this.accept(TokenType.Ident)) {
170174
this.restoreAtMark(mark);
171175
return null;
172176
}
@@ -179,7 +183,8 @@ export class LESSParser extends cssParser.Parser {
179183

180184
term = <nodes.Term>this.create(nodes.Term);
181185
if (term.setExpression(this._parseVariable()) ||
182-
term.setExpression(this._parseEscaped())) {
186+
term.setExpression(this._parseEscaped()) ||
187+
term.setExpression(this._tryParseMixinReference())) {
183188

184189
return <nodes.Term>this.finish(term);
185190
}
@@ -247,12 +252,14 @@ export class LESSParser extends cssParser.Parser {
247252
|| this._parseImport()
248253
|| this._parseSupports(true) // @supports
249254
|| this._parseDetachedRuleSetMixin() // less detached ruleset mixin
250-
|| this._parseVariableDeclaration(); // Variable declarations
255+
|| this._parseVariableDeclaration() // Variable declarations
256+
|| this._parseUnknownAtRule();
251257

252258
}
253259
return this._tryParseMixinDeclaration()
254260
|| this._tryParseRuleset(true) // nested ruleset
255261
|| this._tryParseMixinReference() // less mixin reference
262+
|| this._parseFunction()
256263
|| this._parseExtend() // less extend declaration
257264
|| super._parseRuleSetDeclaration(); // try css ruleset declaration as the last option
258265
}
@@ -332,7 +339,10 @@ export class LESSParser extends cssParser.Parser {
332339
}
333340

334341
private peekInterpolatedIdent() {
335-
return this.peek(TokenType.Ident) || this.peekDelim('@') || this.peekDelim('-');
342+
return this.peek(TokenType.Ident) ||
343+
this.peekDelim('@') ||
344+
this.peekDelim('$') ||
345+
this.peekDelim('-');
336346
}
337347

338348
public _acceptInterpolatedIdent(node: nodes.Node): boolean {
@@ -361,9 +371,10 @@ export class LESSParser extends cssParser.Parser {
361371
}
362372

363373
public _parseInterpolation(): nodes.Node {
364-
// @{name}
374+
// @{name} Variable or
375+
// ${name} Property
365376
let mark = this.mark();
366-
if (this.peekDelim('@')) {
377+
if (this.peekDelim('@') || this.peekDelim('$')) {
367378
let node = this.createNode(nodes.NodeType.Interpolation);
368379
this.consumeToken();
369380
if (this.hasWhitespace() || !this.accept(TokenType.CurlyL)) {
@@ -492,7 +503,7 @@ export class LESSParser extends cssParser.Parser {
492503
}
493504
let mark = this.mark();
494505
let node = <nodes.MixinReference>this.create(nodes.MixinReference);
495-
if (!node.addChild(this._parseVariable()) || !this.accept(TokenType.ParenthesisL)) {
506+
if (node.addChild(this._parseVariable()) && (this.hasWhitespace() || !this.accept(TokenType.ParenthesisL))) {
496507
this.restoreAtMark(mark);
497508
return null;
498509
}
@@ -503,7 +514,7 @@ export class LESSParser extends cssParser.Parser {
503514
}
504515

505516

506-
public _tryParseMixinReference(atRoot = false): nodes.Node {
517+
public _tryParseMixinReference(): nodes.Node {
507518
let mark = this.mark();
508519
let node = <nodes.MixinReference>this.create(nodes.MixinReference);
509520

@@ -602,7 +613,7 @@ export class LESSParser extends cssParser.Parser {
602613
this.accept(TokenType.Colon);
603614
hasContent = true;
604615
}
605-
if (!node.setDefaultValue(this._parseExpr(true)) && !hasContent) {
616+
if (!node.setDefaultValue(this._parseDetachedRuleSet() || this._parseExpr(true)) && !hasContent) {
606617
return null;
607618
}
608619
return this.finish(node);
@@ -647,6 +658,37 @@ export class LESSParser extends cssParser.Parser {
647658
return this.finish(node);
648659
}
649660

661+
public _parseFunction(): nodes.Function {
662+
663+
let pos = this.mark();
664+
let node = <nodes.Function>this.create(nodes.Function);
665+
666+
if (!node.setIdentifier(this._parseFunctionIdentifier())) {
667+
return null;
668+
}
669+
670+
if (this.hasWhitespace() || !this.accept(TokenType.ParenthesisL)) {
671+
this.restoreAtMark(pos);
672+
return null;
673+
}
674+
675+
if (node.getArguments().addChild(this._parseMixinArgument())) {
676+
while (this.accept(TokenType.Comma) || this.accept(TokenType.SemiColon)) {
677+
if (this.peek(TokenType.ParenthesisR)) {
678+
break;
679+
}
680+
if (!node.getArguments().addChild(this._parseMixinArgument())) {
681+
return this.finish(node, ParseError.ExpressionExpected);
682+
}
683+
}
684+
}
685+
686+
if (!this.accept(TokenType.ParenthesisR)) {
687+
return <nodes.Function>this.finish(node, ParseError.RightParenthesisExpected);
688+
}
689+
return <nodes.Function>this.finish(node);
690+
}
691+
650692
public _parseFunctionIdentifier(): nodes.Identifier {
651693
if (this.peekDelim('%')) {
652694
let node = <nodes.Identifier>this.create(nodes.Identifier);

src/services/lessCompletion.ts

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,41 @@ interface IFunctionInfo {
2222
export class LESSCompletion extends CSSCompletion {
2323

2424
private static builtInProposals: IFunctionInfo[] = [
25+
// Boolean functions
26+
{
27+
'name': 'if',
28+
'example': 'if(condition, trueValue [, falseValue]);',
29+
'description': localize('less.builtin.if', 'returns one of two values depending on a condition.')
30+
},
31+
{
32+
'name': 'boolean',
33+
'example': 'boolean(condition);',
34+
'description': localize('less.builtin.boolean', '"store" a boolean test for later evaluation in a guard or if().')
35+
},
36+
37+
// List functions
38+
{
39+
'name': 'length',
40+
'example': 'length(@list);',
41+
'description': localize('less.builtin.length', 'returns the number of elements in a value list')
42+
},
43+
{
44+
'name': 'extract',
45+
'example': 'extract(@list, index);',
46+
'description': localize('less.builtin.extract', 'returns a value at the specified position in the list')
47+
},
48+
{
49+
'name': 'range',
50+
'example': 'range([start, ] end [, step]);',
51+
'description': localize('less.builtin.range', 'generate a list spanning a range of values')
52+
},
53+
{
54+
'name': 'each',
55+
'example': 'each(@list, ruleset);',
56+
'description': localize('less.builtin.each', 'bind the evaluation of a ruleset to each member of a list.')
57+
},
58+
59+
// Other built-ins
2560
{
2661
'name': 'escape',
2762
'example': 'escape(@string);',
@@ -59,16 +94,6 @@ export class LESSCompletion extends CSSCompletion {
5994
'description': localize('less.builtin.data-uri', 'inlines a resource and falls back to `url()`'),
6095
'type': 'url'
6196
},
62-
{
63-
'name': 'length',
64-
'example': 'length(@list);',
65-
'description': localize('less.builtin.length', 'returns the number of elements in a value list')
66-
},
67-
{
68-
'name': 'extract',
69-
'example': 'extract(@list, index);',
70-
'description': localize('less.builtin.extract', 'returns a value at the specified position in the list')
71-
},
7297
{
7398
'name': 'abs',
7499
'description': localize('less.builtin.abs', 'absolute value of a number'),
@@ -368,11 +393,11 @@ export class LESSCompletion extends CSSCompletion {
368393
this.createFunctionProposals(LESSCompletion.colorProposals, existingNode, false, result);
369394
return super.getColorProposals(entry, existingNode, result);
370395
}
371-
396+
372397
public getCompletionsForDeclarationProperty(declaration: nodes.Declaration, result: CompletionList): CompletionList {
373398
this.getCompletionsForSelector(null, true, result);
374399
return super.getCompletionsForDeclarationProperty(declaration, result);
375-
}
400+
}
376401

377402
}
378403

0 commit comments

Comments
 (0)