[css] use vscode-css-languageservice

This commit is contained in:
Martin Aeschlimann
2016-06-23 17:41:36 +02:00
parent 90f9c9389e
commit 79b65f326a
55 changed files with 7 additions and 32544 deletions

View File

@@ -1,64 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 {CSSCompletion} from '../../services/cssCompletion';
import {CSSCodeActions} from '../../services/cssCodeActions';
import {CSSValidation} from '../../services/cssValidation';
import {CompletionList, TextDocument, TextEdit, Position, Range, Command} from 'vscode-languageserver';
import {applyEdits} from '../textEditSupport';
suite('CSS - Code Actions', () => {
let testCodeActions = function (value: string, tokenBefore: string): Thenable<{ commands: Command[]; document: TextDocument; }> {
let document = TextDocument.create('test://test/test.css', 'css', 0, value);
let styleSheet = new Parser().parseStylesheet(document);
let offset = value.indexOf(tokenBefore);
let startPosition = document.positionAt(offset);
let endPosition = document.positionAt(offset + tokenBefore.length);
let range = Range.create(startPosition, endPosition);
let validation = new CSSValidation();
validation.configure({ validate: true });
return validation.doValidation(document, styleSheet).then(diagnostics => {
return new CSSCodeActions().doCodeActions(document, range, { diagnostics }, styleSheet).then(commands => {
return { commands, document };
});
});
};
let assertCodeAction = function (commands: Command[], document: TextDocument, expected: { title: string; content: string; }[]) {
let labels = commands.map(command => command.title);
for (let exp of expected) {
let index = labels.indexOf(exp.title);
assert.ok(index !== -1, 'Quick fix not found: ' + exp.title + ' , found:' + labels.join(','));
let command = commands[index];
assert.equal(applyEdits(document, <TextEdit[]>command.arguments[2]), exp.content);
assert.equal(command.arguments[0], document.uri);
assert.equal(command.arguments[1], document.version);
}
};
test('Unknown Properties', function (testDone): any {
Promise.all([
testCodeActions('body { /*here*/displai: inline }', '/*here*/').then((result) => {
assertCodeAction(result.commands, result.document, [
{ title: 'Rename to \'display\'', content: 'body { /*here*/display: inline }' }
])
}),
testCodeActions('body { /*here*/background-colar: red }', '/*here*/').then((result) => {
assertCodeAction(result.commands, result.document, [
{ title: 'Rename to \'background-color\'', content: 'body { /*here*/background-color: red }' },
{ title: 'Rename to \'background-clip\'', content: 'body { /*here*/background-clip: red }' },
{ title: 'Rename to \'background-image\'', content: 'body { /*here*/background-image: red }' }
])
})
]).then(() => testDone(), (error) => testDone(error));
});
})

View File

@@ -1,230 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 {CSSCompletion} from '../../services/cssCompletion';
import {CompletionList, TextDocument, TextEdit, Position, CompletionItemKind} from 'vscode-languageserver';
import {applyEdits} from '../textEditSupport';
export interface ItemDescription {
label: string;
documentation?: string;
kind?: CompletionItemKind;
resultText?: string;
}
export let assertCompletion = function (completions: CompletionList, expected: ItemDescription, document?: TextDocument) {
let matches = completions.items.filter(completion => {
return completion.label === expected.label;
});
assert.equal(matches.length, 1, expected.label + " should only existing once: Actual: " + completions.items.map(c => c.label).join(', '));
if (expected.documentation) {
assert.equal(matches[0].documentation, expected.documentation);
}
if (expected.kind) {
assert.equal(matches[0].kind, expected.kind);
}
if (document && expected.resultText) {
assert.equal(applyEdits(document, [matches[0].textEdit]), expected.resultText);
}
};
suite('CSS - Completion', () => {
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 CSSCompletion();
let document = TextDocument.create('test://test/test.css', 'css', 0, value);
let position = Position.create(0, idx);
let jsonDoc = new Parser().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(' ', null, {
items: [
{ label: '@import' },
{ label: '@keyframes' },
{ label: 'div' }
]
}),
testCompletionFor(' body {', null, {
items: [
{ label: '@import' },
{ label: '@keyframes' },
{ label: 'html' }
]
}),
testCompletionFor('@import url("something.css");', '@', {
count: 0
})
]).then(() => testDone(), (error) => testDone(error));
});
test('properties', function (testDone): any {
Promise.all([
testCompletionFor('body {', '{', {
items: [
{ label: 'display' },
{ label: 'background' }
]
}),
testCompletionFor('body { ver', 'ver', {
items: [
{ label: 'vertical-align' }
]
}),
testCompletionFor('body { vertical-align', 'vertical-ali', {
items: [
{ label: 'vertical-align' }
]
}),
testCompletionFor('body { vertical-align', 'vertical-align', {
items: [
{ label: 'vertical-align' }
]
}),
testCompletionFor('body { vertical-align: bottom;}', 'vertical-align', {
items: [
{ label: 'vertical-align' }
]
}),
testCompletionFor('body { trans ', 'trans', {
items: [
{ label: 'transition' }
]
})
]).then(() => testDone(), (error) => testDone(error));
});
test('values', function (testDone): any {
Promise.all([
testCompletionFor('body { vertical-align: bottom;}', 'vertical-align:', {
items: [
{ label: 'bottom' },
{ label: '0cm' }
]
}),
testCompletionFor('body { vertical-align: bottom;}', 'vertical-align: ', {
items: [
{ label: 'bottom' },
{ label: '0cm' }
]
}),
testCompletionFor('body { vertical-align: bott', 'bott', {
items: [
{ label: 'bottom' }
]
}),
testCompletionFor('body { vertical-align: bottom }', 'bott', {
items: [
{ label: 'bottom' }
]
}),
testCompletionFor('body { vertical-align: bottom }', 'bottom', {
items: [
{ label: 'bottom' }
]
}),
testCompletionFor('body { vertical-align: bottom; }', 'bottom', {
items: [
{ label: 'bottom' }
]
}),
testCompletionFor('body { vertical-align: bottom; }', 'bottom;', {
count: 0
}),
testCompletionFor('body { vertical-align: bottom; }', 'bottom; ', {
items: [
{ label: 'display' }
]
})
]).then(() => testDone(), (error) => testDone(error));
});
test('units', function (testDone): any {
Promise.all([
testCompletionFor('body { vertical-align: 9 }', '9', {
items: [
{ label: '9cm' }
]
}),
testCompletionFor('body { vertical-align: 1.2 }', '1.2', {
items: [
{ label: '1.2em' }
]
}),
testCompletionFor('body { vertical-align: 10 }', '1', {
items: [
{ label: '1cm' }
]
}),
testCompletionFor('body { vertical-align: 10c }', '10c', {
items: [
{ label: '10cm' }
]
})
]).then(() => testDone(), (error) => testDone(error));
});
test('unknown', function (testDone): any {
Promise.all([
testCompletionFor('body { notexisting: ;}', 'notexisting: ', {
count: 0
}),
testCompletionFor('.foo { unknown: foo; } .bar { unknown: }', '.bar { unknown:', {
items: [
{ label: 'foo', kind: CompletionItemKind.Value }
]
})
]).then(() => testDone(), (error) => testDone(error));
});
test('colors', function (testDone): any {
Promise.all([
testCompletionFor('body { border-right: ', 'right: ', {
items: [
{ label: 'cyan' },
{ label: 'dotted' },
{ label: '0em' }
]
}),
testCompletionFor('body { border-right: cyan dotted 2em ', 'cyan', {
items: [
{ label: 'cyan' },
{ label: 'darkcyan' }
]
}),
testCompletionFor('body { border-right: dotted 2em ', '2em ', {
items: [
{ label: 'cyan' }
]
}),
testCompletionFor('.foo { background-color: #123456; } .bar { background-color: }', '.bar { background-color:', {
items: [
{ label: '#123456', kind: CompletionItemKind.Color }
]
}),
testCompletionFor('.foo { background-color: r', 'background-color: r', {
items: [
{ label: 'rgb', kind: CompletionItemKind.Function },
{ label: 'rgba', kind: CompletionItemKind.Function },
{ label: 'red', kind: CompletionItemKind.Color }
]
})
]).then(() => testDone(), (error) => testDone(error));
});
});

View File

@@ -1,60 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 * as languageFacts from '../../services/languageFacts';
import {Parser} from '../../parser/cssParser';
import * as nodes from '../../parser/cssNodes';
import {TextDocument} from 'vscode-languageserver';
export function assertColor(parser: Parser, text: string, selection: string, isColor: boolean): void {
let document = TextDocument.create('test://test/test.css', 'css', 0, text);
let stylesheet = parser.parseStylesheet(document);
assert.equal(0, nodes.ParseErrorCollector.entries(stylesheet).length, 'compile errors');
let node = nodes.getNodeAtOffset(stylesheet, text.indexOf(selection));
assert.equal(isColor, languageFacts.isColorValue(node));
}
suite('CSS - Language Facts', () => {
test('properties', function () {
let properties = languageFacts.getProperties();
let alignLast = properties['text-align-last'];
assert.ok(alignLast !== null);
assert.equal(alignLast.name, 'text-align-last');
let b = alignLast.browsers;
assert.equal(b['FF'], '12');
assert.equal(b['IE'], '5');
assert.equal(b['E'], '');
assert.equal(b['C'], void 0);
assert.equal(b['count'], 3);
assert.equal(languageFacts.getBrowserLabel(alignLast.browsers), 'Edge, Firefox 12, IE 5');
let r = alignLast.restrictions;
assert.equal(r.length, 1);
assert.equal(r[0], 'enum');
let v = alignLast.values;
assert.equal(v.length, 5);
assert.equal(v[0].name, 'auto');
assert.equal(v[0].browsers.all, true);
assert.equal(v[0].browsers.count, Number.MAX_VALUE);
});
test('is color', function () {
let parser = new Parser();
assertColor(parser, '#main { color: red }', 'red', true);
assertColor(parser, '#main { color: #231 }', '#231', true);
assertColor(parser, '#main { red: 1 }', 'red', false);
assertColor(parser, '#red { foo: 1 }', 'red', false);
});
});

View File

@@ -1,129 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 * as nodes from '../../parser/cssNodes';
import {Parser} from '../../parser/cssParser';
import {LintVisitor} from '../../services/lint';
import {Rule, Rules} from '../../services/lintRules';
import {TextDocument} from 'vscode-languageserver';
export function assertEntries(node: nodes.Node, rules: nodes.IRule[]): void {
let visitor = new LintVisitor();
node.accept(visitor);
let entries = visitor.getEntries(nodes.Level.Error | nodes.Level.Warning | nodes.Level.Ignore);
assert.equal(entries.length, rules.length);
for (let entry of entries) {
let idx = rules.indexOf(entry.getRule());
rules.splice(idx, 1);
}
assert.equal(rules.length, 0);
}
function assertStyleSheet(input: string, ...rules: Rule[]): void {
let p = new Parser();
let document = TextDocument.create('test://test/test.css', 'css', 0, input);
let node = p.parseStylesheet(document);
assertEntries(node, rules);
}
function assertRuleSet(input: string, ...rules: Rule[]): void {
let p = new Parser();
let node = p.internalParse(input, p._parseRuleset);
assertEntries(node, rules);
}
function assertFontFace(input: string, ...rules: Rule[]): void {
let p = new Parser();
let node = p.internalParse(input, p._parseFontFace);
assertEntries(node, rules);
}
suite('CSS - Lint', () => {
test('universal selector, empty rule', function () {
assertRuleSet('* { color: perty }', Rules.UniversalSelector);
assertRuleSet('*, div { color: perty }', Rules.UniversalSelector);
assertRuleSet('div, * { color: perty }', Rules.UniversalSelector);
assertRuleSet('div > * { color: perty }', Rules.UniversalSelector);
assertRuleSet('div + * { color: perty }', Rules.UniversalSelector);
});
test('empty ruleset', function () {
assertRuleSet('selector {}', Rules.EmptyRuleSet);
});
test('properies ignored due to inline ', function () {
assertRuleSet('selector { display: inline; height: 100px; }', Rules.PropertyIgnoredDueToDisplay);
assertRuleSet('selector { display: inline; width: 100px; }', Rules.PropertyIgnoredDueToDisplay);
assertRuleSet('selector { display: inline; margin-top: 1em; }', Rules.PropertyIgnoredDueToDisplay);
assertRuleSet('selector { display: inline; margin-bottom: 1em; }', Rules.PropertyIgnoredDueToDisplay);
assertRuleSet('selector { display: inline; float: right; }', Rules.PropertyIgnoredDueToDisplay, Rules.AvoidFloat);
assertRuleSet('selector { display: inline-block; float: right; }', Rules.PropertyIgnoredDueToDisplay, Rules.AvoidFloat);
assertRuleSet('selector { display: block; vertical-align: center; }', Rules.PropertyIgnoredDueToDisplay);
});
test('avoid !important', function () {
assertRuleSet('selector { display: inline !important; }', Rules.AvoidImportant);
});
test('avoid float', function () {
assertRuleSet('selector { float: right; }', Rules.AvoidFloat);
});
test('avoid id selectors', function () {
assertRuleSet('#selector { display: inline; }', Rules.AvoidIdSelector);
});
test('zero with unit', function () {
// assertRuleSet('selector { width: 0px }', lint.Rules.ZeroWithUnit);
assertRuleSet('selector { width: 0% }');
});
test('duplicate declarations', function () {
assertRuleSet('selector { color: perty; color: perty }', Rules.DuplicateDeclarations, Rules.DuplicateDeclarations);
assertRuleSet('selector { color: -o-perty; color: perty }');
});
test('unknown properties', function () {
assertRuleSet('selector { -ms-property: "rest is missing" }', Rules.UnknownVendorSpecificProperty);
assertRuleSet('selector { -moz-box-shadow: "rest is missing" }', Rules.UnknownVendorSpecificProperty, Rules.IncludeStandardPropertyWhenUsingVendorPrefix);
assertRuleSet('selector { box-shadow: none }'); // no error
assertRuleSet('selector { box-property: "rest is missing" }', Rules.UnknownProperty);
});
test('IE hacks', function () {
assertRuleSet('selector { display: inline-block; *display: inline; }', Rules.IEStarHack);
assertRuleSet('selector { background: #00f; /* all browsers including Mac IE */ *background: #f00; /* IE 7 and below */ _background: #f60; /* IE 6 and below */ }', Rules.IEStarHack, Rules.IEStarHack);
});
test('vendor specific prefixes', function () {
assertRuleSet('selector { -moz-animation: none }', Rules.AllVendorPrefixes, Rules.IncludeStandardPropertyWhenUsingVendorPrefix);
assertRuleSet('selector { -moz-transform: none; transform: none }', Rules.AllVendorPrefixes);
assertRuleSet('selector { transform: none; }');
assertRuleSet('selector { -moz-transform: none; transform: none; -o-transform: none; -webkit-transform: none; -ms-transform: none; }');
assertRuleSet('selector { --transform: none; }');
});
test('font-face required properties', function () {
assertFontFace('@font-face { }', Rules.RequiredPropertiesForFontFace);
assertFontFace('@font-face { src: url(test.tff) }', Rules.RequiredPropertiesForFontFace);
assertFontFace('@font-face { font-family: \'name\' }', Rules.RequiredPropertiesForFontFace);
assertFontFace('@font-face { src: url(test.tff); font-family: \'name\' }'); // no error
});
test('keyframes', function () {
assertStyleSheet('@keyframes foo { }');
assertStyleSheet('@keyframes foo { } @-moz-keyframes foo { }', Rules.AllVendorPrefixes);
assertStyleSheet('@-moz-keyframes foo { }', Rules.AllVendorPrefixes, Rules.IncludeStandardPropertyWhenUsingVendorPrefix);
});
});

View File

@@ -1,242 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 {Scope, GlobalScope, ScopeBuilder} from '../../parser/cssSymbolScope';
import * as nodes from '../../parser/cssNodes';
import {Parser} from '../../parser/cssParser';
import {CSSNavigation} from '../../services/cssNavigation';
import {TextDocument, DocumentHighlightKind} from 'vscode-languageserver';
export function assertScopesAndSymbols(p: Parser, input: string, expected: string): void {
let global = createScope(p, input);
assert.equal(scopeToString(global), expected);
}
export function assertHighlights(p: Parser, input: string, marker: string, expectedMatches: number, expectedWrites: number, elementName?: string): Thenable<void> {
let document = TextDocument.create('test://test/test.css', 'css', 0, input);
let stylesheet = p.parseStylesheet(document);
assertNoErrors(stylesheet);
let index = input.indexOf(marker) + marker.length;
let position = document.positionAt(index);
return new CSSNavigation().findDocumentHighlights(document, position, stylesheet).then(highlights => {
assert.equal(highlights.length, expectedMatches, input);
let nWrites = 0;
for (let highlight of highlights) {
if (highlight.kind === DocumentHighlightKind.Write) {
nWrites++;
}
let range = highlight.range;
let start = document.offsetAt(range.start), end = document.offsetAt(range.end);
assert.equal(document.getText().substring(start, end), elementName || marker);
}
assert.equal(nWrites, expectedWrites);
});
}
export function assertSymbolsInScope(p: Parser, input: string, offset: number, ...selections: { name: string; type: nodes.ReferenceType }[]): void {
let global = createScope(p, input);
let scope = global.findScope(offset);
let getErrorMessage = function (name: string) {
let all = 'symbol ' + name + ' not found. In scope: ';
scope.getSymbols().forEach((sym) => { all += (sym.name + ' '); });
return all;
};
for (let i = 0; i < selections.length; i++) {
let selection = selections[i];
let sym = scope.getSymbol(selection.name, selection.type) || global.getSymbol(selection.name, selection.type);
assert.ok(!!sym, getErrorMessage(selection.name));
}
}
export function assertScopeBuilding(p: Parser, input: string, ...scopes: { offset: number; length: number; }[]): void {
let global = createScope(p, input);
function assertChildren(scope: Scope): void {
scope.children.forEach((scope) => {
// check bounds
let expected = scopes.shift();
assert.equal(scope.offset, expected.offset);
assert.equal(scope.length, expected.length);
// recursive descent
assertChildren(scope);
});
}
assertChildren(global);
assert.equal(scopes.length, 0, 'remainig scopes: ' + scopes.join());
}
function scopeToString(scope: Scope): string {
let str = '';
let symbols = scope.getSymbols();
for (let index = 0; index < symbols.length; index++) {
if (str.length > 0) {
str += ',';
}
str += symbols[index].name;
}
let scopes = scope.children;
for (let index = 0; index < scopes.length; index++) {
if (str.length > 0) {
str += ',';
}
str += ('[' + scopeToString(scopes[index]) + ']');
}
return str;
}
function assertNoErrors(node: nodes.Node): void {
let markers = nodes.ParseErrorCollector.entries(node);
if (markers.length > 0) {
assert.ok(false, 'node has errors: ' + markers[0].getMessage() + ', offset: ' + markers[0].getNode().offset);
}
}
function createScope(p: Parser, input: string): Scope {
let document = TextDocument.create('test://test/test.css', 'css', 0, input);
let styleSheet = p.parseStylesheet(document),
global = new GlobalScope(),
builder = new ScopeBuilder(global);
assertNoErrors(styleSheet);
styleSheet.accept(builder);
return global;
}
suite('CSS - Symbols', () => {
test('scope creation', function () {
let global = new GlobalScope(),
child1 = new Scope(10, 5),
child2 = new Scope(15, 5);
global.addChild(child1);
global.addChild(child2);
assert.equal(global.children.length, 2);
assert.ok(child1.parent === global);
assert.ok(child2.parent === global);
// find children
assert.ok(global.findScope(-1) === null);
assert.ok(global.findScope(0) === global);
assert.ok(global.findScope(10) === child1);
assert.ok(global.findScope(14) === child1);
assert.ok(global.findScope(15) === child2);
assert.ok(global.findScope(19) === child2);
assert.ok(global.findScope(19).parent === global);
});
test('scope building', function () {
let p = new Parser();
assertScopeBuilding(p, '.class {}', { offset: 7, length: 2 });
assertScopeBuilding(p, '.class {} .class {}', { offset: 7, length: 2 }, { offset: 17, length: 2 });
});
test('symbols in scopes', function () {
let p = new Parser();
assertSymbolsInScope(p, '@keyframes animation {};', 0, { name: 'animation', type: nodes.ReferenceType.Keyframe });
assertSymbolsInScope(p, ' .class1 {} .class2 {}', 0, { name: '.class1', type: nodes.ReferenceType.Rule }, { name: '.class2', type: nodes.ReferenceType.Rule });
});
test('scopes and symbols', function () {
let p = new Parser();
assertScopesAndSymbols(p, '.class {}', '.class,[]');
assertScopesAndSymbols(p, '@keyframes animation {}; .class {}', 'animation,.class,[],[]');
assertScopesAndSymbols(p, '@page :pseudo-class { margin:2in; }', '[]');
assertScopesAndSymbols(p, '@media print { body { font-size: 10pt } }', '[body,[]]');
assertScopesAndSymbols(p, '@-moz-keyframes identifier { 0% { top: 0; } 50% { top: 30px; left: 20px; }}', 'identifier,[[],[]]');
assertScopesAndSymbols(p, '@font-face { font-family: "Bitstream Vera Serif Bold"; }', '[]');
});
test('mark highlights', function (testDone) {
let p = new Parser();
Promise.all([
assertHighlights(p, '@keyframes id {}; #main { animation: id 4s linear 0s infinite alternate; }', 'id', 2, 1),
assertHighlights(p, '@keyframes id {}; #main { animation-name: id; foo: id;}', 'id', 2, 1)
]).then(() => testDone(), (error) => testDone(error));
});
test('test variables in root scope', function () {
let p = new Parser();
assertSymbolsInScope(p, ':root{ --var1: abc; --var2: def; }', 0, { name: '--var1', type: nodes.ReferenceType.Variable }, { name: '--var2', type: nodes.ReferenceType.Variable });
});
test('test variables in local scope', function () {
let p = new Parser();
assertSymbolsInScope(p, '.a{ --var1: abc; --var2: def; }', 2, { name: '--var1', type: nodes.ReferenceType.Variable }, { name: '--var2', type: nodes.ReferenceType.Variable });
});
test('test variables in local scope get root variables too', function () {
let p = new Parser();
assertSymbolsInScope(p, '.a{ --var1: abc; } :root{ --var2: abc;}', 2, { name: '--var1', type: nodes.ReferenceType.Variable }, { name: '--var2', type: nodes.ReferenceType.Variable });
});
test('test variables in local scope get root variables and other local variables too', function () {
let p = new Parser();
assertSymbolsInScope(p, '.a{ --var1: abc; } .b{ --var2: abc; } :root{ --var3: abc;}', 2, { name: '--var1', type: nodes.ReferenceType.Variable }, { name: '--var2', type: nodes.ReferenceType.Variable }, { name: '--var3', type: nodes.ReferenceType.Variable });
});
test('mark occurrences for variable defined in root and used in a rule', function (testDone) {
let p = new Parser();
Promise.all([
assertHighlights(p, '.a{ background: let(--var1); } :root{ --var1: abc;}', '--var1', 2, 1)
]).then(() => testDone(), (error) => testDone(error));
});
test('mark occurrences for variable defined in a rule and used in a different rule', function (testDone) {
let p = new Parser();
Promise.all([
assertHighlights(p, '.a{ background: let(--var1); } :b{ --var1: abc;}', '--var1', 2, 1)
]).then(() => testDone(), (error) => testDone(error));
});
test('mark occurrences for property', function (testDone) {
let p = new Parser();
Promise.all([
assertHighlights(p, 'body { display: inline } #foo { display: inline }', 'display', 2, 0)
]).then(() => testDone(), (error) => testDone(error));
});
test('mark occurrences for value', function (testDone) {
let p = new Parser();
Promise.all([
assertHighlights(p, 'body { display: inline } #foo { display: inline }', 'inline', 2, 0)
]).then(() => testDone(), (error) => testDone(error));
});
test('mark occurrences for selector', function (testDone) {
let p = new Parser();
Promise.all([
assertHighlights(p, 'body { display: inline } #foo { display: inline }', 'body', 1, 1)
]).then(() => testDone(), (error) => testDone(error));
});
test('mark occurrences for comment', function (testDone) {
let p = new Parser();
Promise.all([
assertHighlights(p, '/* comment */body { display: inline } ', 'comment', 0, 0)
]).then(() => testDone(), (error) => testDone(error));
});
});

View File

@@ -1,118 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 * as nodes from '../../parser/cssNodes';
import {Parser} from '../../parser/cssParser';
export class PrintingVisitor implements nodes.IVisitor {
public tree: string[] = [];
public visitNode(node: nodes.Node): boolean {
this.tree.push(nodes.NodeType[node.type].toLowerCase());
return true;
}
}
export function assertNodes(fn: (input: string) => nodes.Node, input: string, expected: string): void {
let node = fn(input);
let visitor = new PrintingVisitor();
node.accept(visitor);
let actual = visitor.tree.join(',') + ',';
let segments = expected.split(',');
let oldIndex: number = undefined;
let index = -1;
while (segments.length > 0) {
let segment = segments.shift();
if (segment === '...') {
continue;
}
index = actual.indexOf(segment + ',', oldIndex);
if (index <= oldIndex) {
assert.ok(false, segment + ' NOT found in ' + actual);
}
oldIndex = index + segment.length;
}
assert.ok(true);
}
suite('CSS - Nodes', () => {
test('Test Node', function () {
let node = new nodes.Node();
assert.equal(node.offset, -1);
assert.equal(node.length, -1);
assert.equal(node.parent, null);
assert.equal(node.getChildren().length, 0);
let c = 0;
node.accept((n: nodes.Node) => {
assert.ok(n === node);
c += 1;
return true;
});
assert.equal(c, 1);
let child = new nodes.Node();
node.adoptChild(child);
c = 0;
let expects = [node, child];
node.accept((n: nodes.Node) => {
assert.ok(n === expects[c]);
c += 1;
return true;
});
assert.equal(c, 2);
});
test('Test Adopting', function () {
let child = new nodes.Node();
let p1 = new nodes.Node();
let p2 = new nodes.Node();
assert.ok(child.parent === null);
assert.equal(p1.getChildren().length, 0);
assert.equal(p2.getChildren().length, 0);
p1.adoptChild(child);
assert.ok(child.parent === p1);
assert.equal(p1.getChildren().length, 1);
assert.equal(p2.getChildren().length, 0);
p2.adoptChild(child);
assert.ok(child.parent === p2);
assert.equal(p1.getChildren().length, 0);
assert.equal(p2.getChildren().length, 1);
});
function ruleset(input: string): nodes.RuleSet {
let parser = new Parser();
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');
});
test('Keyframe', function () {
function fn(input: string): nodes.Node {
let parser = new Parser();
let node = parser.internalParse(input, parser._parseKeyframe);
return node;
};
assertNodes(fn, '@keyframes name { from { top: 0px} to { top: 100px } }', 'keyframe,identifier,keyframeselector,declaration,keyframeselector,declaration');
});
});

View File

@@ -1,404 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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';
export function assertNode(text: string, parser: Parser, f: () => nodes.Node): nodes.Node {
let node = parser.internalParse(text, f);
assert.ok(node !== null, 'no node returned');
let markers = nodes.ParseErrorCollector.entries(node);
if (markers.length > 0) {
assert.ok(false, 'node has errors: ' + markers[0].getMessage() + ', offset: ' + markers[0].getNode().offset);
}
assert.ok(parser.accept(TokenType.EOF), 'Expect scanner at EOF');
return node;
}
export function assertFunction(text: string, parser: Parser, f: () => nodes.Node): void {
assertNode(text, parser, f);
}
export function assertNoNode(text: string, parser: Parser, f: () => nodes.Node): void {
let node = parser.internalParse(text, f);
assert.ok(node === null);
}
export function assertError(text: string, parser: Parser, f: () => nodes.Node, error: nodes.IRule): void {
let node = parser.internalParse(text, f);
assert.ok(node !== null, 'no node returned');
let markers = nodes.ParseErrorCollector.entries(node);
if (markers.length === 0) {
assert.ok(false, 'no errors but error expected: ' + error.message);
} else {
markers = markers.sort((a, b) => { return a.getOffset() - b.getOffset(); });
assert.equal(markers[0].getRule().id, error.id);
}
}
suite('CSS - Parser', () => {
test('Test stylesheet', function () {
let parser = new Parser();
assertNode('@charset "demo" ;', parser, parser._parseStylesheet.bind(parser));
assertNode('body { margin: 0px; padding: 3em, 6em; }', parser, parser._parseStylesheet.bind(parser));
assertNode('--> <!--', parser, parser._parseStylesheet.bind(parser));
assertNode('', parser, parser._parseStylesheet.bind(parser));
assertNode('<!-- --> @import "string"; <!-- -->', parser, parser._parseStylesheet.bind(parser));
assertNode('@media asdsa { } <!-- --> <!-- -->', parser, parser._parseStylesheet.bind(parser));
assertNode('@media screen, projection { }', parser, parser._parseStylesheet.bind(parser));
assertNode('@media screen and (max-width: 400px) { @-ms-viewport { width: 320px; }}', parser, parser._parseStylesheet.bind(parser));
assertNode('@-ms-viewport { width: 320px; height: 768px; }', parser, parser._parseStylesheet.bind(parser));
assertNode('#boo, far {} \n.far boo {}', parser, parser._parseStylesheet.bind(parser));
assertNode('@-moz-keyframes darkWordHighlight { from { background-color: inherit; } to { background-color: rgba(83, 83, 83, 0.7); } }', parser, parser._parseStylesheet.bind(parser));
assertNode('@import "foo";', parser, parser._parseStylesheet.bind(parser));
assertNode('@import url(/css/screen.css) screen, projection;', parser, parser._parseStylesheet.bind(parser));
assertNode('@page { margin: 2.5cm; }', parser, parser._parseStylesheet.bind(parser));
assertNode('@font-face { font-family: "Example Font"; }', parser, parser._parseStylesheet.bind(parser));
assertNode('@namespace "http://www.w3.org/1999/xhtml";', parser, parser._parseStylesheet.bind(parser));
assertNode('@namespace pref url(http://test);', parser, parser._parseStylesheet.bind(parser));
assertNode('@-moz-document url(http://test), url-prefix(http://www.w3.org/Style/) { body { color: purple; background: yellow; } }', parser, parser._parseStylesheet.bind(parser));
assertNode('E E[foo] E[foo="bar"] E[foo~="bar"] E[foo^="bar"] E[foo$="bar"] E[foo*="bar"] E[foo|="en"] {}', parser, parser._parseStylesheet.bind(parser));
assertNode('input[type=\"submit\"] {}', parser, parser._parseStylesheet.bind(parser));
assertNode('E:root E:nth-child(n) E:nth-last-child(n) E:nth-of-type(n) E:nth-last-of-type(n) E:first-child E:last-child {}', parser, parser._parseStylesheet.bind(parser));
assertNode('E:first-of-type E:last-of-type E:only-child E:only-of-type E:empty E:link E:visited E:active E:hover E:focus E:target E:lang(fr) E:enabled E:disabled E:checked {}', parser, parser._parseStylesheet.bind(parser));
assertNode('E::first-line E::first-letter E::before E::after {}', parser, parser._parseStylesheet.bind(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 /Panic/', function () {
let parser = new Parser();
assertError('#boo, far } \n.far boo {}', parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected);
assertError('#boo, far { far: 43px; \n.far boo {}', parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected);
});
test('@font-face', function () {
let parser = new Parser();
assertNode('@font-face {}', parser, parser._parseFontFace.bind(parser));
assertNode('@font-face { src: url(http://test) }', parser, parser._parseFontFace.bind(parser));
assertNode('@font-face { font-style: normal; font-stretch: normal; }', parser, parser._parseFontFace.bind(parser));
assertError('@font-face { font-style: normal font-stretch: normal; }', parser, parser._parseFontFace.bind(parser), ParseError.SemiColonExpected);
});
test('@keyframe selector', function () {
let parser = new Parser();
assertNode('from {}', parser, parser._parseKeyframeSelector.bind(parser));
assertNode('to {}', parser, parser._parseKeyframeSelector.bind(parser));
assertNode('0% {}', parser, parser._parseKeyframeSelector.bind(parser));
assertNode('10% {}', parser, parser._parseKeyframeSelector.bind(parser));
assertNode('100000% {}', parser, parser._parseKeyframeSelector.bind(parser));
assertNode('from { width: 100% }', parser, parser._parseKeyframeSelector.bind(parser));
assertNode('from { width: 100%; to: 10px; }', parser, parser._parseKeyframeSelector.bind(parser));
});
test('@keyframe', function () {
let parser = new Parser();
assertNode('@keyframes name {}', parser, parser._parseKeyframe.bind(parser));
assertNode('@-webkit-keyframes name {}', parser, parser._parseKeyframe.bind(parser));
assertNode('@-o-keyframes name {}', parser, parser._parseKeyframe.bind(parser));
assertNode('@-moz-keyframes name {}', parser, parser._parseKeyframe.bind(parser));
assertNode('@keyframes name { from {} to {}}', parser, parser._parseKeyframe.bind(parser));
assertNode('@keyframes name { from {} 80% {} 100% {}}', parser, parser._parseKeyframe.bind(parser));
assertNode('@keyframes name { from { top: 0px; } 80% { top: 100px; } 100% { top: 50px; }}', parser, parser._parseKeyframe.bind(parser));
assertNode('@keyframes name { from { top: 0px; } 70%, 80% { top: 100px; } 100% { top: 50px; }}', parser, parser._parseKeyframe.bind(parser));
assertNode('@keyframes name { from { top: 0px; left: 1px; right: 2px }}', parser, parser._parseKeyframe.bind(parser));
assertError('@keyframes name { from { top: 0px; left: 1px, right: 2px }}', parser, parser._parseKeyframe.bind(parser), ParseError.SemiColonExpected);
assertError('@keyframes )', parser, parser._parseKeyframe.bind(parser), ParseError.IdentifierExpected);
assertError('@keyframes name { { top: 0px; } }', parser, parser._parseKeyframe.bind(parser), ParseError.RightCurlyExpected);
assertError('@keyframes name { from, #123', parser, parser._parseKeyframe.bind(parser), ParseError.PercentageExpected);
});
test('Test import', function () {
let parser = new Parser();
assertNode('@import "asdasdsa"', parser, parser._parseImport.bind(parser));
assertNode('@ImPort "asdsadsa"', parser, parser._parseImport.bind(parser));
assertNode('@import "asdasd" dsfsdf', parser, parser._parseImport.bind(parser));
assertError('@import', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected);
});
test('Test media', function () {
let parser = new Parser();
assertNode('@media asdsa { }', parser, parser._parseMedia.bind(parser));
assertNode('@meDia sadd{} ', parser, parser._parseMedia.bind(parser));
assertNode('@media somename, othername2 { }', parser, parser._parseMedia.bind(parser));
assertNode('@media only screen and (max-width:850px) { }', parser, parser._parseMedia.bind(parser));
assertNode('@media only screen and (max-width:850px) { }', parser, parser._parseMedia.bind(parser));
assertNode('@media all and (min-width:500px) { }', parser, parser._parseMedia.bind(parser));
assertNode('@media screen and (color), projection and (color) { }', parser, parser._parseMedia.bind(parser));
assertNode('@media not screen and (device-aspect-ratio: 16/9) { }', parser, parser._parseMedia.bind(parser));
assertNode('@media print and (min-resolution: 300dpi) { }', parser, parser._parseMedia.bind(parser));
assertNode('@media print and (min-resolution: 118dpcm) { }', parser, parser._parseMedia.bind(parser));
assertNode('@media print { @page { margin: 10% } blockquote, pre { page-break-inside: avoid } }', parser, parser._parseMedia.bind(parser));
assertNode('@media print { body:before { } }', parser, parser._parseMedia.bind(parser));
assertError('@media somename othername2 { }', parser, parser._parseMedia.bind(parser), ParseError.LeftCurlyExpected);
assertError('@media not, screen { }', parser, parser._parseMedia.bind(parser), ParseError.IdentifierExpected);
assertError('@media not screen and foo { }', parser, parser._parseMedia.bind(parser), ParseError.LeftParenthesisExpected);
assertError('@media not screen and () { }', parser, parser._parseMedia.bind(parser), ParseError.IdentifierExpected);
assertError('@media not screen and (color:) { }', parser, parser._parseMedia.bind(parser), ParseError.TermExpected);
assertError('@media not screen and (color:#234567 { }', parser, parser._parseMedia.bind(parser), ParseError.RightParenthesisExpected);
});
test('Test media_list', function () {
let parser = new Parser();
assertNode('somename', parser, parser._parseMediaList.bind(parser));
assertNode('somename, othername', parser, parser._parseMediaList.bind(parser));
});
test('medium', function () {
let parser = new Parser();
assertNode('somename', parser, parser._parseMedium.bind(parser));
assertNode('-asdas', parser, parser._parseMedium.bind(parser));
assertNode('-asda34s', parser, parser._parseMedium.bind(parser));
});
test('page', function () {
let parser = new Parser();
assertNode('@page : name{ }', parser, parser._parsePage.bind(parser));
assertNode('@page :left, :right { }', parser, parser._parsePage.bind(parser));
assertNode('@page : name{ some : "asdas" }', parser, parser._parsePage.bind(parser));
assertNode('@page : name{ some : "asdas" !important }', parser, parser._parsePage.bind(parser));
assertNode('@page : name{ some : "asdas" !important; some : "asdas" !important }', parser, parser._parsePage.bind(parser));
assertNode('@page rotated { size : landscape }', parser, parser._parsePage.bind(parser));
assertNode('@page :left { margin-left: 4cm; margin-right: 3cm; }', parser, parser._parsePage.bind(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 :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);
});
test('pseudo page', function () {
let parser = new Parser();
assertNode(': some ', parser, parser._parsePageSelector.bind(parser));
});
test('operator', function () {
let parser = new Parser();
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('combinator', function () {
let parser = new Parser();
assertNode('+', parser, parser._parseCombinator.bind(parser));
assertNode('+ ', parser, parser._parseCombinator.bind(parser));
assertNode('> ', parser, parser._parseCombinator.bind(parser));
assertNode('>', parser, parser._parseCombinator.bind(parser));
});
test('unary_operator', function () {
let parser = new Parser();
assertNode('-', parser, parser._parseUnaryOperator.bind(parser));
assertNode('+', parser, parser._parseUnaryOperator.bind(parser));
});
test('Property', function () {
let parser = new Parser();
assertNode('asdsa', parser, parser._parseProperty.bind(parser));
assertNode('asdsa334', parser, parser._parseProperty.bind(parser));
assertNode('--color', parser, parser._parseProperty.bind(parser));
assertNode('--primary-font', parser, parser._parseProperty.bind(parser));
assertNode('-color', parser, parser._parseProperty.bind(parser));
assertNode('somevar', parser, parser._parseProperty.bind(parser));
assertNode('some--let', parser, parser._parseProperty.bind(parser));
assertNode('somevar--', parser, parser._parseProperty.bind(parser));
});
test('Ruleset', function () {
let parser = new Parser();
assertNode('name{ }', parser, parser._parseRuleset.bind(parser));
assertNode(' name\n{ some : "asdas" }', parser, parser._parseRuleset.bind(parser));
assertNode(' name{ some : "asdas" !important }', parser, parser._parseRuleset.bind(parser));
assertNode('name{ \n some : "asdas" !important; some : "asdas" }', parser, parser._parseRuleset.bind(parser));
assertNode('* {}', parser, parser._parseRuleset.bind(parser));
assertNode('.far{}', parser, parser._parseRuleset.bind(parser));
assertNode('boo {}', parser, parser._parseRuleset.bind(parser));
assertNode('.far #boo {}', parser, parser._parseRuleset.bind(parser));
assertNode('boo { prop: value }', parser, parser._parseRuleset.bind(parser));
assertNode('boo { prop: value; }', parser, parser._parseRuleset.bind(parser));
assertNode('boo { prop: value; prop: value }', parser, parser._parseRuleset.bind(parser));
assertNode('boo { prop: value; prop: value; }', parser, parser._parseRuleset.bind(parser));
});
test('Ruleset /Panic/', function () {
let parser = new Parser();
// assertNode('boo { : value }', parser, parser._parseRuleset.bind(parser));
assertError('boo { prop: ; }', parser, parser._parseRuleset.bind(parser), ParseError.PropertyValueExpected);
assertError('boo { prop }', parser, parser._parseRuleset.bind(parser), ParseError.ColonExpected);
assertError('boo { prop: ; far: 12em; }', parser, parser._parseRuleset.bind(parser), ParseError.PropertyValueExpected);
// assertNode('boo { prop: ; 1ar: 12em; }', parser, parser._parseRuleset.bind(parser));
});
test('selector', function () {
let parser = new Parser();
assertNode('asdsa', parser, parser._parseSelector.bind(parser));
assertNode('asdsa + asdas', parser, parser._parseSelector.bind(parser));
assertNode('asdsa + asdas + name', parser, parser._parseSelector.bind(parser));
assertNode('asdsa + asdas + name', parser, parser._parseSelector.bind(parser));
assertNode('name #id#anotherid', parser, parser._parseSelector.bind(parser));
assertNode('name.far .boo', parser, parser._parseSelector.bind(parser));
assertNode('name .name .zweitername', parser, parser._parseSelector.bind(parser));
assertNode('*', parser, parser._parseSelector.bind(parser));
assertNode('#id', parser, parser._parseSelector.bind(parser));
assertNode('far.boo', parser, parser._parseSelector.bind(parser));
});
test('simple selector', function () {
let parser = new Parser();
assertNode('name', parser, parser._parseSimpleSelector.bind(parser));
assertNode('#id#anotherid', parser, parser._parseSimpleSelector.bind(parser));
assertNode('name.far', parser, parser._parseSimpleSelector.bind(parser));
assertNode('name.erstername.zweitername', parser, parser._parseSimpleSelector.bind(parser));
});
test('element name', function () {
let parser = new Parser();
assertNode('name', parser, parser._parseElementName.bind(parser));
assertNode('*', parser, parser._parseElementName.bind(parser));
});
test('attrib', function () {
let parser = new Parser();
assertNode('[name]', parser, parser._parseAttrib.bind(parser));
assertNode('[name = name2]', parser, parser._parseAttrib.bind(parser));
assertNode('[name ~= name3]', parser, parser._parseAttrib.bind(parser));
assertNode('[name~=name3]', parser, parser._parseAttrib.bind(parser));
assertNode('[name |= name3]', parser, parser._parseAttrib.bind(parser));
assertNode('[name |= "this is a striiiing"]', parser, parser._parseAttrib.bind(parser));
});
test('pseudo', function () {
let parser = new Parser();
assertNode(':some', parser, parser._parsePseudo.bind(parser));
assertNode(':some(thing)', parser, parser._parsePseudo.bind(parser));
assertNode(':nth-child(12)', parser, parser._parsePseudo.bind(parser));
assertNode(':lang(it)', parser, parser._parsePseudo.bind(parser));
assertNode(':not(.class)', parser, parser._parsePseudo.bind(parser));
assertNode(':not(:disabled)', parser, parser._parsePseudo.bind(parser));
assertNode(':not(#foo)', parser, parser._parsePseudo.bind(parser));
});
test('declaration', function () {
let parser = new Parser();
assertNode('name : "this is a string" !important', parser, parser._parseDeclaration.bind(parser));
assertNode('name : "this is a string"', parser, parser._parseDeclaration.bind(parser));
assertNode('property:12', parser, parser._parseDeclaration.bind(parser));
assertNode('-vendor-property: 12', parser, parser._parseDeclaration.bind(parser));
assertNode('font-size: 12px', parser, parser._parseDeclaration.bind(parser));
assertNode('color : #888 /4', parser, parser._parseDeclaration.bind(parser));
assertNode('filter : progid:DXImageTransform.Microsoft.Shadow(color=#000000,direction=45)', parser, parser._parseDeclaration.bind(parser));
assertNode('filter : progid: DXImageTransform.\nMicrosoft.\nDropShadow(\noffx=2, offy=1, color=#000000)', parser, parser._parseDeclaration.bind(parser));
assertNode('font-size: 12px', parser, parser._parseDeclaration.bind(parser));
assertNode('*background: #f00 /* IE 7 and below */', parser, parser._parseDeclaration.bind(parser));
assertNode('_background: #f60 /* IE 6 and below */', parser, parser._parseDeclaration.bind(parser));
assertNode('background-image: linear-gradient(to right, silver, white 50px, white calc(100% - 50px), silver)', parser, parser._parseDeclaration.bind(parser));
assertNode('--color: #F5F5F5', parser, parser._parseDeclaration.bind(parser));
assertNode('--color: 0', parser, parser._parseDeclaration.bind(parser));
assertNode('--color: 255', parser, parser._parseDeclaration.bind(parser));
assertNode('--color: 25.5', parser, parser._parseDeclaration.bind(parser));
assertNode('--color: 25px', parser, parser._parseDeclaration.bind(parser));
assertNode('--color: 25.5px', parser, parser._parseDeclaration.bind(parser));
assertNode('--primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', parser, parser._parseDeclaration.bind(parser));
assertError('--color : ', parser, parser._parseDeclaration.bind(parser), ParseError.PropertyValueExpected);
assertError('--color value', parser, parser._parseDeclaration.bind(parser), ParseError.ColonExpected);
});
test('term', function () {
let parser = new Parser();
assertNode('"asdasd"', parser, parser._parseTerm.bind(parser));
assertNode('name', parser, parser._parseTerm.bind(parser));
assertNode('#FFFFFF', parser, parser._parseTerm.bind(parser));
assertNode('url("this is a url")', parser, parser._parseTerm.bind(parser));
assertNode('+324', parser, parser._parseTerm.bind(parser));
assertNode('-45', parser, parser._parseTerm.bind(parser));
assertNode('+45', parser, parser._parseTerm.bind(parser));
assertNode('-45%', parser, parser._parseTerm.bind(parser));
assertNode('-45mm', parser, parser._parseTerm.bind(parser));
assertNode('-45em', parser, parser._parseTerm.bind(parser));
assertNode('"asdsa"', parser, parser._parseTerm.bind(parser));
assertNode('faa', parser, parser._parseTerm.bind(parser));
assertNode('url("this is a striiiiing")', parser, parser._parseTerm.bind(parser));
assertNode('#FFFFFF', parser, parser._parseTerm.bind(parser));
assertNode('name(asd)', parser, parser._parseTerm.bind(parser));
assertNode('calc(50% + 20px)', parser, parser._parseTerm.bind(parser));
assertNode('calc(50% + (100%/3 - 2*1em - 2*1px))', parser, parser._parseTerm.bind(parser));
assertNoNode('%(\'repetitions: %S file: %S\', 1 + 2, "directory/file.less")', parser, parser._parseTerm.bind(parser)); // less syntax
assertNoNode('~"ms:alwaysHasItsOwnSyntax.For.Stuff()"', parser, parser._parseTerm.bind(parser)); // less syntax
});
test('function', function () {
let parser = new Parser();
assertNode('name( "bla" )', parser, parser._parseFunction.bind(parser));
assertNode('name( name )', parser, parser._parseFunction.bind(parser));
assertNode('name( -500mm )', parser, parser._parseFunction.bind(parser));
assertNode('\u060frf()', parser, parser._parseFunction.bind(parser));
assertNode('über()', parser, parser._parseFunction.bind(parser));
assertNoNode('über ()', parser, parser._parseFunction.bind(parser));
assertNoNode('%()', parser, parser._parseFunction.bind(parser));
assertNoNode('% ()', parser, parser._parseFunction.bind(parser));
assertFunction('let(--color)', parser, parser._parseFunction.bind(parser));
assertFunction('let(--color, somevalue)', parser, parser._parseFunction.bind(parser));
assertFunction('let(--variable1, --variable2)', parser, parser._parseFunction.bind(parser));
assertFunction('let(--variable1, let(--variable2))', parser, parser._parseFunction.bind(parser));
assertFunction('fun(value1, value2)', parser, parser._parseFunction.bind(parser));
});
test('Test Token prio', function () {
let parser = new Parser();
assertNode('!important', parser, parser._parsePrio.bind(parser));
assertNode('!/*demo*/important', parser, parser._parsePrio.bind(parser));
assertNode('! /*demo*/ important', parser, parser._parsePrio.bind(parser));
assertNode('! /*dem o*/ important', parser, parser._parsePrio.bind(parser));
});
test('hexcolor', function () {
let parser = new Parser();
assertNode('#FFF', parser, parser._parseHexColor.bind(parser));
assertNode('#FFFFFF', parser, parser._parseHexColor.bind(parser));
});
test('Test class', function () {
let parser = new Parser();
assertNode('.faa', parser, parser._parseClass.bind(parser));
assertNode('faa', parser, parser._parseElementName.bind(parser));
assertNode('*', parser, parser._parseElementName.bind(parser));
assertNode('.faa42', parser, parser._parseClass.bind(parser));
});
test('Prio', function () {
let parser = new Parser();
assertNode('!important', parser, parser._parsePrio.bind(parser));
});
test('Expr', function () {
let parser = new Parser();
assertNode('45,5px', parser, parser._parseExpr.bind(parser));
assertNode(' 45 , 5px ', parser, parser._parseExpr.bind(parser));
assertNode('5/6', parser, parser._parseExpr.bind(parser));
assertNode('36mm, -webkit-calc(100%-10px)', parser, parser._parseExpr.bind(parser));
});
});

View File

@@ -1,228 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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';
suite('CSS - Scanner', () => {
function assertSingleToken(scan: Scanner, source: string, len: number, offset: number, text: string, type: TokenType): void {
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);
}
test('Whitespace', function () {
let scanner = new Scanner();
assertSingleToken(scanner, ' @', 1, 1, '@', TokenType.Delim);
assertSingleToken(scanner, ' /* comment*/ \n/*comment*/@', 1, 26, '@', TokenType.Delim);
scanner = new Scanner();
scanner.ignoreWhitespace = false;
assertSingleToken(scanner, ' @', 1, 0, ' ', TokenType.Whitespace);
assertSingleToken(scanner, '/*comment*/ @', 1, 11, ' ', TokenType.Whitespace);
scanner = new Scanner();
scanner.ignoreComment = false;
assertSingleToken(scanner, ' /*comment*/@', 11, 1, '/*comment*/', TokenType.Comment);
assertSingleToken(scanner, '/*comment*/ @', 11, 0, '/*comment*/', TokenType.Comment);
});
test('Token Ident', function () {
let scanner = new Scanner();
assertSingleToken(scanner, '\u060frf', 3, 0, '\u060frf', TokenType.Ident);
assertSingleToken(scanner, 'über', 4, 0, 'über', TokenType.Ident);
assertSingleToken(scanner, '-bo', 3, 0, '-bo', TokenType.Ident);
assertSingleToken(scanner, '_bo', 3, 0, '_bo', TokenType.Ident);
assertSingleToken(scanner, 'boo', 3, 0, 'boo', TokenType.Ident);
assertSingleToken(scanner, 'Boo', 3, 0, 'Boo', TokenType.Ident);
assertSingleToken(scanner, 'red--', 5, 0, 'red--', TokenType.Ident);
assertSingleToken(scanner, 'red-->', 5, 0, 'red--', TokenType.Ident);
assertSingleToken(scanner, '--red', 5, 0, '--red', TokenType.Ident);
assertSingleToken(scanner, 'a\\.b', 4, 0, 'a\.b', TokenType.Ident);
assertSingleToken(scanner, '\\E9motion', 9, 0, 'émotion', TokenType.Ident);
assertSingleToken(scanner, '\\E9 dition', 10, 0, 'édition', TokenType.Ident);
assertSingleToken(scanner, '\\0000E9dition', 13, 0, 'édition', TokenType.Ident);
assertSingleToken(scanner, 'S\\0000e9f', 9, 0, 'Séf', TokenType.Ident);
});
test('Token Url', function () {
let scanner = new Scanner();
assertSingleToken(scanner, 'url(\'http://msft.com\')', 22, 0, 'url(\'http://msft.com\')', TokenType.URI);
assertSingleToken(scanner, 'url("http://msft.com")', 22, 0, 'url("http://msft.com")', TokenType.URI);
assertSingleToken(scanner, 'url( "http://msft.com")', 23, 0, 'url( "http://msft.com")', TokenType.URI);
assertSingleToken(scanner, 'url(\t"http://msft.com")', 23, 0, 'url(\t"http://msft.com")', TokenType.URI);
assertSingleToken(scanner, 'url(\n"http://msft.com")', 23, 0, 'url(\n"http://msft.com")', TokenType.URI);
assertSingleToken(scanner, 'url("http://msft.com"\n)', 23, 0, 'url("http://msft.com"\n)', TokenType.URI);
assertSingleToken(scanner, 'url("")', 7, 0, 'url("")', TokenType.URI);
assertSingleToken(scanner, 'uRL("")', 7, 0, 'uRL("")', TokenType.URI);
assertSingleToken(scanner, 'URL("")', 7, 0, 'URL("")', TokenType.URI);
assertSingleToken(scanner, 'url(http://msft.com)', 20, 0, 'url(http://msft.com)', TokenType.URI);
assertSingleToken(scanner, 'url()', 5, 0, 'url()', TokenType.URI);
assertSingleToken(scanner, 'url(\'http://msft.com\n)', 22, 0, 'url(\'http://msft.com\n)', TokenType.BadUri);
assertSingleToken(scanner, 'url("http://msft.com"', 21, 0, 'url("http://msft.com"', TokenType.BadUri);
assertSingleToken(scanner, 'url(http://msft.com\')', 21, 0, 'url(http://msft.com\')', TokenType.URI);
});
test('Token AtKeyword', function () {
let scanner = new Scanner();
assertSingleToken(scanner, '@import', 7, 0, '@import', TokenType.AtKeyword);
assertSingleToken(scanner, '@importttt', 10, 0, '@importttt', TokenType.AtKeyword);
assertSingleToken(scanner, '@imp', 4, 0, '@imp', TokenType.AtKeyword);
assertSingleToken(scanner, '@5', 2, 0, '@5', TokenType.AtKeyword);
assertSingleToken(scanner, '@media', 6, 0, '@media', TokenType.AtKeyword);
assertSingleToken(scanner, '@page', 5, 0, '@page', TokenType.AtKeyword);
assertSingleToken(scanner, '@charset', 8, 0, '@charset', TokenType.Charset);
assertSingleToken(scanner, '@-mport', 7, 0, '@-mport', TokenType.AtKeyword);
assertSingleToken(scanner, '@\u00f0mport', 7, 0, '@\u00f0mport', TokenType.AtKeyword);
assertSingleToken(scanner, '@', 1, 0, '@', TokenType.Delim);
});
test('Token Number', function () {
let scanner = new Scanner();
assertSingleToken(scanner, '1234', 4, 0, '1234', TokenType.Num);
assertSingleToken(scanner, '1.34', 4, 0, '1.34', TokenType.Num);
assertSingleToken(scanner, '.234', 4, 0, '.234', TokenType.Num);
assertSingleToken(scanner, '.234.', 4, 0, '.234', TokenType.Num);
assertSingleToken(scanner, '..234', 1, 0, '.', TokenType.Delim);
});
test('Token Delim', function () {
let scanner = new Scanner();
assertSingleToken(scanner, '@', 1, 0, '@', TokenType.Delim);
assertSingleToken(scanner, '+', 1, 0, '+', TokenType.Delim);
assertSingleToken(scanner, '>', 1, 0, '>', TokenType.Delim);
assertSingleToken(scanner, '#', 1, 0, '#', TokenType.Delim);
assertSingleToken(scanner, '\'', 1, 0, '\'', TokenType.BadString);
assertSingleToken(scanner, '"', 1, 0, '"', TokenType.BadString);
});
test('Token Hash', function () {
let scanner = new Scanner();
assertSingleToken(scanner, '#import', 7, 0, '#import', TokenType.Hash);
assertSingleToken(scanner, '#-mport', 7, 0, '#-mport', TokenType.Hash);
assertSingleToken(scanner, '#123', 4, 0, '#123', TokenType.Hash);
});
test('Token Dimension/Percentage', function () {
let scanner = new Scanner();
assertSingleToken(scanner, '3em', 3, 0, '3em', TokenType.EMS);
assertSingleToken(scanner, '4.423ex', 7, 0, '4.423ex', TokenType.EXS);
assertSingleToken(scanner, '3423px', 6, 0, '3423px', TokenType.Length);
assertSingleToken(scanner, '4.423cm', 7, 0, '4.423cm', TokenType.Length);
assertSingleToken(scanner, '4.423mm', 7, 0, '4.423mm', TokenType.Length);
assertSingleToken(scanner, '4.423in', 7, 0, '4.423in', TokenType.Length);
assertSingleToken(scanner, '4.423pt', 7, 0, '4.423pt', TokenType.Length);
assertSingleToken(scanner, '4.423pc', 7, 0, '4.423pc', TokenType.Length);
assertSingleToken(scanner, '4.423deg', 8, 0, '4.423deg', TokenType.Angle);
assertSingleToken(scanner, '4.423rad', 8, 0, '4.423rad', TokenType.Angle);
assertSingleToken(scanner, '4.423grad', 9, 0, '4.423grad', TokenType.Angle);
assertSingleToken(scanner, '4.423ms', 7, 0, '4.423ms', TokenType.Time);
assertSingleToken(scanner, '4.423s', 6, 0, '4.423s', TokenType.Time);
assertSingleToken(scanner, '4.423hz', 7, 0, '4.423hz', TokenType.Freq);
assertSingleToken(scanner, '.423khz', 7, 0, '.423khz', TokenType.Freq);
assertSingleToken(scanner, '3.423%', 6, 0, '3.423%', TokenType.Percentage);
assertSingleToken(scanner, '.423%', 5, 0, '.423%', TokenType.Percentage);
assertSingleToken(scanner, '.423ft', 6, 0, '.423ft', TokenType.Dimension);
assertSingleToken(scanner, '200dpi', 6, 0, '200dpi', TokenType.Resolution);
assertSingleToken(scanner, '123dpcm', 7, 0, '123dpcm', TokenType.Resolution);
});
test('Token String', function () {
let scanner = new Scanner();
assertSingleToken(scanner, '\'farboo\'', 8, 0, '\'farboo\'', TokenType.String);
assertSingleToken(scanner, '"farboo"', 8, 0, '"farboo"', TokenType.String);
assertSingleToken(scanner, '"farbo\u00f0"', 8, 0, '"farbo\u00f0"', TokenType.String);
assertSingleToken(scanner, '"far\\\"oo"', 9, 0, '"far\"oo"', TokenType.String);
assertSingleToken(scanner, '"fa\\\noo"', 8, 0, '"fa\noo"', TokenType.String);
assertSingleToken(scanner, '"fa\\\roo"', 8, 0, '"fa\roo"', TokenType.String);
assertSingleToken(scanner, '"fa\\\foo"', 8, 0, '"fa\foo"', TokenType.String);
assertSingleToken(scanner, '\'farboo"', 8, 0, '\'farboo"', TokenType.BadString);
assertSingleToken(scanner, '\'farboo', 7, 0, '\'farboo', TokenType.BadString);
assertSingleToken(scanner, '\'', 1, 0, '\'', TokenType.BadString);
assertSingleToken(scanner, '"', 1, 0, '"', TokenType.BadString);
});
test('Token CDO', function () {
let scanner = new Scanner();
assertSingleToken(scanner, '<!--', 4, 0, '<!--', TokenType.CDO);
assertSingleToken(scanner, '<!-\n-', 1, 0, '<', TokenType.Delim);
});
test('Token CDC', function () {
let scanner = new Scanner();
assertSingleToken(scanner, '-->', 3, 0, '-->', TokenType.CDC);
assertSingleToken(scanner, '--y>', 3, 0, '--y', TokenType.Ident);
assertSingleToken(scanner, '--<', 1, 0, '-', TokenType.Delim);
});
test('Token singletokens ;:{}[]()', function () {
let scanner = new Scanner();
assertSingleToken(scanner, ': ', 1, 0, ':', TokenType.Colon);
assertSingleToken(scanner, '; ', 1, 0, ';', TokenType.SemiColon);
assertSingleToken(scanner, '{ ', 1, 0, '{', TokenType.CurlyL);
assertSingleToken(scanner, '} ', 1, 0, '}', TokenType.CurlyR);
assertSingleToken(scanner, '[ ', 1, 0, '[', TokenType.BracketL);
assertSingleToken(scanner, '] ', 1, 0, ']', TokenType.BracketR);
assertSingleToken(scanner, '( ', 1, 0, '(', TokenType.ParenthesisL);
assertSingleToken(scanner, ') ', 1, 0, ')', TokenType.ParenthesisR);
});
test('Token dashmatch & includes', function () {
let scanner = new Scanner();
assertSingleToken(scanner, '~=', 2, 0, '~=', TokenType.Includes);
assertSingleToken(scanner, '~', 1, 0, '~', TokenType.Delim);
assertSingleToken(scanner, '|=', 2, 0, '|=', TokenType.Dashmatch);
assertSingleToken(scanner, '|', 1, 0, '|', TokenType.Delim);
assertSingleToken(scanner, '^=', 2, 0, '^=', TokenType.PrefixOperator);
assertSingleToken(scanner, '$=', 2, 0, '$=', TokenType.SuffixOperator);
assertSingleToken(scanner, '*=', 2, 0, '*=', TokenType.SubstringOperator);
});
test('Comments', function () {
let scanner = new Scanner();
assertSingleToken(scanner, '/* */', 0, 10, '', TokenType.EOF);
assertSingleToken(scanner, '/* abcd*/', 0, 14, '', TokenType.EOF);
assertSingleToken(scanner, '/*abcd */', 0, 10, '', TokenType.EOF);
assertSingleToken(scanner, '/* ab- .-cd */', 0, 15, '', TokenType.EOF);
});
test('Whitespaces', function () {
let scanner = new Scanner();
assertSingleToken(scanner, ' ', 0, 1, '', TokenType.EOF);
assertSingleToken(scanner, ' ', 0, 6, '', TokenType.EOF);
});
});
suite('CSS - Token Sequences', () => {
function assertTokenSequence(scan: Scanner, source: string, ...tokens: TokenType[]): void {
scan.setSource(source);
let token = scan.scan();
let i = 0;
while (tokens.length > i) {
assert.equal(token.type, tokens[i]);
token = scan.scan();
i++;
}
}
// tests with skipping comments
test('Token Sequence', function () {
let scanner = new Scanner();
assertTokenSequence(scanner, '5 5 5 5', TokenType.Num, TokenType.Num, TokenType.Num, TokenType.Num);
assertTokenSequence(scanner, '/* 5 4 */-->', TokenType.CDC);
assertTokenSequence(scanner, '/* 5 4 */ -->', TokenType.CDC);
assertTokenSequence(scanner, '/* "adaasd" */ -->', TokenType.CDC);
assertTokenSequence(scanner, '/* <!-- */ -->', TokenType.CDC);
assertTokenSequence(scanner, 'red-->', TokenType.Ident, TokenType.Delim);
assertTokenSequence(scanner, '@ import', TokenType.Delim, TokenType.Ident);
});
});

View File

@@ -1,106 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 * as nodes from '../../parser/cssNodes';
import * as selectorPrinter from '../../services/selectorPrinting';
import {TextDocument} from 'vscode-languageserver';
function elementToString(element: selectorPrinter.Element): string {
let label = element.name || '';
if (element.attributes) {
label = label + '[';
let needsSeparator = false;
for (let key in element.attributes) {
if (needsSeparator) {
label = label + '|';
}
needsSeparator = true;
label = label + key + '=' + element.attributes[key];
}
label = label + ']';
}
if (element.children) {
label = label + '{';
for (let index = 0; index < element.children.length; index++) {
if (index > 0) {
label = label + '|';
}
label = label + elementToString(element.children[index]);
}
label = label + '}';
}
return label;
}
export function parseSelector(p: Parser, input: string, selectorName: string, expected: string): void {
let document = TextDocument.create('test://test/test.css', 'css', 0, input);
let styleSheet = p.parseStylesheet(document);
let node = nodes.getNodeAtOffset(styleSheet, input.indexOf(selectorName));
let selector = node.findParent(nodes.NodeType.Selector);
let element = selectorPrinter.selectorToElement(selector);
assert.equal(elementToString(element), expected);
}
export interface ExpectedElement {
name?: string;
attributes?: { [name: string]: string; };
}
export function assertElement(p: Parser, input: string, element: ExpectedElement): void {
let node = p.internalParse(input, p._parseSimpleSelector);
let actual = selectorPrinter.toElement(node);
assert.equal(actual.name, element.name);
assert.deepEqual(actual.attributes, element.attributes);
}
suite('CSS - Selector Printing', () => {
test('class/hash/elementname/attr', function () {
let p = new Parser();
assertElement(p, 'element', { name: 'element' });
assertElement(p, '.div', { attributes: { class: 'div' } });
assertElement(p, '#first', { attributes: { id: 'first' } });
assertElement(p, 'element.on', { name: 'element', attributes: { class: 'on' } });
assertElement(p, 'element.on#first', { name: 'element', attributes: { class: 'on', id: 'first' } });
assertElement(p, '.on#first', { attributes: { class: 'on', id: 'first' } });
assertElement(p, '[lang=\'de\']', { attributes: { lang: 'de' } });
assertElement(p, '[enabled]', { attributes: { enabled: undefined } });
});
test('simple selector', function () {
let p = new Parser();
parseSelector(p, 'element { }', 'element', '{element}');
parseSelector(p, 'element.div { }', 'element', '{element[class=div]}');
parseSelector(p, 'element.on#first { }', 'element', '{element[class=on|id=first]}');
parseSelector(p, 'element:hover { }', 'element', '{element[:hover=]}');
parseSelector(p, 'element[lang=\'de\'] { }', 'element', '{element[lang=de]}');
parseSelector(p, 'element[enabled] { }', 'element', '{element[enabled=undefined]}');
parseSelector(p, 'element[foo~="warning"] { }', 'element', '{element[foo= … warning … ]}');
parseSelector(p, 'element[lang|="en"] { }', 'element', '{element[lang=en-…]}');
parseSelector(p, '* { }', '*', '{element}');
});
test('selector', function () {
let p = new Parser();
parseSelector(p, 'e1 e2 { }', 'e1', '{e1{…{e2}}}');
parseSelector(p, 'e1 .div { }', 'e1', '{e1{…{[class=div]}}}');
parseSelector(p, 'e1 > e2 { }', 'e2', '{e1{e2}}');
parseSelector(p, 'e1, e2 { }', 'e1', '{e1}');
parseSelector(p, 'e1 + e2 { }', 'e2', '{e1|e2}');
parseSelector(p, 'e1 ~ e2 { }', 'e2', '{e1|e2|⋮|e2}');
});
});

View File

@@ -1,73 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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));
});
});

View File

@@ -1,55 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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));
});
});

View File

@@ -1,24 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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');
});
});

View File

@@ -1,218 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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));
});
});

View File

@@ -1,39 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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);
});
});

View File

@@ -1,336 +0,0 @@
// snippets from the scss documentation at http://sass-lang.com/
/* css stuff */
/* charset */
@charset "UTF-8";
/* nested rules */
#main {
width: 97%;
p, div {
font-size: 2em;
a { font-weight: bold; }
}
pre { font-size: 3em; }
}
/* parent selector (&) */
#main {
color: black;
a {
font-weight: bold;
&:hover { color: red; }
}
}
/* nested properties */
.funky {
font: 2px/3px {
family: fantasy;
size: 30em;
weight: bold;
}
color: black;
}
/* nesting conflicts */
tr.default {
foo: { // properties
foo : 1;
}
foo: 1px; // rule
foo.bar { // selector
foo : 1;
}
foo:bar { // selector
foo : 1;
}
foo: 1px; // rule
}
/* extended comment syntax */
/* This comment is
* several lines long.
* since it uses the CSS comment syntax,
* it will appear in the CSS output. */
body { color: black; }
// These comments are only one line long each.
// They won't appear in the CSS output,
// since they use the single-line comment syntax.
a { color: green; }
/* variables */
$width: 5em;
$width: "Second width?" !default;
#main {
$localvar: 6em;
width: $width;
$font-size: 12px;
$line-height: 30px;
font: #{$font-size}/#{$line-height};
}
$name: foo;
$attr: border;
p.#{$name} {
#{$attr}-color: blue;
}
/* variable declaration with whitespaces */
// Set the color of your columns
$grid-background-column-color : rgba(100, 100, 225, 0.25) !default;
/* operations*/
p {
width: (1em + 2em) * 3;
color: #010203 + #040506;
font-family: sans- + "serif";
margin: 3px + 4px auto;
content: "I ate #{5 + 10} pies!";
color: hsl(0, 100%, 50%);
color: hsl($hue: 0, $saturation: 100%, $lightness: 50%);
}
/* functions*/
$grid-width: 40px;
$gutter-width: 10px;
@function grid-width($n) {
@return $n * $grid-width + ($n - 1) * $gutter-width;
}
#sidebar { width: grid-width(5); }
/* @import */
@import "foo.scss";
$family: unquote("Droid+Sans");
@import "rounded-corners", url("http://fonts.googleapis.com/css?family=#{$family}");
#main {
@import "example";
}
/* @media */
.sidebar {
width: 300px;
@media screen and (orientation: landscape) {
width: 500px;
}
}
/* @extend */
.error {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
@extend .error;
border-width: 3px;
}
#context a%extreme {
color: blue;
font-weight: bold;
font-size: 2em;
}
.notice {
@extend %extreme !optional;
}
/* @debug and @warn */
@debug 10em + 12em;
@mixin adjust-location($x, $y) {
@if unitless($x) {
@warn "Assuming #{$x} to be in pixels";
$x: 1px * $x;
}
@if unitless($y) {
@warn "Assuming #{$y} to be in pixels";
$y: 1px * $y;
}
position: relative; left: $x; top: $y;
}
/* control directives */
/* if statement */
p {
@if 1 + 1 == 2 { border: 1px solid; }
@if 5 < 3 { border: 2px dotted; }
@if null { border: 3px double; }
}
/* if else statement */
$type: monster;
p {
@if $type == ocean {
color: blue;
} @else {
color: black;
}
}
/* for statement */
@for $i from 1 through 3 {
.item-#{$i} { width: 2em * $i; }
}
/* each statement */
@each $animal in puma, sea-slug, egret, salamander {
.#{$animal}-icon {
background-image: url('/images/#{$animal}.png');
}
}
/* while statement */
$i: 6;
@while $i > 0 {
.item-#{$i} { width: 2em * $i; }
$i: $i - 2;
}
/* function with controlstatements */
@function foo($total, $a) {
@for $i from 0 to $total {
@if (unit($a) == "%") and ($i == ($total - 1)) {
$z: 100%;
@return '1';
}
}
@return $grid;
}
/* @mixin simple*/
@mixin large-text {
font: {
family: Arial;
size: 20px;
weight: bold;
}
color: #ff0000;
}
.page-title {
@include large-text;
padding: 4px;
}
/* mixin with parameters */
@mixin sexy-border($color, $width: 1in) {
border: {
color: $color;
width: $width;
style: dashed;
}
}
p { @include sexy-border(blue); }
/* mixin with varargs */
@mixin box-shadow($shadows...) {
-moz-box-shadow: $shadows;
-webkit-box-shadow: $shadows;
box-shadow: $shadows;
}
.shadows {
@include box-shadow(0px 4px 5px #666, 2px 6px 10px #999);
}
/* include with varargs */
@mixin colors($text, $background, $border) {
color: $text;
background-color: $background;
border-color: $border;
}
$values: #ff0000, #00ff00, #0000ff;
.primary {
@include colors($values...);
}
/* include with body */
@mixin apply-to-ie6-only {
* html {
@content;
}
}
@include apply-to-ie6-only {
#logo {
background-image: url(/logo.gif);
}
}
/* attributes */
[rel="external"]::after {
content: 's';
}
/*page */
@page :left {
margin-left: 4cm;
margin-right: 3cm;
}
/* missing semicolons */
tr.default {
foo.bar {
$foo: 1px
}
foo: {
foo : white
}
foo.bar1 {
@extend tr.default
}
foo.bar2 {
@import "compass"
}
bar: black
}
/* rules without whitespace */
legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:s(1)}
/* extend with interpolation variable */
@mixin error($a: false) {
@extend .#{$a};
@extend ##{$a};
}
#bar {a: 1px;}
.bar {b: 1px;}
foo {
@include error('bar');
}
/* css3: @font face */
@font-face { font-family: Delicious; src: url('Delicious-Roman.otf'); }
/* rule names with variables */
.orbit-#{$d}-prev {
#{$d}-style: 0;
foo-#{$d}: 1;
#{$d}-bar-#{$d}: 2;
foo-#{$d}-bar: 1;
}
/* keyframes */
@-webkit-keyframes NAME-YOUR-ANIMATION {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-moz-keyframes NAME-YOUR-ANIMATION {
0% { opacity: 0; }
100% { opacity: 1; }
}
@-o-keyframes NAME-YOUR-ANIMATION {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes NAME-YOUR-ANIMATION {
0% { opacity: 0; }
100% { opacity: 1; }
}
/* string escaping */
[data-icon='test-1']:before {
content:'\\';
}
/* a comment */
$var1: '\'';
$var2: "\"";
/* another comment */

View File

@@ -1,24 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 {SCSSParser} from '../../parser/scssParser';
import {assertColor} from '../css/languageFacts.test';
suite('SCSS - Language facts', () => {
test('is color', function () {
let parser = new SCSSParser();
assertColor(parser, '#main { color: foo(red) }', 'red', true);
assertColor(parser, '#main { color: red() }', 'red', false);
assertColor(parser, '#main { red { nested: 1px } }', 'red', false);
assertColor(parser, '#main { @include red; }', 'red', false);
assertColor(parser, '#main { @include foo($f: red); }', 'red', true);
assertColor(parser, '@function red($p) { @return 1px; }', 'red', false);
assertColor(parser, '@function foo($p) { @return red; }', 'red', true);
assertColor(parser, '@function foo($r: red) { @return $r; }', 'red', true);
});
});

View File

@@ -1,52 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 {Rule, Rules} from '../../services/lintRules';
import {assertEntries} from '../css/lint.test';
import {SCSSParser} from '../../parser/scssParser';
function assertFontFace(input: string, ...rules: Rule[]): void {
let p = new SCSSParser();
let node = p.internalParse(input, p._parseFontFace);
assertEntries(node, rules);
}
function assertRuleSet(input: string, ...rules: Rule[]): void {
let p = new SCSSParser();
let node = p.internalParse(input, p._parseRuleset);
assertEntries(node, rules);
}
suite('SCSS - Lint', () => {
test('empty ruleset', function () {
assertRuleSet('selector { color: red; nested {} }', Rules.EmptyRuleSet);
});
test('font-face required properties', function () {
assertFontFace('@font-face { }', Rules.RequiredPropertiesForFontFace);
assertFontFace('@font-face { src: url(test.tff) }', Rules.RequiredPropertiesForFontFace);
assertFontFace('@font-face { font-family: \'name\' }', Rules.RequiredPropertiesForFontFace);
assertFontFace('@font-face { font-#{family}: foo }'); // no error, ignore all unknown properties
assertFontFace('@font-face { font: {family: foo } }'); // no error, ignore all nested properties
assertFontFace('@font-face { @if true { } }'); // no error, ignore all nested properties
});
test('unknown properties', function () {
assertRuleSet('selector { -ms-property: "rest is missing" }', Rules.UnknownProperty);
assertRuleSet('selector { -moz-box-shadow: "rest is missing" }', Rules.UnknownProperty, Rules.IncludeStandardPropertyWhenUsingVendorPrefix);
assertRuleSet('selector { box-shadow: none }'); // no error
assertRuleSet('selector { -moz-#{box}-shadow: none }'); // no error if theres an interpolation
assertRuleSet('selector { outer: { nested : blue }'); // no error for nested
});
test('vendor specific prefixes', function () {
assertRuleSet('selector { -moz-animation: none }', Rules.AllVendorPrefixes, Rules.IncludeStandardPropertyWhenUsingVendorPrefix);
assertRuleSet('selector { -moz-transform: none; transform: none }', Rules.AllVendorPrefixes);
assertRuleSet('selector { -moz-transform: none; transform: none; -o-transform: none; -webkit-transform: none; -ms-transform: none; }');
});
});

View File

@@ -1,335 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 {SCSSParser} from '../../parser/scssParser';
import * as nodes from '../../parser/cssNodes';
import {ParseError} from '../../parser/cssErrors';
import {SCSSParseError} from '../../parser/scssErrors';
import {assertNode, assertError} from '../css/parser.test';
suite('SCSS - Parser', () => {
test('Comments', function () {
let parser = new SCSSParser();
assertNode(' a { b: /* comment */ c }', parser, parser._parseStylesheet.bind(parser));
assertNode(' a { b: /* comment \n * is several\n * lines long\n */ c }', parser, parser._parseStylesheet.bind(parser));
assertNode(' a { b: // single line comment\n c }', parser, parser._parseStylesheet.bind(parser));
});
test('Variable', function () {
let parser = new SCSSParser();
assertNode('$color', parser, parser._parseVariable.bind(parser));
assertNode('$co42lor', parser, parser._parseVariable.bind(parser));
assertNode('$-co42lor', parser, parser._parseVariable.bind(parser));
});
test('VariableDeclaration', function () {
let parser = new SCSSParser();
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 !default', parser, parser._parseVariableDeclaration.bind(parser));
assertNode('$primary-font: "wf_SegoeUI","Segoe UI","Segoe","Segoe WP"', parser, parser._parseVariableDeclaration.bind(parser));
assertError('$color: red !def', parser, parser._parseVariableDeclaration.bind(parser), ParseError.UnknownKeyword);
assertError('$color : !default', parser, parser._parseVariableDeclaration.bind(parser), ParseError.VariableValueExpected);
assertError('$color !default', parser, parser._parseVariableDeclaration.bind(parser), ParseError.ColonExpected);
});
test('Expr', function () {
let parser = new SCSSParser();
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));
assertNode('not ($v and $b) or $c', parser, parser._parseExpr.bind(parser));
assertError('(20 + 20', parser, parser._parseExpr.bind(parser), ParseError.RightParenthesisExpected);
});
test('SCSSOperator', function () {
let parser = new SCSSParser();
assertNode('>=', parser, parser._parseOperator.bind(parser));
assertNode('>', parser, parser._parseOperator.bind(parser));
assertNode('<', parser, parser._parseOperator.bind(parser));
assertNode('<=', parser, parser._parseOperator.bind(parser));
assertNode('==', parser, parser._parseOperator.bind(parser));
assertNode('!=', parser, parser._parseOperator.bind(parser));
assertNode('and', parser, parser._parseOperator.bind(parser));
assertNode('+', parser, parser._parseOperator.bind(parser));
assertNode('-', parser, parser._parseOperator.bind(parser));
assertNode('*', parser, parser._parseOperator.bind(parser));
assertNode('/', parser, parser._parseOperator.bind(parser));
assertNode('%', parser, parser._parseOperator.bind(parser));
assertNode('not', parser, parser._parseUnaryOperator.bind(parser));
});
test('Interpolation', function () {
let parser = new SCSSParser();
assertNode('#{red}', parser, parser._parseIdent.bind(parser));
assertNode('#{$color}', parser, parser._parseIdent.bind(parser));
assertNode('#{3 + 4}', parser, parser._parseIdent.bind(parser));
assertNode('#{3 + #{3 + 4}}', parser, parser._parseIdent.bind(parser));
assertNode('#{$d}-style: 0', parser, parser._parseDeclaration.bind(parser));
assertNode('foo-#{$d}: 1', parser, parser._parseDeclaration.bind(parser));
assertNode('#{$d}-bar-#{$d}: 2', parser, parser._parseDeclaration.bind(parser));
assertNode('foo-#{$d}-bar: 1', parser, parser._parseDeclaration.bind(parser));
assertNode('#{$d}-#{$d}: 2', parser, parser._parseDeclaration.bind(parser));
assertNode('&:nth-child(#{$query}+1) { clear: $opposite-direction; }', parser, parser._parseRuleset.bind(parser));
assertError('#{}', parser, parser._parseIdent.bind(parser), ParseError.ExpressionExpected);
assertError('#{1 + 2', parser, parser._parseIdent.bind(parser), ParseError.RightCurlyExpected);
});
test('Declaration', function () {
let parser = new SCSSParser();
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));
assertNode('width: (1em + 2em) * 3', parser, parser._parseDeclaration.bind(parser));
assertNode('color: #010203 + #040506', parser, parser._parseDeclaration.bind(parser));
assertNode('font-family: sans- + "serif"', parser, parser._parseDeclaration.bind(parser));
assertNode('margin: 3px + 4px auto', parser, parser._parseDeclaration.bind(parser));
assertNode('color: hsl(0, 100%, 50%)', parser, parser._parseDeclaration.bind(parser));
assertNode('color: hsl($hue: 0, $saturation: 100%, $lightness: 50%)', parser, parser._parseDeclaration.bind(parser));
assertNode('foo: if($value == \'default\', flex-gutter(), $value)', parser, parser._parseDeclaration.bind(parser));
assertError('fo = 8', parser, parser._parseDeclaration.bind(parser), ParseError.ColonExpected);
assertError('fo:', parser, parser._parseDeclaration.bind(parser), ParseError.PropertyValueExpected);
assertError('color: hsl($hue: 0,', parser, parser._parseDeclaration.bind(parser), ParseError.ExpressionExpected);
assertError('color: hsl($hue: 0', parser, parser._parseDeclaration.bind(parser), ParseError.RightParenthesisExpected);
});
test('Stylesheet', function () {
let parser = new SCSSParser();
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('#main { width: 97%; p, div { a { font-weight: bold; } } }', parser, parser._parseStylesheet.bind(parser));
assertNode('a { &:hover { color: red; } }', parser, parser._parseStylesheet.bind(parser));
assertNode('fo { font: 2px/3px { family: fantasy; } }', parser, parser._parseStylesheet.bind(parser));
assertNode('.foo { bar: { yoo: fantasy; } }', parser, parser._parseStylesheet.bind(parser));
assertNode('selector { propsuffix: { nested: 1px; } rule: 1px; nested.selector { foo: 1; } nested:selector { foo: 2 }}', parser, parser._parseStylesheet.bind(parser));
assertNode('legend {foo{a:s}margin-top:0;margin-bottom:#123;margin-top:s(1)}', parser, parser._parseStylesheet.bind(parser));
assertNode('@mixin keyframe { @keyframes name { @content; } }', parser, parser._parseStylesheet.bind(parser));
assertNode('@include keyframe { 10% { top: 3px; } }', parser, parser._parseStylesheet.bind(parser));
assertNode('.class{&--sub-class-with-ampersand{color: red;}}', parser, parser._parseStylesheet.bind(parser));
assertError('fo { font: 2px/3px { family } }', parser, parser._parseStylesheet.bind(parser), ParseError.ColonExpected);
});
test('@import', function () {
let parser = new SCSSParser();
assertNode('@import "test.css"', parser, parser._parseImport.bind(parser));
assertNode('@import url("test.css")', parser, parser._parseImport.bind(parser));
assertNode('@import "test.css", "bar.css"', parser, parser._parseImport.bind(parser));
assertNode('@import "test.css", "bar.css" screen, projection', parser, parser._parseImport.bind(parser));
assertNode('foo { @import "test.css"; }', parser, parser._parseStylesheet.bind(parser));
assertError('@import "test.css" "bar.css"', parser, parser._parseStylesheet.bind(parser), ParseError.SemiColonExpected);
assertError('@import "test.css", screen', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected);
assertError('@import', parser, parser._parseImport.bind(parser), ParseError.URIOrStringExpected);
});
test('@media', function () {
let parser = new SCSSParser();
assertNode('@media screen { .sidebar { @media (orientation: landscape) { width: 500px; } } }', parser, parser._parseStylesheet.bind(parser));
assertNode('@media #{$media} and ($feature: $value) {}', parser, parser._parseStylesheet.bind(parser));
assertNode('foo { bar { @media screen and (orientation: landscape) {}} }', parser, parser._parseStylesheet.bind(parser));
assertNode('@media screen and (nth($query, 1): nth($query, 2)) { }', parser, parser._parseMedia.bind(parser));
});
test('@keyframe', function () {
let parser = new SCSSParser();
assertNode('@keyframes name { @content; }', parser, parser._parseKeyframe.bind(parser));
});
test('@extend', function () {
let parser = new SCSSParser();
assertNode('foo { @extend .error; border-width: 3px; }', parser, parser._parseStylesheet.bind(parser));
assertNode('a.important { @extend .notice !optional; }', parser, parser._parseStylesheet.bind(parser));
assertNode('.hoverlink { @extend a:hover; }', parser, parser._parseStylesheet.bind(parser));
assertNode('.seriousError { @extend .error; @extend .attention; }', parser, parser._parseStylesheet.bind(parser));
assertNode('#context a%extreme { color: blue; } .notice { @extend %extreme }', parser, parser._parseStylesheet.bind(parser));
assertNode('@media print { .error { } .seriousError { @extend .error; } }', parser, parser._parseStylesheet.bind(parser));
assertNode('@mixin error($a: false) { @extend .#{$a}; @extend ##{$a}; }', parser, parser._parseStylesheet.bind(parser));
assertError('.hoverlink { @extend }', parser, parser._parseStylesheet.bind(parser), ParseError.SelectorExpected);
assertError('.hoverlink { @extend %extreme !default }', parser, parser._parseStylesheet.bind(parser), ParseError.UnknownKeyword);
});
test('@debug', function () {
let parser = new SCSSParser();
assertNode('@debug test;', parser, parser._parseStylesheet.bind(parser));
assertNode('foo { @debug 1 + 4; nested { @warn 1 4; } }', parser, parser._parseStylesheet.bind(parser));
assertNode('@if $foo == 1 { @debug 1 + 4 }', parser, parser._parseStylesheet.bind(parser));
});
test('@if', function () {
let parser = new SCSSParser();
assertNode('@if 1 + 1 == 2 { border: 1px solid; }', parser, parser._parseRuleSetDeclaration.bind(parser));
assertNode('@if 5 < 3 { border: 2px dotted; }', parser, parser._parseRuleSetDeclaration.bind(parser));
assertNode('@if null { border: 3px double; }', parser, parser._parseRuleSetDeclaration.bind(parser));
assertNode('@if 1 <= $let { border: 3px; } @else { border: 4px; }', parser, parser._parseRuleSetDeclaration.bind(parser));
assertNode('@if 1 >= (1 + $foo) { border: 3px; } @else if 1 + 1 == 2 { border: 4px; }', parser, parser._parseRuleSetDeclaration.bind(parser));
assertNode('p { @if $i == 1 { x: 3px; } @else if $i == 1 { x: 4px; } @else { x: 4px; } }', parser, parser._parseStylesheet.bind(parser));
assertNode('@if $i == 1 { p { x: 3px; } }', parser, parser._parseStylesheet.bind(parser));
assertError('@if { border: 1px solid; }', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected);
assertError('@if 1 }', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected);
});
test('@for', function () {
let parser = new SCSSParser();
assertNode('@for $i from 1 to 5 { .item-#{$i} { width: 2em * $i; } }', parser, parser._parseRuleSetDeclaration.bind(parser));
assertNode('@for $k from 1 + $x through 5 + $x { }', parser, parser._parseRuleSetDeclaration.bind(parser));
assertError('@for i from 0 to 4 {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.VariableNameExpected);
assertError('@for $i to 4 {}', parser, parser._parseRuleSetDeclaration.bind(parser), SCSSParseError.FromExpected);
assertError('@for $i from 0 by 4 {}', parser, parser._parseRuleSetDeclaration.bind(parser), SCSSParseError.ThroughOrToExpected);
assertError('@for $i from {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected);
assertError('@for $i from 0 to {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected);
});
test('@each', function () {
let parser = new SCSSParser();
assertNode('@each $i in 1, 2, 3 { }', parser, parser._parseRuleSetDeclaration.bind(parser));
assertNode('@each $i in 1 2 3 { }', parser, parser._parseRuleSetDeclaration.bind(parser));
assertError('@each i in 4 {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.VariableNameExpected);
assertError('@each $i from 4 {}', parser, parser._parseRuleSetDeclaration.bind(parser), SCSSParseError.InExpected);
assertError('@each $i in {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected);
});
test('@while', function () {
let parser = new SCSSParser();
assertNode('@while $i < 0 { .item-#{$i} { width: 2em * $i; } $i: $i - 2; }', parser, parser._parseRuleSetDeclaration.bind(parser));
assertError('@while {}', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.ExpressionExpected);
assertError('@while $i != 4', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.LeftCurlyExpected);
assertError('@while ($i >= 4) {', parser, parser._parseRuleSetDeclaration.bind(parser), ParseError.RightCurlyExpected);
});
test('@mixin', function () {
let parser = new SCSSParser();
assertNode('@mixin large-text { font: { family: Arial; size: 20px; } color: #ff0000; }', parser, parser._parseStylesheet.bind(parser));
assertNode('@mixin sexy-border($color, $width: 1in) { color: black; }', parser, parser._parseStylesheet.bind(parser));
assertNode('@mixin box-shadow($shadows...) { -moz-box-shadow: $shadows; }', parser, parser._parseStylesheet.bind(parser));
assertNode('@mixin apply-to-ie6-only { * html { @content; } }', parser, parser._parseStylesheet.bind(parser));
assertNode('@mixin #{foo}($color){}', parser, parser._parseStylesheet.bind(parser));
assertNode('@mixin foo ($i:4) { size: $i; @include wee ($i - 1); }', parser, parser._parseStylesheet.bind(parser));
assertError('@mixin $1 {}', parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected);
assertError('@mixin foo() i {}', parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected);
assertError('@mixin foo(1) {}', parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected);
assertError('@mixin foo($color = 9) {}', parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected);
assertError('@mixin foo($color)', parser, parser._parseStylesheet.bind(parser), ParseError.LeftCurlyExpected);
assertError('@mixin foo($color){', parser, parser._parseStylesheet.bind(parser), ParseError.RightCurlyExpected);
assertError('@mixin foo($color,){', parser, parser._parseStylesheet.bind(parser), ParseError.VariableNameExpected);
});
test('@include', function () {
let parser = new SCSSParser();
assertNode('p { @include sexy-border(blue); }', parser, parser._parseStylesheet.bind(parser));
assertNode('.shadows { @include box-shadow(0px 4px 5px #666, 2px 6px 10px #999); }', parser, parser._parseStylesheet.bind(parser));
assertNode('$values: #ff0000, #00ff00, #0000ff; .primary { @include colors($values...); }', parser, parser._parseStylesheet.bind(parser));
assertNode('p { @include apply-to-ie6-only { #logo { background-image: url(/logo.gif); } } }', parser, parser._parseStylesheet.bind(parser));
assertError('p { @include sexy-border blue', parser, parser._parseStylesheet.bind(parser), ParseError.SemiColonExpected);
assertError('p { @include sexy-border($values blue', parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected);
assertError('p { @include }', parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected);
assertError('p { @include foo($values }', parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected);
assertError('p { @include foo($values, }', parser, parser._parseStylesheet.bind(parser), ParseError.ExpressionExpected);
});
test('@function', function () {
let parser = new SCSSParser();
assertNode('@function grid-width($n) { @return $n * $grid-width + ($n - 1) * $gutter-width; }', parser, parser._parseStylesheet.bind(parser));
assertNode('@function grid-width($n: 1, $e) { @return 0; }', parser, parser._parseStylesheet.bind(parser));
assertNode('@function foo($total, $a) { @for $i from 0 to $total { } @return $grid; }', parser, parser._parseStylesheet.bind(parser));
assertNode('@function foo() { @if (unit($a) == "%") and ($i == ($total - 1)) { @return 0; } @return 1; }', parser, parser._parseStylesheet.bind(parser));
assertNode('@function is-even($int) { @if $int%2 == 0 { @return true; } @return false }', parser, parser._parseStylesheet.bind(parser));
assertNode('@function bar ($i) { @if $i > 0 { @return $i * bar($i - 1); } @return 1; }', parser, parser._parseStylesheet.bind(parser));
assertError('@function foo {} ', parser, parser._parseStylesheet.bind(parser), ParseError.LeftParenthesisExpected);
assertError('@function {} ', parser, parser._parseStylesheet.bind(parser), ParseError.IdentifierExpected);
assertError('@function foo($a $b) {} ', parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected);
assertError('@function foo($a {} ', parser, parser._parseStylesheet.bind(parser), ParseError.RightParenthesisExpected);
assertError('@function foo($a...) { @return; }', parser, parser._parseStylesheet.bind(parser), ParseError.ExpressionExpected);
assertError('@function foo($a,) {} ', parser, parser._parseStylesheet.bind(parser), ParseError.VariableNameExpected);
assertError('@function foo($a:) {} ', parser, parser._parseStylesheet.bind(parser), ParseError.VariableValueExpected);
});
test('Ruleset', function () {
let parser = new SCSSParser();
assertNode('.selector { prop: erty $let 1px; }', 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('Nested Ruleset', function () {
let parser = new SCSSParser();
assertNode('.class1 { $let: 1; .class { $let: 2; three: $let; let: 3; } one: $let; }', parser, parser._parseRuleset.bind(parser));
assertNode('.class1 { > .class2 { & > .class4 { rule1: v1; } } }', parser, parser._parseRuleset.bind(parser));
});
test('Selector Interpolation', function () {
let parser = new SCSSParser();
assertNode('.#{$name} { }', parser, parser._parseRuleset.bind(parser));
assertNode('p.#{$name} { #{$attr}-color: blue; }', parser, parser._parseRuleset.bind(parser));
assertNode('sans-#{serif} { a-#{1 + 2}-color-#{$attr}: blue; }', parser, parser._parseRuleset.bind(parser));
assertNode('##{f} .#{f} #{f}:#{f} { }', parser, parser._parseRuleset.bind(parser));
});
test('Parent Selector', function () {
let parser = new SCSSParser();
assertNode('&:hover', parser, parser._parseSimpleSelector.bind(parser));
assertNode('&.float', parser, parser._parseSimpleSelector.bind(parser));
assertNode('&-bar', parser, parser._parseSimpleSelector.bind(parser));
assertNode('&&', parser, parser._parseSimpleSelector.bind(parser));
});
test('Selector Placeholder', function () {
let parser = new SCSSParser();
assertNode('%hover', parser, parser._parseSimpleSelector.bind(parser));
assertNode('a%float', parser, parser._parseSimpleSelector.bind(parser));
});
});

View File

@@ -1,77 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 {SCSSParser} from '../../parser/scssParser';
import {SCSSCompletion} from '../../services/scssCompletion';
import {TextDocument, Position} from 'vscode-languageserver';
import {assertCompletion, ItemDescription} from '../css/completion.test';
suite('SCSS - 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 SCSSCompletion();
let document = TextDocument.create('test://test/test.scss', 'scss', 0, value);
let position = Position.create(0, idx);
let jsonDoc = new SCSSParser().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('$i: 0; body { width: ', 'width: ', {
items: [
{ label: '$i' }
]
}),
testCompletionFor('@for $i from 1 through 3 { .item-#{$i} { width: 2em * $i; } }', '.item-#{', {
items: [
{ label: '$i' }
]
}),
testCompletionFor('.foo { background-color: d', 'background-color: d', {
items: [
{ label: 'darken' },
{ label: 'desaturate' }
]
}),
testCompletionFor('@function foo($x, $y) { @return $x + $y; } .foo { background-color: f', 'background-color: f', {
items: [
{ label: 'foo' }
]
}),
testCompletionFor('.foo { di span { } ', 'di', {
items: [
{ label: 'display' },
{ label: 'div' }
]
}),
testCompletionFor('.foo { .', '{ .', {
items: [
{ label: '.foo' }
]
}),
// issue #250
testCompletionFor('.foo { display: block;', 'block;', {
count: 0
}),
]).then(() => testDone(), (error) => testDone(error));
});
});

View File

@@ -1,68 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 {SCSSParser} from '../../parser/scssParser';
import * as nodes from '../../parser/cssNodes';
import {assertSymbolsInScope, assertScopesAndSymbols, assertHighlights} from '../css/navigation.test';
suite('SCSS - Symbols', () => {
test('symbols in scopes', function() {
var p = new SCSSParser();
assertSymbolsInScope(p, '$var: iable;', 0, { name:'$var', type:nodes.ReferenceType.Variable });
assertSymbolsInScope(p, '$var: iable;', 11, { name:'$var', type:nodes.ReferenceType.Variable });
assertSymbolsInScope(p, '$var: iable; .class { $color: blue; }', 11, { name:'$var', type:nodes.ReferenceType.Variable }, { name:'.class', type:nodes.ReferenceType.Rule });
assertSymbolsInScope(p, '$var: iable; .class { $color: blue; }', 22, { name:'$color', type:nodes.ReferenceType.Variable });
assertSymbolsInScope(p, '$var: iable; .class { $color: blue; }', 36, { name:'$color', type:nodes.ReferenceType.Variable });
assertSymbolsInScope(p, '@namespace "x"; @mixin mix() {}', 0, { name:'mix', type:nodes.ReferenceType.Mixin });
assertSymbolsInScope(p, '@mixin mix { @mixin nested() {} }', 12, { name:'nested', type:nodes.ReferenceType.Mixin });
assertSymbolsInScope(p, '@mixin mix () { @mixin nested() {} }', 13);
});
test('scopes and symbols', function() {
var p = new SCSSParser();
assertScopesAndSymbols(p, '$var1: 1; $var2: 2; .foo { $var3: 3; }', '$var1,$var2,.foo,[$var3]');
assertScopesAndSymbols(p, '@mixin mixin1 { $var0: 1} @mixin mixin2($var1) { $var3: 3 }', 'mixin1,mixin2,[$var0],[$var1,$var3]');
assertScopesAndSymbols(p, 'a b { $var0: 1; c { d { } } }', '[$var0,c,[d,[]]]');
assertScopesAndSymbols(p, '@function a($p1: 1, $p2: 2) { $v1: 3; @return $v1; }', 'a,[$p1,$p2,$v1]');
assertScopesAndSymbols(p, '$var1: 3; @if $var1 == 2 { $var2: 1; } @else { $var2: 2; $var3: 2;} ', '$var1,[$var2],[$var2,$var3]');
assertScopesAndSymbols(p, '@if $var1 == 2 { $var2: 1; } @else if $var1 == 2 { $var3: 2; } @else { $var3: 2; } ', '[$var2],[$var3],[$var3]');
assertScopesAndSymbols(p, '$var1: 3; @while $var1 < 2 { #rule { a: b; } }', '$var1,[#rule,[]]');
assertScopesAndSymbols(p, '$i:0; @each $name in f1, f2, f3 { $i:$i+1; }', '$i,[$name,$i]');
assertScopesAndSymbols(p, '$i:0; @for $x from $i to 5 { }', '$i,[$x]');
});
test('mark highlights', function(testDone) {
var p = new SCSSParser();
Promise.all([
assertHighlights(p, '$var1: 1; $var2: /**/$var1;', '$var1', 2, 1),
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; @extend /**/.r1;}', '/**/', 2, 1, '.r1'),
assertHighlights(p, '/**/%r1 { r1: 1em; } r2 { r1: 2em; @extend %r1;}', '/**/', 2, 1, '%r1'),
assertHighlights(p, '@mixin r1 { r1: $p1; } r2 { r2: 2em; @include /**/r1; }', '/**/', 2, 1, 'r1'),
assertHighlights(p, '@mixin r1($p1) { r1: $p1; } r2 { r2: 2em; @include /**/r1(2px); }', '/**/', 2, 1, 'r1'),
assertHighlights(p, '$p1: 1; @mixin r1($p1: $p1) { r1: $p1; } r2 { r2: 2em; @include /**/r1; }', '/**/', 2, 1, 'r1'),
assertHighlights(p, '/**/$p1: 1; @mixin r1($p1: $p1) { r1: $p1; }', '/**/', 2, 1, '$p1'),
assertHighlights(p, '$p1 : 1; @mixin r1($p1) { r1: /**/$p1; }', '/**/', 2, 1, '$p1'),
assertHighlights(p, '/**/$p1 : 1; @mixin r1($p1) { r1: $p1; }', '/**/', 1, 1, '$p1'),
assertHighlights(p, '$p1 : 1; @mixin r1(/**/$p1) { r1: $p1; }', '/**/', 2, 1, '$p1'),
assertHighlights(p, '$p1 : 1; @function r1($p1, $p2: /**/$p1) { @return $p1 + $p1 + $p2; }', '/**/', 2, 1, '$p1'),
assertHighlights(p, '$p1 : 1; @function r1($p1, /**/$p2: $p1) { @return $p1 + $p2 + $p2; }', '/**/', 3, 1, '$p2'),
assertHighlights(p, '@function r1($p1, $p2) { @return $p1 + $p2; } @function r2() { @return /**/r1(1, 2); }', '/**/', 2, 1, 'r1'),
assertHighlights(p, '@function /**/r1($p1, $p2) { @return $p1 + $p2; } @function r2() { @return r1(1, 2); } p { x: r2(); }', '/**/', 2, 1, 'r1'),
assertHighlights(p, '@function r1($p1, $p2) { @return $p1 + $p2; } @function r2() { @return r1(/**/$p1 : 1, $p2 : 2); } p { x: r2(); }', '/**/', 3, 1, '$p1'),
assertHighlights(p, '@mixin /*here*/foo { display: inline } foo { @include foo; }', '/*here*/', 2, 1, 'foo'),
assertHighlights(p, '@mixin foo { display: inline } foo { @include /*here*/foo; }', '/*here*/', 2, 1, 'foo'),
assertHighlights(p, '@mixin foo { display: inline } /*here*/foo { @include foo; }', '/*here*/', 1, 1, 'foo'),
assertHighlights(p, '@function /*here*/foo($i) { @return $i*$i; } #foo { width: foo(2); }', '/*here*/', 2, 1, 'foo'),
assertHighlights(p, '@function foo($i) { @return $i*$i; } #foo { width: /*here*/foo(2); }', '/*here*/', 2, 1, 'foo')
]).then(() => testDone(), (error) => testDone(error));
});
});

View File

@@ -1,34 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 {SCSSParser} from '../../parser/scssParser';
import {parseSelector} from '../css/selectorPrinting.test';
suite('SCSS - Selector Printing', () => {
test('nested selector', function () {
let p = new SCSSParser();
parseSelector(p, 'o1 { e1 { } }', 'e1', '{o1{…{e1}}}');
parseSelector(p, 'o1 { e1.div { } }', 'e1', '{o1{…{e1[class=div]}}}');
parseSelector(p, 'o1 o2 { e1 { } }', 'e1', '{o1{…{o2{…{e1}}}}}');
parseSelector(p, 'o1, o2 { e1 { } }', 'e1', '{o1{…{e1}}}');
parseSelector(p, 'o1 { @if $a { e1 { } } }', 'e1', '{o1{…{e1}}}');
parseSelector(p, 'o1 { @mixin a { e1 { } } }', 'e1', '{e1}');
parseSelector(p, 'o1 { @mixin a { e1 { } } }', 'e1', '{e1}');
});
test('referencing selector', function () {
let p = new SCSSParser();
parseSelector(p, 'o1 { &:hover { }}', '&', '{o1[:hover=]}');
parseSelector(p, 'o1 { &:hover & { }}', '&', '{o1[:hover=]{…{o1}}}');
});
test('placeholders', function () {
let p = new SCSSParser();
parseSelector(p, '%o1 { e1 { } }', 'e1', '{%o1{…{e1}}}');
});
});

View File

@@ -1,23 +0,0 @@
/*---------------------------------------------------------------------------------------------
* 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 {TextDocument, TextEdit} from 'vscode-languageserver';
import * as assert from 'assert';
export function applyEdits(document: TextDocument, edits: TextEdit[]): string {
let text = document.getText();
let sortedEdits = edits.sort((a, b) => document.offsetAt(b.range.start) - document.offsetAt(a.range.start));
let lastOffset = text.length;
sortedEdits.forEach(e => {
let startOffset = document.offsetAt(e.range.start);
let endOffset = document.offsetAt(e.range.end);
assert.ok(startOffset <= endOffset);
assert.ok(endOffset <= lastOffset);
text = text.substring(0, startOffset) + e.newText + text.substring(endOffset, text.length);
lastOffset = startOffset;
});
return text;
}