提交 1e7486aa 编写于 作者: M Martin Aeschlimann

Move LESS to extension

上级 0517cce2
......@@ -17,6 +17,8 @@ import {CSSValidation} from './services/cssValidation';
import {SCSSParser} from './parser/scssParser';
import {SCSSCompletion} from './services/scssCompletion';
import {LESSParser} from './parser/lessParser';
import {LESSCompletion} from './services/lessCompletion';
export interface LanguageService {
configure(raw: LanguageSettings): void;
......@@ -37,12 +39,12 @@ export interface LanguageSettings {
lint?: any;
}
let cssParser = new Parser();
let cssCompletion = new CSSCompletion();
let cssHover = new CSSHover();
let cssValidation = new CSSValidation();
let cssNavigation = new CSSNavigation();
let cssCodeActions = new CSSCodeActions();
let cssParser = new Parser();
let cssCompletion = new CSSCompletion();
let cssHover = new CSSHover();
let cssValidation = new CSSValidation();
let cssNavigation = new CSSNavigation();
let cssCodeActions = new CSSCodeActions();
export function getCSSLanguageService() : LanguageService {
return {
......@@ -68,4 +70,14 @@ export function getSCSSLanguageService() : LanguageService {
languageService.parseStylesheet = scssParser.parseStylesheet.bind(scssParser);
languageService.doComplete = scssCompletion.doComplete.bind(scssCompletion);
return languageService;
}
let lessParser = new LESSParser();
let lessCompletion = new LESSCompletion();
export function getLESSLanguageService() : LanguageService {
let languageService = getCSSLanguageService();
languageService.parseStylesheet = lessParser.parseStylesheet.bind(lessParser);
languageService.doComplete = lessCompletion.doComplete.bind(lessCompletion);
return languageService;
}
\ No newline at end of file
......@@ -9,7 +9,7 @@ import {
TextDocuments, TextDocument, InitializeParams, InitializeResult, RequestType
} from 'vscode-languageserver';
import {getCSSLanguageService, getSCSSLanguageService, LanguageSettings, LanguageService} from './cssLanguageService';
import {getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService} from './cssLanguageService';
import {Stylesheet} from './parser/cssNodes';
import * as nls from 'vscode-nls';
......@@ -57,7 +57,8 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
let languageServices : { [id:string]: LanguageService} = {
css: getCSSLanguageService(),
scss: getSCSSLanguageService()
scss: getSCSSLanguageService(),
less: getLESSLanguageService()
};
function getLanguageService(document: TextDocument) {
......
/*---------------------------------------------------------------------------------------------
* 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 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>
export class LESSParser extends cssParser.Parser {
public constructor() {
super(new lessScanner.LESSScanner());
}
public _parseStylesheetStatement(): nodes.Node {
return this._tryParseMixinDeclaration() || super._parseStylesheetStatement() || this._parseVariableDeclaration();
}
public _parseImport(): nodes.Node {
let node = <nodes.Import>this.create(nodes.Import);
if (!this.accept(TokenType.AtKeyword, '@import') && !this.accept(TokenType.AtKeyword, '@import-once') /* deprecated in less 1.4.1 */) {
return null;
}
// 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]);
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.SemiColon]);
}
}
if (!this.accept(TokenType.URI) && !this.accept(TokenType.String)) {
return this.finish(node, ParseError.URIOrStringExpected, [TokenType.SemiColon]);
}
node.setMedialist(this._parseMediaList());
return this.finish(node);
}
public _parseMediaQuery(resyncStopToken: TokenType[]): nodes.Node {
let node = <nodes.MediaQuery>super._parseMediaQuery(resyncStopToken);
if (!node) {
let node = <nodes.MediaQuery>this.create(nodes.MediaQuery);
if (node.addChild(this._parseVariable())) {
return this.finish(node);
}
return null;
}
return node;
}
public _parseVariableDeclaration(panic: TokenType[] = []): nodes.VariableDeclaration {
let node = <nodes.VariableDeclaration>this.create(nodes.VariableDeclaration);
let mark = this.mark();
if (!node.setVariable(this._parseVariable())) {
return null;
}
if (this.accept(TokenType.Colon, ':')) {
node.colonPosition = this.prevToken.offset;
if (!node.setValue(this._parseExpr())) {
return <nodes.VariableDeclaration>this.finish(node, ParseError.VariableValueExpected, [], panic);
}
} 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 <nodes.VariableDeclaration>this.finish(node);
}
public _parseVariable(): nodes.Variable {
let node = <nodes.Variable>this.create(nodes.Variable);
let mark = this.mark();
while (this.accept(TokenType.Delim, '@')) {
if (this.hasWhitespace()) {
this.restoreAtMark(mark);
return null;
}
}
if (!this.accept(TokenType.AtKeyword)) {
this.restoreAtMark(mark);
return null;
}
return <nodes.Variable>node;
}
public _parseTerm(): nodes.Term {
let term = super._parseTerm();
if (term) { return term; }
term = <nodes.Term>this.create(nodes.Term);
if (term.setExpression(this._parseVariable()) ||
term.setExpression(this._parseEscaped())) {
return <nodes.Term>this.finish(term);
}
return null;
}
public _parseEscaped(): nodes.Node {
let node = this.createNode(nodes.NodeType.EscapedValue);
if (this.accept(TokenType.EscapedJavaScript) ||
this.accept(TokenType.BadEscapedJavaScript)) {
return this.finish(node);
}
if (this.accept(TokenType.Delim, '~')) {
return this.finish(node, this.accept(TokenType.String) ? null : ParseError.TermExpected);
}
return null;
}
public _parseOperator(): nodes.Node {
let node = this._parseGuardOperator();
if (node) {
return node;
} else {
return super._parseOperator();
}
}
public _parseGuardOperator(): nodes.Node {
let node = this.createNode(nodes.NodeType.Operator);
if (this.accept(TokenType.Delim, '>')) {
this.accept(TokenType.Delim, '=');
return node;
} else if (this.accept(TokenType.Delim, '=')) {
this.accept(TokenType.Delim, '<');
return node;
} else if (this.accept(TokenType.Delim, '<')) {
return node;
}
return null;
}
public _parseRuleSetDeclaration(): nodes.Node {
if (this.peek(TokenType.AtKeyword)) {
return this._parseKeyframe()
|| this._parseMedia()
|| this._parseVariableDeclaration(); // Variable declarations
}
return this._tryParseMixinDeclaration()
|| this._tryParseRuleset(true) // nested ruleset
|| this._parseMixinReference() // less mixin reference
|| this._parseExtend() // less extend declaration
|| super._parseRuleSetDeclaration(); // try css ruleset declaration as the last option
}
public _parseSimpleSelectorBody(): nodes.Node {
return this._parseSelectorCombinator() || super._parseSimpleSelectorBody();
}
public _parseSelectorCombinator(): nodes.Node {
let node = this.createNode(nodes.NodeType.SelectorCombinator);
if (this.accept(TokenType.Delim, '&')) {
while (!this.hasWhitespace() && (this.accept(TokenType.Delim, '-') || node.addChild(this._parseIdent()) || this.accept(TokenType.Delim, '&'))) {
// support &-foo
}
return this.finish(node);
}
return null;
}
public _parseSelectorIdent(): nodes.Node {
return this._parseIdent() || this._parseSelectorInterpolation();
}
public _parseSelectorInterpolation(): nodes.Node {
// Selector interpolation; old: ~"@{name}", new: @{name}
let node = this.createNode(nodes.NodeType.SelectorInterpolation);
if (this.accept(TokenType.Delim, '~')) {
if (!this.hasWhitespace() && (this.accept(TokenType.String) || this.accept(TokenType.BadString))) {
return this.finish(node);
}
return this.finish(node, ParseError.StringLiteralExpected);
} else if (this.accept(TokenType.Delim, '@')) {
if (this.hasWhitespace() || !this.accept(TokenType.CurlyL)) {
return this.finish(node, ParseError.LeftCurlyExpected);
}
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;
}
public _tryParseMixinDeclaration(): nodes.Node {
if (!this.peek(TokenType.Delim, '.')) {
return null;
}
let mark = this.mark();
let node = <nodes.MixinDeclaration>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 (!node.getParameters().addChild(this._parseMixinParameter())) {
return this.finish(node, ParseError.IdentifierExpected);
}
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
node.setGuard(this._parseGuard());
if (!this.peek(TokenType.CurlyL)) {
this.restoreAtMark(mark);
return null;
}
return this._parseBody(node, this._parseRuleSetDeclaration.bind(this));
}
public _parseMixinDeclarationIdentifier(): nodes.Identifier {
let identifier = <nodes.Identifier>this.create(nodes.Identifier); // identifier should contain dot
this.consumeToken(); // .
if (this.hasWhitespace() || !this.accept(TokenType.Ident)) {
return null;
}
identifier.referenceTypes = [nodes.ReferenceType.Mixin];
return this.finish(identifier);
}
public _parseExtend(): nodes.Node {
if (!this.peek(TokenType.Delim, '&')) {
return null;
}
let mark = this.mark();
let node = <nodes.ExtendsReference>this.create(nodes.ExtendsReference);
this.consumeToken(); // &
if (this.hasWhitespace() || !this.accept(TokenType.Colon) || !this.accept(TokenType.Ident, 'extend')) {
this.restoreAtMark(mark);
return null;
}
if (!this.accept(TokenType.ParenthesisL)) {
return this.finish(node, ParseError.LeftParenthesisExpected);
}
if (!node.setSelector(this._parseSimpleSelector())) {
return this.finish(node, ParseError.SelectorExpected);
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
return this.finish(node);
}
public _parseMixinReference(): nodes.Node {
if (!this.peek(TokenType.Delim, '.')) {
return null;
}
let node = <nodes.MixinReference>this.create(nodes.MixinReference);
let identifier = <nodes.Identifier>this.create(nodes.Identifier);
this.consumeToken(); // dot, part of the identifier
if (this.hasWhitespace() || !this.accept(TokenType.Ident)) {
return this.finish(node, ParseError.IdentifierExpected);
}
node.setIdentifier(this.finish(identifier));
if (!this.hasWhitespace() && this.accept(TokenType.ParenthesisL)) {
if (node.getArguments().addChild(this._parseFunctionArgument())) {
while (this.accept(TokenType.Comma) || this.accept(TokenType.SemiColon)) {
if (!node.getArguments().addChild(this._parseExpr())) {
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());
return this.finish(node);
}
public _parseMixinParameter(): nodes.Node {
let node = <nodes.FunctionParameter>this.create(nodes.FunctionParameter);
// special rest variable: @rest...
if (this.peek(TokenType.AtKeyword, '@rest')) {
let 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)) {
let varargsNode = this.create(nodes.Node);
this.consumeToken();
node.setIdentifier(this.finish(varargsNode));
return this.finish(node);
}
// default variable declaration: @param: 12 or @name
if (node.setIdentifier(this._parseVariable())) {
this.accept(TokenType.Colon);
}
node.setDefaultValue(this._parseExpr(true));
return this.finish(node);
}
public _parseGuard(): nodes.LessGuard {
let node = <nodes.LessGuard>this.create(nodes.LessGuard);
if (!this.accept(TokenType.Ident, 'when')) {
return null;
}
node.isNegated = this.accept(TokenType.Ident, 'not');
if (!node.getConditions().addChild(this._parseGuardCondition())) {
return <nodes.LessGuard>this.finish(node, ParseError.ConditionExpected);
}
while (this.accept(TokenType.Ident, 'and') || this.accept(TokenType.Comma, ',')) {
if (!node.getConditions().addChild(this._parseGuardCondition())) {
return <nodes.LessGuard>this.finish(node, ParseError.ConditionExpected);
}
}
return <nodes.LessGuard>this.finish(node);
}
public _parseGuardCondition(): nodes.Node {
let node = this.create(nodes.GuardCondition);
if (!this.accept(TokenType.ParenthesisL)) {
return null;
}
if (!node.addChild(this._parseExpr())) {
// empty (?)
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
return this.finish(node);
}
public _parseFunctionIdentifier(): nodes.Identifier {
if (this.peek(TokenType.Delim, '%')) {
let node = <nodes.Identifier>this.create(nodes.Identifier);
node.referenceTypes = [nodes.ReferenceType.Function];
this.consumeToken();
return this.finish(node);
}
return super._parseFunctionIdentifier();
}
}
/*---------------------------------------------------------------------------------------------
* 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 scanner from './cssScanner';
const _FSL = '/'.charCodeAt(0);
const _NWL = '\n'.charCodeAt(0);
const _CAR = '\r'.charCodeAt(0);
const _LFD = '\f'.charCodeAt(0);
const _TIC = '`'.charCodeAt(0);
const _DOT = '.'.charCodeAt(0);
let customTokenValue = scanner.TokenType.CustomToken;
export const Ellipsis: scanner.TokenType = customTokenValue++;
export class LESSScanner extends scanner.Scanner {
public scan(): scanner.IToken {
let triviaToken = this.trivia();
if (triviaToken !== null) {
return triviaToken;
}
let offset = this.stream.pos();
// LESS: escaped JavaScript code `let a = "dddd"`
let tokenType = this.escapedJavaScript();
if (tokenType !== null) {
return this.finishToken(offset, tokenType);
}
if (this.stream.advanceIfChars([_DOT, _DOT, _DOT])) {
return this.finishToken(offset, Ellipsis);
}
return super.scan();
}
protected comment(): boolean {
if (super.comment()) {
return true;
}
if (this.stream.advanceIfChars([_FSL, _FSL])) {
this.stream.advanceWhileChar((ch: number) => {
switch (ch) {
case _NWL:
case _CAR:
case _LFD:
return false;
default:
return true;
}
});
return true;
} else {
return false;
}
}
private escapedJavaScript(): scanner.TokenType {
let ch = this.stream.peekChar();
if (ch === _TIC) {
this.stream.advance(1);
this.stream.advanceWhileChar((ch) => { return ch !== _TIC; });
return this.stream.advanceIfChar(_TIC) ? scanner.TokenType.EscapedJavaScript : scanner.TokenType.BadEscapedJavaScript;
}
return null;
}
}
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 languageFacts from './languageFacts';
import {CSSCompletion} from './cssCompletion';
import {CompletionList, CompletionItemKind} from 'vscode-languageserver';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export class LESSCompletion extends CSSCompletion {
private static builtInProposals = [
{
'name': 'escape',
'example': 'escape(@string);',
'description': localize('less.builtin.escape', 'URL encodes a string')
},
{
'name': 'e',
'example': 'e(@string);',
'description': localize('less.builtin.e', 'escape string content')
},
{
'name': 'replace',
'example': 'replace(@string, @pattern, @replacement[, @flags]);',
'description': localize('less.builtin.replace', 'string replace')
},
{
'name': 'unit',
'example': 'unit(@dimension, [@unit: \'\']);',
'description': localize('less.builtin.unit', 'remove or change the unit of a dimension')
},
{
'name': 'color',
'example': 'color(@string);',
'description': localize('less.builtin.color', 'parses a string to a color')
},
{
'name': 'convert',
'example': 'convert(@value, unit);',
'description': localize('less.builtin.convert', 'converts numbers from one type into another')
},
{
'name': 'data-uri',
'example': 'data-uri([mimetype,] url);',
'description': localize('less.builtin.data-uri', 'inlines a resource and falls back to `url()`')
},
{
'name': 'length',
'example': 'length(@list);',
'description': localize('less.builtin.length', 'returns the number of elements in a value list')
},
{
'name': 'extract',
'example': 'extract(@list, index);',
'description': localize('less.builtin.extract', 'returns a value at the specified position in the list')
},
{
'name': 'abs',
'description': localize('less.builtin.abs', 'absolute value of a number'),
'example': 'abs(number);'
},
{
'name': 'acos',
'description': localize('less.builtin.acos', 'arccosine - inverse of cosine function'),
'example': 'acos(number);'
},
{
'name': 'asin',
'description': localize('less.builtin.asin', 'arcsine - inverse of sine function'),
'example': 'asin(number);'
},
{
'name': 'ceil',
'example': 'ceil(@number);',
'description': localize('less.builtin.ceil', 'rounds up to an integer')
},
{
'name': 'cos',
'description': localize('less.builtin.cos', 'cosine function'),
'example': 'cos(number);'
},
{
'name': 'floor',
'description': localize('less.builtin.floor', 'rounds down to an integer'),
'example': 'floor(@number);'
},
{
'name': 'percentage',
'description': localize('less.builtin.percentage', 'converts to a %, e.g. 0.5 > 50%'),
'example': 'percentage(@number);'
},
{
'name': 'round',
'description': localize('less.builtin.round', 'rounds a number to a number of places'),
'example': 'round(number, [places: 0]);'
},
{
'name': 'sqrt',
'description': localize('less.builtin.sqrt', 'calculates square root of a number'),
'example': 'sqrt(number);'
},
{
'name': 'sin',
'description': localize('less.builtin.sin', 'sine function'),
'example': 'sin(number);'
},
{
'name': 'tan',
'description': localize('less.builtin.tan', 'tangent function'),
'example': 'tan(number);'
},
{
'name': 'atan',
'description': localize('less.builtin.atan', 'arctangent - inverse of tangent function'),
'example': 'atan(number);'
},
{
'name': 'pi',
'description': localize('less.builtin.pi', 'returns pi'),
'example': 'pi();'
},
{
'name': 'pow',
'description': localize('less.builtin.pow', 'first argument raised to the power of the second argument'),
'example': 'pow(@base, @exponent);'
},
{
'name': 'mod',
'description': localize('less.builtin.mod', 'first argument modulus second argument'),
'example': 'mod(number, number);'
},
{
'name': 'min',
'description': localize('less.builtin.min', 'returns the lowest of one or more values'),
'example': 'min(@x, @y);'
},
{
'name': 'max',
'description': localize('less.builtin.max', 'returns the lowest of one or more values'),
'example': 'max(@x, @y);'
}
];
private static colorProposals = [
{
'name': 'argb',
'example': 'argb(@color);',
'description': localize('less.builtin.argb', 'creates a #AARRGGBB')
},
{
'name': 'hsl',
'example': 'hsl(@hue, @saturation, @lightness);',
'description': localize('less.builtin.hsl', 'creates a color')
},
{
'name': 'hsla',
'example': 'hsla(@hue, @saturation, @lightness, @alpha);',
'description': localize('less.builtin.hsla', 'creates a color')
},
{
'name': 'hsv',
'example': 'hsv(@hue, @saturation, @value);',
'description': localize('less.builtin.hsv', 'creates a color')
},
{
'name': 'hsva',
'example': 'hsva(@hue, @saturation, @value, @alpha);',
'description': localize('less.builtin.hsva', 'creates a color')
},
{
'name': 'hue',
'example': 'hue(@color);',
'description': localize('less.builtin.hue', 'returns the `hue` channel of `@color` in the HSL space')
},
{
'name': 'saturation',
'example': 'saturation(@color);',
'description': localize('less.builtin.saturation', 'returns the `saturation` channel of `@color` in the HSL space')
},
{
'name': 'lightness',
'example': 'lightness(@color);',
'description': localize('less.builtin.lightness', 'returns the `lightness` channel of `@color` in the HSL space')
},
{
'name': 'hsvhue',
'example': 'hsvhue(@color);',
'description': localize('less.builtin.hsvhue', 'returns the `hue` channel of `@color` in the HSV space')
},
{
'name': 'hsvsaturation',
'example': 'hsvsaturation(@color);',
'description': localize('less.builtin.hsvsaturation', 'returns the `saturation` channel of `@color` in the HSV space')
},
{
'name': 'hsvvalue',
'example': 'hsvvalue(@color);',
'description': localize('less.builtin.hsvvalue', 'returns the `value` channel of `@color` in the HSV space')
},
{
'name': 'red',
'example': 'red(@color);',
'description': localize('less.builtin.red', 'returns the `red` channel of `@color`')
},
{
'name': 'green',
'example': 'green(@color);',
'description': localize('less.builtin.green', 'returns the `green` channel of `@color`')
},
{
'name': 'blue',
'example': 'blue(@color);',
'description': localize('less.builtin.blue', 'returns the `blue` channel of `@color`')
},
{
'name': 'alpha',
'example': 'alpha(@color);',
'description': localize('less.builtin.alpha', 'returns the `alpha` channel of `@color`')
},
{
'name': 'luma',
'example': 'luma(@color);',
'description': localize('less.builtin.luma', 'returns the `luma` value (perceptual brightness) of `@color`')
},
{
'name': 'saturate',
'example': 'saturate(@color, 10%);',
'description': localize('less.builtin.saturate', 'return `@color` 10% points more saturated')
},
{
'name': 'desaturate',
'example': 'desaturate(@color, 10%);',
'description': localize('less.builtin.desaturate', 'return `@color` 10% points less saturated')
},
{
'name': 'lighten',
'example': 'lighten(@color, 10%);',
'description': localize('less.builtin.lighten', 'return `@color` 10% points lighter')
},
{
'name': 'darken',
'example': 'darken(@color, 10%);',
'description': localize('less.builtin.darken', 'return `@color` 10% points darker')
},
{
'name': 'fadein',
'example': 'fadein(@color, 10%);',
'description': localize('less.builtin.fadein', 'return `@color` 10% points less transparent')
},
{
'name': 'fadeout',
'example': 'fadeout(@color, 10%);',
'description': localize('less.builtin.fadeout', 'return `@color` 10% points more transparent')
},
{
'name': 'fade',
'example': 'fade(@color, 50%);',
'description': localize('less.builtin.fade', 'return `@color` with 50% transparency')
},
{
'name': 'spin',
'example': 'spin(@color, 10);',
'description': localize('less.builtin.spin', 'return `@color` with a 10 degree larger in hue')
},
{
'name': 'mix',
'example': 'mix(@color1, @color2, [@weight: 50%]);',
'description': localize('less.builtin.mix', 'return a mix of `@color1` and `@color2`')
},
{
'name': 'greyscale',
'example': 'greyscale(@color);',
'description': localize('less.builtin.greyscale', 'returns a grey, 100% desaturated color')
},
{
'name': 'contrast',
'example': 'contrast(@color1, [@darkcolor: black], [@lightcolor: white], [@threshold: 43%]);',
'description': localize('less.builtin.contrast', 'return `@darkcolor` if `@color1 is> 43% luma` otherwise return `@lightcolor`, see notes')
},
{
'name': 'multiply',
'example': 'multiply(@color1, @color2);'
},
{
'name': 'screen',
'example': 'screen(@color1, @color2);'
},
{
'name': 'overlay',
'example': 'overlay(@color1, @color2);'
},
{
'name': 'softlight',
'example': 'softlight(@color1, @color2);'
},
{
'name': 'hardlight',
'example': 'hardlight(@color1, @color2);'
},
{
'name': 'difference',
'example': 'difference(@color1, @color2);'
},
{
'name': 'exclusion',
'example': 'exclusion(@color1, @color2);'
},
{
'name': 'average',
'example': 'average(@color1, @color2);'
},
{
'name': 'negation',
'example': 'negation(@color1, @color2);'
}
];
constructor() {
super('@');
}
private createFunctionProposals(proposals: { name: string; example: string; description?: string; }[], result: CompletionList): CompletionList {
proposals.forEach(p => {
result.items.push({
label: p.name,
detail: p.example,
documentation: p.description,
insertText: p.name + '({{}})',
kind: CompletionItemKind.Function
});
});
return result;
}
public getTermProposals(result: CompletionList): CompletionList {
this.createFunctionProposals(LESSCompletion.builtInProposals, result);
return super.getTermProposals(result);
}
protected getColorProposals(entry: languageFacts.IEntry, result: CompletionList): CompletionList {
this.createFunctionProposals(LESSCompletion.colorProposals, result);
return super.getColorProposals(entry, result);
}
}
/*---------------------------------------------------------------------------------------------
* 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 assert from 'assert';
import {LESSParser} from '../../parser/lessParser';
import {LESSCompletion} from '../../services/lessCompletion';
import * as nodes from '../../parser/cssNodes';
import {TextDocument, Position} from 'vscode-languageserver';
import {assertCompletion, ItemDescription} from '../css/completion.test';
suite('LESS - Completions', () => {
let testCompletionFor = function (value: string, stringBefore: string, expected: { count?: number, items?: ItemDescription[] }): Thenable<void> {
let idx = stringBefore ? value.indexOf(stringBefore) + stringBefore.length : 0;
let completionProvider = new LESSCompletion();
let document = TextDocument.create('test://test/test.less', 'less', 0, value);
let position = Position.create(0, idx);
let jsonDoc = new LESSParser().parseStylesheet(document);
return completionProvider.doComplete(document, position, jsonDoc).then(list => {
if (expected.count) {
assert.equal(list.items, expected.count);
}
if (expected.items) {
for (let item of expected.items) {
assertCompletion(list, item, document);
}
}
});
};
test('sylesheet', function (testDone): any {
Promise.all([
testCompletionFor('body { ', '{ ', {
items: [
{ label: 'display' },
{ label: 'background' }
]
}),
testCompletionFor('body { ver', 'ver', {
items: [
{ label: 'vertical-align' }
]
}),
testCompletionFor('body { word-break: ', ': ', {
items: [
{ label: 'keep-all' }
]
}),
testCompletionFor('body { inner { vertical-align: }', ': ', {
items: [
{ label: 'bottom' }
]
}),
testCompletionFor('@var1: 3; body { inner { vertical-align: }', 'align: ', {
items: [
{ label: '@var1' }
]
}),
testCompletionFor('.foo { background-color: d', 'background-color: d', {
items: [
{ label: 'darken' },
{ label: 'desaturate' }
]
})
]).then(() => testDone(), (error) => testDone(error));
});
});
/*---------------------------------------------------------------------------------------------
* 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 {LESSParser} from '../../parser/lessParser';
import * as nodes from '../../parser/cssNodes';
import {assertScopeBuilding, assertSymbolsInScope, assertScopesAndSymbols, assertHighlights} from '../css/navigation.test';
suite('LESS - Symbols', () => {
test('scope building', function () {
let p = new LESSParser();
assertScopeBuilding(p, '@let: blue');
assertScopeBuilding(p, '.class { .nested {} }', { offset: 7, length: 14 }, { offset: 17, length: 2 });
});
test('symbols in scopes', function () {
let p = new LESSParser();
assertSymbolsInScope(p, '@let: iable;', 0, { name: '@let', type: nodes.ReferenceType.Variable });
assertSymbolsInScope(p, '@let: iable;', 11, { name: '@let', type: nodes.ReferenceType.Variable });
assertSymbolsInScope(p, '@let: iable; .class { @color: blue; }', 11, { name: '@let', type: nodes.ReferenceType.Variable }, { name: '.class', type: nodes.ReferenceType.Rule });
assertSymbolsInScope(p, '@let: iable; .class { @color: blue; }', 21, { name: '@color', type: nodes.ReferenceType.Variable });
assertSymbolsInScope(p, '@let: iable; .class { @color: blue; }', 36, { name: '@color', type: nodes.ReferenceType.Variable });
assertSymbolsInScope(p, '@namespace "x"; .mixin() {}', 0, { name: '.mixin', type: nodes.ReferenceType.Mixin });
assertSymbolsInScope(p, '.mixin() { .nested() {} }', 10, { name: '.nested', type: nodes.ReferenceType.Mixin });
assertSymbolsInScope(p, '.mixin() { .nested() {} }', 11);
assertSymbolsInScope(p, '@keyframes animation {};', 0, { name: 'animation', type: nodes.ReferenceType.Keyframe });
});
test('scopes and symbols', function () {
let p = new LESSParser();
assertScopesAndSymbols(p, '@var1: 1; @var2: 2; .foo { @var3: 3; }', '@var1,@var2,.foo,[@var3]');
assertScopesAndSymbols(p, '.mixin1 { @var0: 1} .mixin2(@var1) { @var3: 3 }', '.mixin1,.mixin2,[@var0],[@var1,@var3]');
assertScopesAndSymbols(p, 'a b { @var0: 1; c { d { } } }', '[@var0,c,[d,[]]]');
});
test('mark highlights', function (testDone) {
let p = new LESSParser();
Promise.all([
assertHighlights(p, '@var1: 1; @var2: /**/@var1;', '/**/', 2, 1, '@var1'),
assertHighlights(p, '@var1: 1; p { @var2: /**/@var1; }', '/**/', 2, 1, '@var1'),
assertHighlights(p, 'r1 { @var1: 1; p1: @var1;} r2,r3 { @var1: 1; p1: /**/@var1 + @var1;}', '/**/', 3, 1, '@var1'),
assertHighlights(p, '.r1 { r1: 1em; } r2 { r1: 2em; /**/.r1;}', '/**/', 2, 1, '.r1'),
assertHighlights(p, '.r1(@p1) { r1: @p1; } r2 { r1: 2em; /**/.r1(2px); }', '/**/', 2, 1, '.r1'),
assertHighlights(p, '/**/.r1(@p1) { r1: @p1; } r2 { r1: 2em; .r1(2px); }', '/**/', 2, 1, '.r1'),
assertHighlights(p, '@p1 : 1; .r1(@p1) { r1: /**/@p1; }', '/**/', 2, 1, '@p1'),
assertHighlights(p, '/**/@p1 : 1; .r1(@p1) { r1: @p1; }', '/**/', 1, 1, '@p1'),
assertHighlights(p, '@p1 : 1; .r1(/**/@p1) { r1: @p1; }', '/**/', 2, 1, '@p1'),
]).then(() => testDone(), (error) => testDone(error));
});
});
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 {assertNodes} from '../css/nodes.test';
import * as nodes from '../../parser/cssNodes';
import {LESSParser} from '../../parser/lessParser';
suite('LESS - Nodes', () => {
function ruleset(input: string): nodes.RuleSet {
let parser = new LESSParser();
let node = parser.internalParse(input, parser._parseRuleset);
return node;
}
test('RuleSet', function () {
assertNodes(ruleset, 'selector { prop: value }', 'ruleset,...,selector,...,declaration,...,property,...,expression');
assertNodes(ruleset, 'selector { prop; }', 'ruleset,...,selector,...,selector');
assertNodes(ruleset, 'selector { prop {} }', 'ruleset,...,ruleset');
});
});
/*---------------------------------------------------------------------------------------------
* 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 assert from 'assert';
import {Parser} from '../../parser/cssParser';
import {TokenType} from '../../parser/cssScanner';
import * as nodes from '../../parser/cssNodes';
import {ParseError} from '../../parser/cssErrors';
import {LESSParser} from '../../parser/lessParser';
import {assertNode, assertNoNode, assertError} from '../css/parser.test';
suite('LESS - Parser', () => {
test('Variable', function() {
let parser = new LESSParser();
assertNode('@color', parser, parser._parseVariable.bind(parser));
assertNode('@co42lor', parser, parser._parseVariable.bind(parser));
assertNode('@-co42lor', parser, parser._parseVariable.bind(parser));
assertNode('@@foo', parser, parser._parseVariable.bind(parser));
assertNode('@@@foo', parser, parser._parseVariable.bind(parser));
assertNode('@12ooo', parser, parser._parseVariable.bind(parser));
assertNoNode('@ @foo', parser, parser._parseFunction.bind(parser));
assertNoNode('@-@foo', parser, parser._parseFunction.bind(parser));
});
test('Media', function() {
let parser = new LESSParser();
assertNode('@media @phone {}', parser, parser._parseMedia.bind(parser));
});
test('VariableDeclaration', function() {
let parser = new LESSParser();
assertNode('@color: #F5F5F5', parser, parser._parseVariableDeclaration.bind(parser));
assertNode('@color: 0', parser, parser._parseVariableDeclaration.bind(parser));
assertNode('@color: 255', parser, parser._parseVariableDeclaration.bind(parser));
assertNode('@color: 25.5', parser, parser._parseVariableDeclaration.bind(parser));
assertNode('@color: 25px', parser, parser._parseVariableDeclaration.bind(parser));
assertNode('@color: 25.5px', parser, parser._parseVariableDeclaration.bind(parser));
assertNode('@primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', parser, parser._parseVariableDeclaration.bind(parser));
assertNode('@greeting: `"hello".toUpperCase() + "!";`', parser, parser._parseVariableDeclaration.bind(parser));
});
test('MixinDeclaration', function() {
let parser = new LESSParser();
assertNode('.color (@color: 25.5px) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
assertNode('.color(@color: 25.5px) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
assertNode('.color(@color) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
assertNode('.color(@color; @border) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
assertNode('.color() { }', parser, parser._tryParseMixinDeclaration.bind(parser));
assertNode('.color( ) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
assertNode('.mixin (@a) when (@a > 10), (@a < -10) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
assertNode('.mixin (@a) when (isnumber(@a)) and (@a > 0) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
assertNode('.mixin (@b) when not (@b >= 0) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
assertNode('.mixin (@b) when not (@b > 0) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
assertNode('.mixin (@a, @rest...) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
assertNode('.mixin (@a) when (lightness(@a) >= 50%) { }', parser, parser._tryParseMixinDeclaration.bind(parser));
});
test('MixinReference', function() {
let parser = new LESSParser();
assertNode('.box-shadow(0 0 5px, 30%)', parser, parser._parseMixinReference.bind(parser));
assertNode('.box-shadow', parser, parser._parseMixinReference.bind(parser));
assertNode('.mixin(10) !important', parser, parser._parseMixinReference.bind(parser));
});
test('MixinParameter', function() {
let parser = new LESSParser();
assertNode('@_', parser, parser._parseMixinParameter.bind(parser));
assertNode('@let: value', parser, parser._parseMixinParameter.bind(parser));
assertNode('@let', parser, parser._parseMixinParameter.bind(parser));
assertNode('@rest...', parser, parser._parseMixinParameter.bind(parser));
assertNode('...', parser, parser._parseMixinParameter.bind(parser));
assertNode('value', parser, parser._parseMixinParameter.bind(parser));
assertNode('"string"', parser, parser._parseMixinParameter.bind(parser));
assertNode('50%', parser, parser._parseMixinParameter.bind(parser));
});
test('Parser - function', function() {
let parser = new LESSParser();
assertNode('%()', parser, parser._parseFunction.bind(parser));
assertNoNode('% ()', parser, parser._parseFunction.bind(parser));
});
test('Expr', function() {
let parser = new LESSParser();
assertNode('(@let + 20)', parser, parser._parseExpr.bind(parser));
assertNode('(@let - 20)', parser, parser._parseExpr.bind(parser));
assertNode('(@let * 20)', parser, parser._parseExpr.bind(parser));
assertNode('(@let / 20)', parser, parser._parseExpr.bind(parser));
assertNode('(20 + @let)', parser, parser._parseExpr.bind(parser));
assertNode('(20 - @let)', parser, parser._parseExpr.bind(parser));
assertNode('(20 * @let)', parser, parser._parseExpr.bind(parser));
assertNode('(20 / @let)', parser, parser._parseExpr.bind(parser));
assertNode('(20 / 20 + @let)', parser, parser._parseExpr.bind(parser));
assertNode('(20 + 20 + @let)', parser, parser._parseExpr.bind(parser));
assertNode('(20 + 20 + 20 + @let)', parser, parser._parseExpr.bind(parser));
assertNode('(20 + 20 + 20 + 20 + @let)', parser, parser._parseExpr.bind(parser));
assertNode('(20 + 20 + @let + 20 + 20 + @let)', parser, parser._parseExpr.bind(parser));
assertNode('(20 + 20)', parser, parser._parseExpr.bind(parser));
assertNode('(@var1 + @var2)', parser, parser._parseExpr.bind(parser));
assertNode('((@let + 5) * 2)', parser, parser._parseExpr.bind(parser));
assertNode('((@let + (5 + 2)) * 2)', parser, parser._parseExpr.bind(parser));
assertNode('(@let + ((5 + 2) * 2))', parser, parser._parseExpr.bind(parser));
assertNode('@color', parser, parser._parseExpr.bind(parser));
assertNode('@color, @color', parser, parser._parseExpr.bind(parser));
assertNode('@color, 42%', parser, parser._parseExpr.bind(parser));
assertNode('@color, 42%, @color', parser, parser._parseExpr.bind(parser));
assertNode('@color - (@color + 10%)', parser, parser._parseExpr.bind(parser));
assertNode('(@base + @filler)', parser, parser._parseExpr.bind(parser));
assertNode('(100% / 2 + @filler)', parser, parser._parseExpr.bind(parser));
assertNode('100% / 2 + @filler', parser, parser._parseExpr.bind(parser));
});
test('LessOperator', function() {
let parser = new LESSParser();
assertNode('>=', parser, parser._parseOperator.bind(parser));
assertNode('>', parser, parser._parseOperator.bind(parser));
assertNode('<', parser, parser._parseOperator.bind(parser));
assertNode('=<', parser, parser._parseOperator.bind(parser));
});
test('Extend', function() {
let parser = new LESSParser();
assertNode('nav { &:extend(.inline); }', parser, parser._parseRuleset.bind(parser));
});
test('Declaration', function() {
let parser = new LESSParser();
assertNode('border: thin solid 1px', parser, parser._parseDeclaration.bind(parser));
assertNode('dummy: @color', parser, parser._parseDeclaration.bind(parser));
assertNode('dummy: blue', parser, parser._parseDeclaration.bind(parser));
assertNode('dummy: (20 / @let)', parser, parser._parseDeclaration.bind(parser));
assertNode('dummy: (20 / 20 + @let)', parser, parser._parseDeclaration.bind(parser));
assertNode('dummy: func(@red)', parser, parser._parseDeclaration.bind(parser));
assertNode('dummy: desaturate(@red, 10%)', parser, parser._parseDeclaration.bind(parser));
assertNode('dummy: desaturate(16, 10%)', parser, parser._parseDeclaration.bind(parser));
assertNode('color: @base-color + #111', parser, parser._parseDeclaration.bind(parser));
assertNode('color: 100% / 2 + @ref', parser, parser._parseDeclaration.bind(parser));
assertNode('border: (@width * 2) solid black', parser, parser._parseDeclaration.bind(parser));
assertNode('property: @class', parser, parser._parseDeclaration.bind(parser));
assertNode('prop-erty: fnc(@t, 10%)', parser, parser._parseDeclaration.bind(parser));
});
test('Stylesheet', function() {
let parser = new LESSParser();
assertNode('.color (@radius: 5px){ -border-radius: #F5F5F5 }', parser, parser._parseStylesheet.bind(parser));
assertNode('.color (@radius: 5px){ -border-radius: @radius }', parser, parser._parseStylesheet.bind(parser));
assertNode('.color (@radius: 5px){ -border-radius: #F5F5F5 } .color (@radius: 5px) { -border-radius: #F5F5F5 }', parser, parser._parseStylesheet.bind(parser));
assertNode('.color (@radius: 5px) { -border-radius: #F5F5F5 } .color (@radius: 5px) { -border-radius: #F5F5F5 } .color (@radius: 5px) { -border-radius: #F5F5F5 }', parser, parser._parseStylesheet.bind(parser));
assertNode('.color (@radius: 5px) { -border-radius: #F5F5F5 } .color (@radius: 5px) { -border-radius: #F5F5F5 } .color (@radius: 5px) { -border-radius: #F5F5F5 }', parser, parser._parseStylesheet.bind(parser));
assertNode('.mixin (@a, @rest...) {}', parser, parser._parseStylesheet.bind(parser));
assertNode('.mixin (@a) when (lightness(@a) >= 50%) { background-color: black;}', parser, parser._parseStylesheet.bind(parser));
assertNode('.some-mixin { font-weight:bold; } h1 { .some-mixin; font-size:40px; }', parser, parser._parseStylesheet.bind(parser));
assertNode('@color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser));
assertNode('@color: #F5F5F5; @color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser));
assertNode('@color: #F5F5F5; @color: #F5F5F5; @color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser));
assertNode('@color: #F5F5F5; .color (@radius: 5px) { -border-radius: #F5F5F5 } @color: #F5F5F5;', parser, parser._parseStylesheet.bind(parser));
assertNode('@import-once "lib";', parser, parser._parseStylesheet.bind(parser));
assertNode('@import-once (css) "hello";', parser, parser._parseStylesheet.bind(parser));
assertError('@import-once () "hello";', parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected);
assertError('@import-once (less);', parser, parser._parseStylesheet.bind(parser), ParseError.URIOrStringExpected);
});
test('Ruleset', function() {
let parser = new LESSParser();
assertNode('.selector { prop: erty @let 1px; }', parser, parser._parseRuleset.bind(parser));
assertNode('selector { .mixin; }', parser, parser._parseRuleset.bind(parser));
assertNode('selector { .mixin(1px); .mixin(blue, 1px, \'farboo\') }', parser, parser._parseRuleset.bind(parser));
assertNode('selector { .mixin(blue; 1px;\'farboo\') }', parser, parser._parseRuleset.bind(parser));
assertNode('selector:active { property:value; nested:hover {}}', parser, parser._parseRuleset.bind(parser));
assertNode('selector {}', parser, parser._parseRuleset.bind(parser));
assertNode('selector { property: declaration }', parser, parser._parseRuleset.bind(parser));
assertNode('selector { @variable: declaration }', parser, parser._parseRuleset.bind(parser));
assertNode('selector { nested {}}', parser, parser._parseRuleset.bind(parser));
assertNode('selector { nested, a, b {}}', parser, parser._parseRuleset.bind(parser));
assertNode('selector { property: value; property: value; }', parser, parser._parseRuleset.bind(parser));
assertNode('selector { property: value; @keyframes foo {} @-moz-keyframes foo {}}', parser, parser._parseRuleset.bind(parser));
});
test('term', function() {
let parser = new LESSParser();
assertNode('%(\'repetitions: %S file: %S\', 1 + 2, "directory/file.less")', parser, parser._parseTerm.bind(parser));
assertNode('~"ms:alwaysHasItsOwnSyntax.For.Stuff()"', parser, parser._parseTerm.bind(parser)); // less syntax
});
test('Nested Ruleset', function() {
let parser = new LESSParser();
assertNode('.class1 { @let: 1; .class { @let: 2; three: @let; let: 3; } one: @let; }', parser, parser._parseRuleset.bind(parser));
assertNode('.class1 { @let: 1; > .class2 { display: none; } }', parser, parser._parseRuleset.bind(parser));
});
test('Selector Interpolation', function() {
let parser = new LESSParser();
assertNode('.@{name} { }', parser, parser._parseRuleset.bind(parser));
assertNode('~"@{name}" { }', parser, parser._parseRuleset.bind(parser));
assertError('~{ }', parser, parser._parseStylesheet.bind(parser), ParseError.StringLiteralExpected);
assertError('@', parser, parser._parseSelectorInterpolation.bind(parser), ParseError.LeftCurlyExpected);
assertError('@{', parser, parser._parseSelectorInterpolation.bind(parser), ParseError.IdentifierExpected);
assertError('@{dd', parser, parser._parseSelectorInterpolation.bind(parser), ParseError.RightCurlyExpected);
});
test('Selector Combinator', function() {
let parser = new LESSParser();
assertNode('&:hover', parser, parser._parseSimpleSelector.bind(parser));
assertNode('&.float', parser, parser._parseSimpleSelector.bind(parser));
assertNode('&-foo', parser, parser._parseSimpleSelector.bind(parser));
assertNode('&--&', parser, parser._parseSimpleSelector.bind(parser));
});
});
\ No newline at end of file
/*---------------------------------------------------------------------------------------------
* 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 assert from 'assert';
import {Scanner, TokenType} from '../../parser/cssScanner';
import {LESSScanner} from '../../parser/lessScanner';
function assertSingleToken(source: string, len: number, offset: number, text: string, type: TokenType): void {
let scan = new LESSScanner();
scan.setSource(source);
let token = scan.scan();
assert.equal(token.len, len);
assert.equal(token.offset, offset);
assert.equal(token.text, text);
assert.equal(token.type, type);
}
suite('LESS - Scanner', () => {
test('Test Escaped JavaScript', function () {
assertSingleToken('`', 1, 0, '`', TokenType.BadEscapedJavaScript);
assertSingleToken('`a', 2, 0, '`a', TokenType.BadEscapedJavaScript);
assertSingleToken('`let a = "ssss"`', 16, 0, '`let a = "ssss"`', TokenType.EscapedJavaScript);
assertSingleToken('`let a = "ss\ns"`', 16, 0, '`let a = "ss\ns"`', TokenType.EscapedJavaScript);
});
// less deactivated comments
test('Test Token SingleLineComment', function () {
assertSingleToken('//', 0, 2, '', TokenType.EOF);
assertSingleToken('//this is a comment test', 0, 24, '', TokenType.EOF);
assertSingleToken('// this is a comment test', 0, 25, '', TokenType.EOF);
assertSingleToken('// this is a\na', 1, 13, 'a', TokenType.Ident);
assertSingleToken('// this is a\n// more\n \n/* comment */a', 1, 38, 'a', TokenType.Ident);
});
});
--ui tdd
--useColors true
./out/test/css
./out/test/less
./out/test/scss
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册