Hello Code

This commit is contained in:
Erich Gamma
2015-11-13 14:39:38 +01:00
commit 8f35cc4768
1897 changed files with 704173 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,382 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/* global __dirname */
var fs = require('fs');
var path = require('path');
var xml2js = require('xml2js');
var os = require('os');
var util = require('util');
// keep in sync with data from language facts
var colors = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgrey: '#a9a9a9',
darkgreen: '#006400',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkslategrey: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
grey: '#808080',
green: '#008000',
greenyellow: '#adff2f',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgrey: '#d3d3d3',
lightgreen: '#90ee90',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370d8',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#d87093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
red: '#ff0000',
rebeccapurple: '#663399',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
slategrey: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd32'
};
var otherColors = {
"ActiveBorder": "Active window border.",
"ActiveCaption": "Active window caption.",
"AppWorkspace": "Background color of multiple document interface.",
"Background": "Desktop background.",
"ButtonFace": "The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.",
"ButtonHighlight": "The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
"ButtonShadow": "The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.",
"ButtonText": "Text on push buttons.",
"CaptionText": "Text in caption, size box, and scrollbar arrow box.",
"currentColor": "The value of the 'color' property. The computed value of the 'currentColor' keyword is the computed value of the 'color' property. If the 'currentColor' keyword is set on the 'color' property itself, it is treated as 'color:inherit' at parse time.",
"GrayText": "Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.",
"Highlight": "Item(s) selected in a control.",
"HighlightText": "Text of item(s) selected in a control.",
"InactiveBorder": "Inactive window border.",
"InactiveCaption": "Inactive window caption.",
"InactiveCaptionText": "Color of text in an inactive caption.",
"InfoBackground": "Background color for tooltip controls.",
"InfoText": "Text color for tooltip controls.",
"Menu": "Menu background.",
"MenuText": "Text in menus.",
"Scrollbar": "Scroll bar gray area.",
"ThreeDDarkShadow": "The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
"ThreeDFace": "The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
"ThreeDHighlight": "The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
"ThreeDLightShadow": "The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
"ThreeDShadow": "The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.",
"transparent": "Fully transparent. This keyword can be considered a shorthand for rgba(0,0,0,0) which is its computed value.",
"Window": "Window background.",
"WindowFrame": "Window frame.",
"WindowText": "Text in windows.",
"none": "",
//ignore these
"-webkit-activelink": "",
"-webkit-focus-ring-color": '',
"-webkit-link": '',
"-webkit-text": ''
};
function clone(obj) {
var copy = {};
for (var i in obj) {
copy[i] = obj[i];
}
return copy;
}
function getProperties(obj) {
var res = [];
for (var i in obj) {
res.push(i);
}
return res;
}
function getValues(valArr, restriction, ruleName) {
if (!Array.isArray(valArr)) {
return [];
}
var vals = valArr.map(function (v) {
return {
name: v.$.name,
desc: v.desc,
browsers: v.$.browsers !== 'all' ? v.$.browsers : void 0
};
}).filter(function (v) {
if (v.browsers === 'none') {
return false;
}
return true;
});
if (restriction.indexOf('color') !== -1) {
var colorsCopy = clone(colors);
var otherColorsCopy = clone(otherColors);
var moreColors = {};
vals = vals.filter(function (v) {
if (typeof colorsCopy[v.name] === 'string') {
delete colorsCopy[v.name];
return false;
}
if (typeof otherColorsCopy[v.name] === 'string') {
delete otherColorsCopy[v.name];
return false;
}
moreColors[v.name] = v.desc;
return true;
});
var notCovered = [];
for (var i in colorsCopy) {
notCovered.push(i);
}
for (var i in otherColorsCopy) {
notCovered.push(i);
}
if (notCovered.length > 0) {
console.log('***' + ruleName + ' uncovered: ' + notCovered.length); // + ' - ' + JSON.stringify(notCovered));
}
if (restriction === 'color') {
var properties = getProperties(moreColors);
console.log('---' + ruleName + ' others : ' + properties.length); // + ' - ' + JSON.stringify(properties));
}
}
return vals;
}
function internalizeDescriptions(entries) {
var descriptions = {};
var conflicts = {};
entries.forEach(function (e) {
if (e.values) {
e.values.forEach(function (d) {
if (!d.desc) {
conflicts[d.name] = true;
return;
}
var existing = descriptions[d.name];
if (existing) {
if (existing !== d.desc) {
conflicts[d.name] = true;
}
}
descriptions[d.name] = d.desc;
});
}
});
entries.forEach(function (e) {
if (e.values) {
e.values.forEach(function (d) {
if (!conflicts[d.name]) {
delete d.desc;
} else {
delete descriptions[d.name];
}
});
}
});
return descriptions;
}
function toSource(object, keyName) {
if (!object.css[keyName]) {
return [];
}
var result = [];
var entryArr = object.css[keyName].entry;
entryArr.forEach(function (e) {
if (e.$.browsers === 'none') {
return;
}
var data = {
name: e.$.name,
desc: e.desc,
browsers: e.$.browsers !== 'all' ? e.$.browsers : void 0
};
if (e.$.restriction) {
data.restriction= e.$.restriction;
}
if (e.values) {
data.values= getValues(e.values.value, data.restriction || '', data.name);
}
result.push(data);
});
return result;
}
var parser = new xml2js.Parser({explicitArray : false});
var schemaFileName= 'css-schema.xml';
fs.readFile(path.resolve(__dirname, schemaFileName), function(err, data) {
parser.parseString(data, function (err, result) {
//console.log(util.inspect(result, {depth: null})); //Work
var atdirectives = toSource(result, 'atDirectives');
var pseudoclasses = toSource(result, 'pseudoClasses');
var pseudoelements = toSource(result, 'pseudoElements');
var properties = toSource(result, 'properties');
var descriptions = internalizeDescriptions([].concat(atdirectives, pseudoclasses, pseudoelements, properties));
var resultObject = {
css: {
atdirectives: atdirectives,
pseudoclasses: pseudoclasses,
pseudoelements: pseudoelements,
properties: properties
}
};
var outputBegin = [
'/*---------------------------------------------------------',
' * Copyright (C) Microsoft Corporation. All rights reserved.',
' *---------------------------------------------------------*/',
'// file generated from ' + schemaFileName + ' using css-exclude_generate_browserjs.js',
'define(["require", "exports"], function(require, exports) { ',
'',
'exports.data =' + JSON.stringify(resultObject, null, '\t') + ';',
'',
'exports.descriptions = ' + JSON.stringify(descriptions, null, '\t') + ';',
'});'
];
var outputPath = path.resolve(__dirname, '../services/browsers.js');
console.log('Writing to: ' + outputPath);
var content = outputBegin.join(os.EOL);
fs.writeFileSync(outputPath, content);
console.log('Done');
});
});

View File

@@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.monaco-editor .monaco-editor-hover .css-selector-hover {
color: gray;
font-size: 12px;
}
.monaco-editor .monaco-editor-hover .css-selector-hover ul {
list-style-type: none;
padding: 0;
margin: 0;
margin-left: 1.5em;
}
.monaco-editor .monaco-editor-hover .css-selector-hover > ul {
margin-left: 0;
}
/*normal theme*/
.monaco-editor .monaco-editor-hover .css-selector-hover .name {
color: black;
font-weight: bold;
}
.monaco-editor .monaco-editor-hover .css-selector-hover .key {
color: red;
}
.monaco-editor .monaco-editor-hover .css-selector-hover .value {
color: blue;
}
/*dark theme*/
.monaco-editor.vs-dark .monaco-editor-hover .css-selector-hover .name {
color: #569CD6;
}
.monaco-editor.vs-dark .monaco-editor-hover .css-selector-hover .key {
color: #9CDCFE;
}
.monaco-editor.vs-dark .monaco-editor-hover .css-selector-hover .value {
color: #CE9178;
}

View File

@@ -0,0 +1,32 @@
/*---------------------------------------------------------------------------------------------
* 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 'vs/css!vs/languages/css/common/css-hover';
import nls = require('vs/nls');
import Platform = require('vs/platform/platform');
import modesExtensions = require('vs/editor/common/modes/modesRegistry');
import ConfigurationRegistry = require('vs/platform/configuration/common/configurationRegistry');
import lintRules = require('vs/languages/css/common/services/lintRules');
modesExtensions.registerMode({
id: 'css',
extensions: ['.css'],
aliases: ['CSS', 'css'],
mimetypes: ['text/css'],
moduleId: 'vs/languages/css/common/css',
ctorName: 'CSSMode'
});
var configurationRegistry = <ConfigurationRegistry.IConfigurationRegistry>Platform.Registry.as(ConfigurationRegistry.Extensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'css',
'order': 20,
'title': nls.localize('cssConfigurationTitle', "CSS configuration"),
'allOf': [{
'title': nls.localize('lint', "Controls CSS validation and problem severities."),
'properties': lintRules.getConfigurationProperties('css')
}]
});

View File

@@ -0,0 +1,392 @@
/*---------------------------------------------------------------------------------------------
* 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 WinJS = require('vs/base/common/winjs.base');
import supports = require('vs/editor/common/modes/supports');
import objects = require('vs/base/common/objects');
import Network = require('vs/base/common/network');
import EditorCommon = require('vs/editor/common/editorCommon');
import Modes = require('vs/editor/common/modes');
import {OneWorkerAttr} from 'vs/platform/thread/common/threadService';
import cssWorker = require('vs/languages/css/common/cssWorker');
import {AbstractMode} from 'vs/editor/common/modes/abstractMode';
import {AbstractState} from 'vs/editor/common/modes/abstractState';
import {AsyncDescriptor2, createAsyncDescriptor2} from 'vs/platform/instantiation/common/descriptors';
import {IMarker} from 'vs/platform/markers/common/markers';
import {OnEnterSupport} from 'vs/editor/common/modes/supports/onEnter';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IThreadService} from 'vs/platform/thread/common/thread';
export enum States {
Selector,
Rule,
Value,
ValuePostUrl,
ValueInUrlFunction,
Unit,
Meta,
MetaPostUrl,
MetaInUrlFunction,
}
var identRegEx = /^-?-?([a-zA-Z]|(\\(([0-9a-fA-F]{1,6}\s?)|[^[0-9a-fA-F])))([\w\-]|(\\(([0-9a-fA-F]{1,6}\s?)|[^[0-9a-fA-F])))*/;
export class State extends AbstractState {
public kind:States;
public inComment:boolean;
public quote:string;
public inMeta:boolean;
public metaBraceCount: number;
constructor(mode:Modes.IMode, kind:States, inComment:boolean, quote:string, inMeta:boolean, metaBraceCount: number) {
super(mode);
this.kind = kind;
this.inComment = inComment;
this.quote = quote;
this.inMeta = inMeta;
this.metaBraceCount = metaBraceCount;
}
private nextState(next:States, token:Modes.ITokenizationResult):Modes.ITokenizationResult {
this.kind = next;
return token;
}
public makeClone():State {
return new State(this.getMode(), this.kind, this.inComment, this.quote, this.inMeta, this.metaBraceCount);
}
public equals(other:Modes.IState):boolean {
return super.equals(other) && objects.equals(this, other);
}
private tokenizeInComment(stream:Modes.IStream):Modes.ITokenizationResult {
if (/\*\/$/.test(stream.advanceUntilString('*/', true))) {
this.inComment = false;
}
return { type:'comment.css' };
}
private tokenizeInString(stream:Modes.IStream):Modes.ITokenizationResult {
var ch:string, afterBackslash = false, quote = this.quote;
while (!stream.eos()) {
ch = stream.next();
if (afterBackslash) {
// Ignore any character after \
afterBackslash = false;
} else if (ch === '\\') {
// Mark next character for ignoring
afterBackslash = true;
} else if (ch === quote) {
// Matching quote found
this.quote = null;
break;
}
}
return { type:'string.css' };
}
private consumeIdent(stream:Modes.IStream) {
stream.goBack(1);
if (stream.advanceIfRegExp2(identRegEx)) {
return true;
}
stream.advance(1);
return false;
}
public tokenize(stream:Modes.IStream):Modes.ITokenizationResult {
if (this.inComment) {
return this.tokenizeInComment(stream);
}
if (this.quote !== null) {
return this.tokenizeInString(stream);
}
if (stream.skipWhitespace2()) {
return { type:'' };
}
if (stream.advanceIfString2('/*')) {
this.inComment = true;
return this.tokenizeInComment(stream);
}
if (stream.advanceIfString2('\'')) {
this.quote = '\'';
return this.tokenizeInString(stream);
}
if (stream.advanceIfString2('\"')) {
this.quote = '\"';
return this.tokenizeInString(stream);
}
var ch = stream.next();
// These states can immediately transition to States.Value or Meta (without consuming ch), that's why they're handled above the switch stmt.
switch (this.kind) {
case States.ValuePostUrl:
if (ch === '(') {
return this.nextState(States.ValueInUrlFunction, { type:'punctuation.parenthesis.css', bracket: Modes.Bracket.Open });
}
this.kind = States.Value;
break;
case States.MetaPostUrl:
if (ch === '(') {
return this.nextState(States.MetaInUrlFunction, { type:'punctuation.parenthesis.css', bracket: Modes.Bracket.Open });
}
this.kind = States.Meta;
break;
case States.ValueInUrlFunction:
case States.MetaInUrlFunction:
// This state is after 'url(' was encountered in the value
if (ch !== ')') {
stream.advanceIfRegExp2(/^[^\)]*/);
return { type: 'string.css' };
}
this.kind = (this.kind === States.ValueInUrlFunction) ? States.Value : States.Meta;
break;
}
switch (this.kind) {
case States.Selector:
if (ch === '{') {
return this.nextState(States.Rule, { type:'punctuation.bracket.css', bracket: Modes.Bracket.Open });
}
if (ch === '(' || ch === ')') {
return { type:'punctuation.parenthesis.css', bracket: ch === '(' ? Modes.Bracket.Open : Modes.Bracket.Close };
}
if (ch === '@' && !this.inMeta) { //@import, @media, @key-word-animation
stream.advanceIfRegExp2(identRegEx);
return this.nextState(States.Meta, { type:'keyword.css' });
}
if (ch === '}' && this.inMeta) { //@import, @media, @key-word-animation
this.inMeta = false;
return this.nextState(States.Selector, { type:'punctuation.bracket.css', bracket: Modes.Bracket.Close });
}
if (/[\*\(\)\[\]\+>=\~\|;]/.test(ch)) {
return { type:'punctuation.css' };
}
if (ch === '#') {
stream.advanceIfRegExp2(identRegEx);
return { type:'entity.other.attribute-name.id.css' };
}
if (ch === '.') {
stream.advanceIfRegExp2(identRegEx);
return { type:'entity.other.attribute-name.class.css' };
}
this.consumeIdent(stream);
return { type:'entity.name.tag.css' };
case States.Meta:
if (ch === '{') {
var nextState = States.Rule;
if (this.inMeta) {
nextState = States.Selector;
}
return this.nextState(nextState, { type:'punctuation.bracket.css', bracket: Modes.Bracket.Open });
}
if (ch === '(' || ch === ')') {
return { type:'punctuation.parenthesis.css', bracket: ch === '(' ? Modes.Bracket.Open : Modes.Bracket.Close };
}
if (ch === ';') {
if (this.metaBraceCount === 0) {
this.inMeta = false;
}
return this.nextState(States.Selector, { type:'punctuation.css' });
}
if ((ch === 'u' || ch === 'U') && stream.advanceIfStringCaseInsensitive2('rl')) {
stream.advanceIfStringCaseInsensitive2('-prefix'); // support 'url-prefix' (part of @-mox-document)
return this.nextState(States.MetaPostUrl, { type:'meta.property-value.css' });
}
if (/[\*\(\)\[\]\+>=\~\|]/.test(ch)) {
return { type:'punctuation.css' };
}
this.inMeta = true;
this.consumeIdent(stream);
return { type:'meta.property-value.css' };
case States.Rule:
if (ch === '}') {
return this.nextState(States.Selector, { type:'punctuation.bracket.css', bracket: Modes.Bracket.Close });
}
if (ch === ':') {
return this.nextState(States.Value, { type:'punctuation.css' });
}
if (ch === '(' || ch === ')') {
return { type:'punctuation.parenthesis.css', bracket: ch === '(' ? Modes.Bracket.Open : Modes.Bracket.Close };
}
this.consumeIdent(stream);
return { type:'support.type.property-name.css' };
case States.Value:
if (ch === '}') {
return this.nextState(States.Selector, { type:'punctuation.bracket.css', bracket: Modes.Bracket.Close });
}
if (ch === ';') {
return this.nextState(States.Rule, { type:'punctuation.css' });
}
if ((ch === 'u' || ch === 'U') && stream.advanceIfStringCaseInsensitive2('rl')) {
return this.nextState(States.ValuePostUrl, { type:'meta.property-value.css' });
}
if (ch === '(' || ch === ')') {
return { type:'punctuation.parenthesis.css', bracket: ch === '(' ? Modes.Bracket.Open : Modes.Bracket.Close };
}
if (ch === ',') {
return { type:'punctuation.css' };
}
if (ch === '#') {
stream.advanceIfRegExp2(/^[\w]*/);
return { type:'meta.property-value.hex.css' };
}
if (/\d/.test(ch) || (/-|\+/.test(ch) && !stream.eos() && /\d/.test(stream.peek()))) {
stream.advanceIfRegExp2(/^[\d\.]*/);
return this.nextState(States.Unit, { type:'meta.property-value.numeric.css' });
}
if (ch === '!') {
return { type:'meta.property-value.keyword.css' }; // !
}
if ((ch === 'i' || ch === 'I') && stream.advanceIfStringCaseInsensitive2('mportant')) {
return { type:'meta.property-value.keyword.css' }; // important
}
if (this.consumeIdent(stream)) {
return { type:'meta.property-value.css' };
}
break;
case States.Unit:
// css units - see: http://www.w3.org/TR/css3-values/#font-relative-lengths
stream.goBack(1);
if(stream.advanceIfRegExp2(/^(em|ex|ch|rem|vw|vh|vm|cm|mm|in|px|pt|pc|deg|grad|rad|turn|s|ms|Hz|kHz|%)/)) {
return { type:'meta.property-value.unit.css' };
}
// no unit, back to value state
this.nextState(States.Value, null);
return this.tokenize(stream);
}
return { type:'' };
}
}
export class CSSMode extends AbstractMode<cssWorker.CSSWorker> {
public tokenizationSupport: Modes.ITokenizationSupport;
public electricCharacterSupport: Modes.IElectricCharacterSupport;
public characterPairSupport: Modes.ICharacterPairSupport;
public referenceSupport: Modes.IReferenceSupport;
public logicalSelectionSupport: Modes.ILogicalSelectionSupport;
public extraInfoSupport:Modes.IExtraInfoSupport;
public outlineSupport: Modes.IOutlineSupport;
public declarationSupport: Modes.IDeclarationSupport;
public suggestSupport: Modes.ISuggestSupport;
public quickFixSupport: Modes.IQuickFixSupport;
public onEnterSupport: Modes.IOnEnterSupport;
constructor(
descriptor:Modes.IModeDescriptor,
@IInstantiationService instantiationService: IInstantiationService,
@IThreadService threadService: IThreadService
) {
super(descriptor, instantiationService, threadService);
this.tokenizationSupport = new supports.TokenizationSupport(this, {
getInitialState: () => new State(this, States.Selector, false, null, false, 0)
}, false, false);
this.electricCharacterSupport = new supports.BracketElectricCharacterSupport(this, { brackets: [
{ tokenType:'punctuation.bracket.css', open: '{', close: '}', isElectric: true }
] });
this.extraInfoSupport = this;
this.referenceSupport = new supports.ReferenceSupport(this, {
tokens: ['support.type.property-name.css', 'meta.property-value.css', 'entity.name.tag.css'],
findReferences: (resource, position, /*unused*/includeDeclaration) => this.findReferences(resource, position)});
this.logicalSelectionSupport = this;
this.outlineSupport = this;
this.declarationSupport = new supports.DeclarationSupport(this, {
tokens: ['meta.property-value.css'],
findDeclaration: (resource, position) => this.findDeclaration(resource, position)});
this.characterPairSupport = new supports.CharacterPairSupport(this, {
autoClosingPairs:
[ { open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '"', close: '"', notIn: ['string'] },
{ open: '\'', close: '\'', notIn: ['string'] }
]});
this.suggestSupport = new supports.SuggestSupport(this, {
triggerCharacters: [' ', ':'],
excludeTokens: ['comment.css', 'string.css'],
suggest: (resource, position) => this.suggest(resource, position)});
this.onEnterSupport = new OnEnterSupport(this.getId(), {
brackets: [
{ open: '(', close: ')' },
{ open: '{', close: '}' },
{ open: '[', close: ']' }
]
});
this.quickFixSupport = this;
}
protected _getWorkerDescriptor(): AsyncDescriptor2<Modes.IMode, Modes.IWorkerParticipant[], cssWorker.CSSWorker> {
return createAsyncDescriptor2('vs/languages/css/common/cssWorker', 'CSSWorker');
}
static $findDeclaration = OneWorkerAttr(CSSMode, CSSMode.prototype.findDeclaration);
public findDeclaration(resource:Network.URL, position:EditorCommon.IPosition):WinJS.TPromise<Modes.IReference> {
return this._worker((w) => w.findDeclaration(resource, position));
}
static $computeInfo = OneWorkerAttr(CSSMode, CSSMode.prototype.computeInfo);
public computeInfo(resource:Network.URL, position:EditorCommon.IPosition): WinJS.TPromise<Modes.IComputeExtraInfoResult> {
return this._worker((w) => w.computeInfo(resource, position));
}
static $findReferences = OneWorkerAttr(CSSMode, CSSMode.prototype.findReferences);
public findReferences(resource:Network.URL, position:EditorCommon.IPosition):WinJS.TPromise<Modes.IReference[]> {
return this._worker((w) => w.findReferences(resource, position));
}
static $getRangesToPosition = OneWorkerAttr(CSSMode, CSSMode.prototype.getRangesToPosition);
public getRangesToPosition(resource:Network.URL, position:EditorCommon.IPosition):WinJS.TPromise<Modes.ILogicalSelectionEntry[]> {
return this._worker((w) => w.getRangesToPosition(resource, position));
}
static $getOutline = OneWorkerAttr(CSSMode, CSSMode.prototype.getOutline);
public getOutline(resource:Network.URL):WinJS.TPromise<Modes.IOutlineEntry[]> {
return this._worker((w) => w.getOutline(resource));
}
public getCommentsConfiguration():Modes.ICommentsConfiguration {
return { blockCommentStartToken: '/*', blockCommentEndToken: '*/' };
}
// TODO@Martin: This definition does not work with umlauts for example
public getWordDefinition():RegExp {
return /(#?-?\d*\.\d\w*%?)|((::|[@#.!:])?[\w-?]+%?)|::|[@#.!:]/g;
}
static $findColorDeclarations = OneWorkerAttr(CSSMode, CSSMode.prototype.findColorDeclarations);
public findColorDeclarations(resource:Network.URL):WinJS.TPromise<{range:EditorCommon.IRange; value:string; }[]> {
return this._worker((w) => w.findColorDeclarations(resource));
}
static getQuickFixes = OneWorkerAttr(CSSMode, CSSMode.prototype.getQuickFixes);
public getQuickFixes(resource: Network.URL, marker: IMarker | EditorCommon.IRange): WinJS.TPromise<Modes.IQuickFix[]>{
return this._worker((w) => w.getQuickFixes(resource, marker));
}
static runQuickFixAction = OneWorkerAttr(CSSMode, CSSMode.prototype.runQuickFixAction);
public runQuickFixAction(resource:Network.URL, range:EditorCommon.IRange, id: any):WinJS.TPromise<Modes.IQuickFixResult>{
return this._worker((w) => w.runQuickFixAction(resource, range, id));
}
}

View File

@@ -0,0 +1,439 @@
/*---------------------------------------------------------------------------------------------
* 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 nls = require('vs/nls');
import _severity from 'vs/base/common/severity';
import strings = require('vs/base/common/strings');
import winjs = require('vs/base/common/winjs.base');
import {AbstractModeWorker} from 'vs/editor/common/modes/abstractModeWorker';
import languageService = require('vs/languages/css/common/services/cssLanguageService');
import languageFacts = require('vs/languages/css/common/services/languageFacts');
import occurrences = require('./services/occurrences');
import cssIntellisense = require('vs/languages/css/common/services/intelliSense');
import network = require('vs/base/common/network');
import EditorCommon = require('vs/editor/common/editorCommon');
import Modes = require('vs/editor/common/modes');
import nodes = require('vs/languages/css/common/parser/cssNodes');
import _level = require('vs/languages/css/common/level');
import parser = require('vs/languages/css/common/parser/cssParser');
import selectorPrinting = require('vs/languages/css/common/services/selectorPrinting');
import lint = require('vs/languages/css/common/services/lint');
import lintRules = require('vs/languages/css/common/services/lintRules');
import supports = require('vs/editor/common/modes/supports');
import {IMarker, IMarkerData} from 'vs/platform/markers/common/markers';
import {IMarkerService} from 'vs/platform/markers/common/markers';
import {IResourceService} from 'vs/editor/common/services/resourceService';
export class CSSWorker extends AbstractModeWorker {
public languageService: languageService.ILanguageService;
private validationEnabled : boolean;
private lintSettings : lintRules.IConfigurationSettings;
constructor(mode: Modes.IMode, participants: Modes.IWorkerParticipant[], @IResourceService resourceService: IResourceService,
@IMarkerService markerService: IMarkerService) {
super(mode, participants, resourceService, markerService);
this.languageService = this.createLanguageService(resourceService, mode.getId());
this.lintSettings = {};
this.validationEnabled = true;
}
protected _createInPlaceReplaceSupport(): Modes.IInplaceReplaceSupport {
return new supports.WorkerInplaceReplaceSupport(this.resourceService, this);
}
public createLanguageService(resourceService:IResourceService, modeId:string): languageService.CSSLanguageService {
return new languageService.CSSLanguageService(resourceService, this.createParser.bind(this), modeId);
}
public createParser() : parser.Parser {
return new parser.Parser();
}
/**
* @return true if you want to revalidate your models
*/
_doConfigure(raw:any):winjs.TPromise<boolean> {
if (raw) {
this.validationEnabled = raw.validate;
if (raw.lint) {
this.lintSettings = lintRules.sanitize(raw.lint);
} else {
this.lintSettings = {};
}
return winjs.TPromise.as(true);
}
return winjs.TPromise.as(false);
}
public doValidate(resource: network.URL):void {
if (!this.validationEnabled) {
this.markerService.changeOne(this._getMode().getId(), resource, []);
return;
}
this.languageService.join().then(() => {
var modelMirror = this.resourceService.get(resource),
node = this.languageService.getStylesheet(resource),
entries: nodes.IMarker[] = [];
entries.push.apply(entries, nodes.ParseErrorCollector.entries(node));
entries.push.apply(entries, this.collectLintEntries(node));
var markerData = entries
.filter(entry => entry.getLevel() !== _level.Level.Ignore)
.map(entry => this._createMarkerData(modelMirror, entry));
this.markerService.changeOne(this._getMode().getId(), resource, markerData);
});
}
private _createMarkerData(model: EditorCommon.IMirrorModel, marker: nodes.IMarker): IMarkerData {
var range = model.getRangeFromOffsetAndLength(marker.getOffset(), marker.getLength());
return <IMarkerData> {
code: marker.getRule().id,
message: marker.getMessage(),
severity: marker.getLevel() === _level.Level.Warning ? _severity.Warning : _severity.Error,
startLineNumber: range.startLineNumber,
startColumn: range.startColumn,
endLineNumber: range.endLineNumber,
endColumn: range.endColumn
};
}
public collectLintEntries(stylesheet:nodes.Stylesheet):nodes.IMarker[] {
return lint.LintVisitor.entries(stylesheet, this.lintSettings);
}
public createIntellisense(): cssIntellisense.CSSIntellisense {
return new cssIntellisense.CSSIntellisense();
}
public doSuggest(resource:network.URL, position:EditorCommon.IPosition):winjs.TPromise<Modes.ISuggestions> {
return this.languageService.join().then(() => {
var model = this.resourceService.get(resource);
var result = this.createIntellisense().getCompletionsAtPosition(this.languageService, model, resource, position);
return result;
});
}
public getRangesToPosition(resource:network.URL, position:EditorCommon.IPosition):winjs.TPromise<Modes.ILogicalSelectionEntry[]> {
return this.languageService.join().then(() => {
var model = this.resourceService.get(resource),
offset = model.getOffsetFromPosition(position),
styleSheet = this.languageService.getStylesheet(resource),
path = nodes.getNodePath(styleSheet, offset),
result: Modes.ILogicalSelectionEntry[] = [];
for (var i = 0; i < path.length; i++) {
var node = path[i];
if(node.offset === -1 || node.length === -1) {
continue;
}
if(node.parent && node.parent.offset === node.offset && node.parent.length === node.length) {
continue;
}
result.push({
type: 'node',
range: this._range(node, model)
});
}
return result;
});
}
public getOutline(resource:network.URL):winjs.TPromise<Modes.IOutlineEntry[]> {
return this.languageService.join().then(() => {
var model = this.resourceService.get(resource),
stylesheet = this.languageService.getStylesheet(resource),
result:Modes.IOutlineEntry[] = [];
stylesheet.accept((node) => {
var entry:Modes.IOutlineEntry = {
label: null,
type: 'rule',
range: null,
children: []
};
if(node instanceof nodes.Selector) {
entry.label = node.getText();
} else if(node instanceof nodes.VariableDeclaration) {
entry.label = (<nodes.VariableDeclaration> node).getName();
entry.type = 'variable';
} else if(node instanceof nodes.MixinDeclaration) {
entry.label = (<nodes.MixinDeclaration> node).getName();
entry.type = 'method';
} else if(node instanceof nodes.FunctionDeclaration) {
entry.label = (<nodes.FunctionDeclaration> node).getName();
entry.type = 'function';
} else if(node instanceof nodes.Keyframe) {
entry.label = nls.localize('literal.keyframes', "@keyframes {0}", (<nodes.Keyframe> node).getName());
} else if(node instanceof nodes.FontFace) {
entry.label = nls.localize('literal.fontface', "@font-face");
}
if(entry.label) {
entry.range = this._range(node, model, true);
result.push(entry);
}
return true;
});
return result;
});
}
public computeInfo(resource:network.URL, position:EditorCommon.IPosition): winjs.TPromise<Modes.IComputeExtraInfoResult> {
return this.languageService.join().then(() => {
var model = this.resourceService.get(resource),
offset = model.getOffsetFromPosition(position),
stylesheet = this.languageService.getStylesheet(resource),
nodepath = nodes.getNodePath(stylesheet, offset);
for (var i = 0; i < nodepath.length; i++) {
var node = nodepath[i];
if(node instanceof nodes.Selector) {
return {
htmlContent: [selectorPrinting.selectorToHtml(<nodes.Selector> node)],
range: this._range(node, model)
};
}
if(node instanceof nodes.SimpleSelector) {
return {
htmlContent: [selectorPrinting.simpleSelectorToHtml(<nodes.SimpleSelector> node)],
range: this._range(node, model)
};
}
}
return null;
});
}
public findDeclaration(resource:network.URL, position:EditorCommon.IPosition):winjs.TPromise<Modes.IReference> {
return this.languageService.join().then(() => {
var model = this.resourceService.get(resource),
offset = model.getOffsetFromPosition(position),
node = occurrences.findDeclaration(this.languageService.getStylesheet(resource), offset);
if (!node) {
return null;
}
return <Modes.IReference> {
resource: resource,
range: this._range(node, model, true)
};
});
}
public findOccurrences(resource:network.URL, position:EditorCommon.IPosition, strict?:boolean):winjs.TPromise<Modes.IOccurence[]> {
return this.languageService.join().then(() => {
var model = this.resourceService.get(resource),
offset = model.getOffsetFromPosition(position),
nodes = occurrences.findOccurrences(this.languageService.getStylesheet(resource), offset);
return nodes.map((occurrence) => {
return {
range: this._range(occurrence.node, model),
kind: occurrence.kind
};
});
});
}
public findReferences(resource:network.URL, position:EditorCommon.IPosition):winjs.TPromise<Modes.IReference[]> {
return this.languageService.join().then(() => {
var model = this.resourceService.get(resource),
offset = model.getOffsetFromPosition(position),
nodes = occurrences.findOccurrences(this.languageService.getStylesheet(resource), offset);
return nodes.map((occurrence) => {
return <Modes.IReference> {
resource: model.getAssociatedResource(),
range: this._range(occurrence.node, model)
};
});
});
}
public navigateValueSetFallback(resource:network.URL, range:EditorCommon.IRange, up:boolean):winjs.TPromise<Modes.IInplaceReplaceSupportResult> {
return this.languageService.join().then(() => {
var model = this.resourceService.get(resource);
var offset = model.getOffsetFromPosition({ lineNumber: range.startLineNumber, column: range.startColumn });
var styleSheet = this.languageService.getStylesheet(resource);
var node = nodes.getNodeAtOffset(styleSheet, offset);
if (!node) {
return;
}
var declaration = nodes.getParentDeclaration(node);
if (!declaration) {
return;
}
var entry: languageFacts.IEntry = languageFacts.getProperties()[declaration.getFullPropertyName()];
if (!entry || !entry.values) {
return;
}
var values = entry.values.filter(value => languageFacts.isCommonValue(value)).map(v => v.name);
var isColor = (entry.restrictions.indexOf('color') >= 0);
if (isColor) {
values = values.concat(Object.getOwnPropertyNames(languageFacts.colors), Object.getOwnPropertyNames(languageFacts.colorKeywords));
}
var text = node.getText();
for (var i = 0, len = values.length; i < len; i++) {
if (strings.equalsIgnoreCase(values[i], text)) {
var nextIdx = i;
if(up) {
nextIdx = (i + 1) % len;
} else {
nextIdx = i - 1;
if(nextIdx < 0) {
nextIdx = len - 1;
}
}
var result:Modes.IInplaceReplaceSupportResult = {
value: values[nextIdx],
range: this._range(node, model)
};
return result;
}
}
// if none matches, take the first one
if (values.length > 0) {
var result:Modes.IInplaceReplaceSupportResult = {
value: values[0],
range: this._range(node, model)
};
return result;
}
return null;
});
}
public findColorDeclarations(resource:network.URL):winjs.Promise {
return this.languageService.join().then(() => {
var model = this.resourceService.get(resource),
styleSheet = this.languageService.getStylesheet(resource),
result:{range:EditorCommon.IRange; value:string; }[] = [];
styleSheet.accept((node) => {
if (languageFacts.isColorValue(node)) {
result.push({
range: this._range(node, model),
value: node.getText()
});
}
return true;
});
return result;
});
}
_range(node:{offset:number; length:number;}, model:EditorCommon.IMirrorModel, empty:boolean = false):EditorCommon.IRange {
if(empty) {
var position = model.getPositionFromOffset(node.offset);
return {
startLineNumber: position.lineNumber,
startColumn: position.column,
endLineNumber: position.lineNumber,
endColumn: position.column
};
} else {
return model.getRangeFromOffsetAndLength(node.offset, node.length);
}
}
private getFixesForUnknownProperty(property: nodes.Property) : Modes.IQuickFix[] {
var propertyName = property.getName();
var result: Modes.IQuickFix[] = [];
for (var p in languageFacts.getProperties()) {
var score = strings.difference(propertyName, p);
if (score >= propertyName.length / 2 /*score_lim*/) {
result.push({
label: nls.localize('css.quickfix.rename', "Rename to '{0}'", p),
id: JSON.stringify({ type: 'rename', name: p }),
score: score
});
}
}
// Sort in descending order.
result.sort((a, b) => {
return b.score - a.score;
});
return result.slice(0, 3 /*max_result*/);
}
public getQuickFixes(resource: network.URL, marker: IMarker | EditorCommon.IRange): winjs.TPromise<Modes.IQuickFix[]> {
if ((<IMarker> marker).code !== lintRules.Rules.UnknownProperty.id) {
return winjs.TPromise.as([]);
}
return this.languageService.join().then(() => {
var model = this.resourceService.get(resource),
offset = model.getOffsetFromPosition({ column: marker.startColumn, lineNumber: marker.startLineNumber }),
stylesheet = this.languageService.getStylesheet(resource),
nodepath = nodes.getNodePath(stylesheet, offset);
for (var i = nodepath.length - 1; i >= 0; i--) {
var node = nodepath[i];
if (node instanceof nodes.Declaration) {
var property = (<nodes.Declaration> node).getProperty();
if (property && property.offset === offset && property.length === marker.endColumn - marker.startColumn) {
return this.getFixesForUnknownProperty(property);
}
}
}
return [];
});
}
public runQuickFixAction(resource:network.URL, range:EditorCommon.IRange, id: any):winjs.TPromise<Modes.IQuickFixResult>{
var command = JSON.parse(id);
switch (command.type) {
case 'rename': {
return winjs.TPromise.as({
edits: [{ resource, range, newText: command.name }]
});
}
}
return null;
}
}

View File

@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export enum Level {
Ignore = 1,
Warning = 2,
Error = 4
}
export function toLevel(level: string):Level {
switch (level) {
case 'ignore': return Level.Ignore;
case 'warning': return Level.Warning;
case 'error': return Level.Error;
}
return null;
}

View File

@@ -0,0 +1,49 @@
/*---------------------------------------------------------------------------------------------
* 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 nls = require('vs/nls');
import nodes = require('./cssNodes');
export class CSSIssueType implements nodes.IRule {
id: string;
message: string;
public constructor(id:string, message: string) {
this.id = id;
this.message = message;
}
}
export var ParseError = {
NumberExpected: new CSSIssueType('css-numberexpected', nls.localize('expected.number', "number expected")),
ConditionExpected: new CSSIssueType('css-conditionexpected', nls.localize('expected.condt', "condition expected")),
RuleOrSelectorExpected: new CSSIssueType('css-ruleorselectorexpected', nls.localize('expected.ruleorselector', "at-rule or selector expected")),
DotExpected: new CSSIssueType('css-dotexpected', nls.localize('expected.dot', "dot expected")),
ColonExpected: new CSSIssueType('css-colonexpected', nls.localize('expected.colon', "colon expected")),
SemiColonExpected: new CSSIssueType('css-semicolonexpected', nls.localize('expected.semicolon', "semi-colon expected")),
TermExpected: new CSSIssueType('css-termexpected', nls.localize('expected.term', "term expected")),
ExpressionExpected: new CSSIssueType('css-expressionexpected', nls.localize('expected.expression', "expression expected")),
OperatorExpected: new CSSIssueType('css-operatorexpected', nls.localize('expected.operator', "operator expected")),
IdentifierExpected: new CSSIssueType('css-identifierexpected', nls.localize('expected.ident', "identifier expected")),
PercentageExpected: new CSSIssueType('css-percentageexpected', nls.localize('expected.percentage', "percentage expected")),
URIOrStringExpected: new CSSIssueType('css-uriorstringexpected', nls.localize('expected.uriorstring', "uri or string expected")),
URIExpected: new CSSIssueType('css-uriexpected', nls.localize('expected.uri', "URI expected")),
VariableNameExpected: new CSSIssueType('css-varnameexpected', nls.localize('expected.varname', "variable name expected")),
VariableValueExpected: new CSSIssueType('css-varvalueexpected', nls.localize('expected.varvalue', "variable value expected")),
PropertyValueExpected: new CSSIssueType('css-propertyvalueexpected', nls.localize('expected.propvalue', "property value expected")),
LeftCurlyExpected: new CSSIssueType('css-lcurlyexpected', nls.localize('expected.lcurly', "{ expected")),
RightCurlyExpected: new CSSIssueType('css-rcurlyexpected', nls.localize('expected.rcurly', "} expected")),
LeftSquareBracketExpected: new CSSIssueType('css-rbracketexpected', nls.localize('expected.lsquare', "[ expected")),
RightSquareBracketExpected: new CSSIssueType('css-lbracketexpected', nls.localize('expected.rsquare', "] expected")),
LeftParenthesisExpected: new CSSIssueType('css-lparentexpected', nls.localize('expected.lparen', "( expected")),
RightParenthesisExpected: new CSSIssueType('css-rparentexpected', nls.localize('expected.rparent', ") expected")),
CommaExpected: new CSSIssueType('css-commaexpected', nls.localize('expected.comma', "comma expected")),
PageDirectiveOrDeclarationExpected: new CSSIssueType('css-pagedirordeclexpected', nls.localize('expected.pagedirordecl', "page directive or declaraton expected")),
UnknownAtRule: new CSSIssueType('css-unknownatrule', nls.localize('unknown.atrule', "at-rule unknown")),
UnknownKeyword: new CSSIssueType('css-unknownkeyword', nls.localize('unknown.keyword', "unknown keyword")),
SelectorExpected: new CSSIssueType('css-selectorexpected', nls.localize('expected.selector', "selector expected")),
StringLiteralExpected: new CSSIssueType('css-stringliteralexpected', nls.localize('expected.stringliteral', "string literal expected")),
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,655 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export enum TokenType {
Ident,
AtKeyword,
String,
BadString,
BadUri,
Hash,
Num,
Percentage,
Dimension,
URI,
UnicodeRange,
CDO,
CDC,
Colon,
SemiColon,
CurlyL,
CurlyR,
ParenthesisL,
ParenthesisR,
BracketL,
BracketR,
Whitespace,
Includes,
Dashmatch,
SubstringOperator,
PrefixOperator,
SuffixOperator,
Delim,
EMS,
EXS,
Length,
Angle,
Time,
Freq,
Exclamation,
Resolution,
Comma,
Charset,
EscapedJavaScript,
BadEscapedJavaScript,
SingleLineComment,
EOF,
CustomToken
}
export interface IToken {
type: TokenType;
text: string;
offset: number;
len: number;
}
export class MultiLineStream {
private source:string;
private len:number;
private position:number;
constructor(source: string) {
this.source = source;
this.len = source.length;
this.position = 0;
}
public substring(from:number, to:number = this.position):string {
return this.source.substring(from, to);
}
public eos():boolean {
return this.len <= this.position;
}
public pos():number {
return this.position;
}
public goBackTo(pos:number):void {
this.position = pos;
}
public goBack(n:number):void {
this.position -= n;
}
public advance(n:number):void {
this.position += n;
}
public nextChar():number {
return this.source.charCodeAt(this.position++) || 0;
}
public peekChar(n:number=0):number {
return this.source.charCodeAt(this.position + n) || 0;
}
public lookbackChar(n:number=0):number {
return this.source.charCodeAt(this.position - n) || 0;
}
public advanceIfChar(ch:number):boolean {
if (ch === this.source.charCodeAt(this.position)) {
this.position++;
return true;
}
return false;
}
public advanceIfChars(ch:number[]):boolean {
var i:number;
if (this.position + ch.length > this.source.length) {
return false;
}
for (i = 0; i < ch.length; i++) {
if (this.source.charCodeAt(this.position + i) !== ch[i]) {
return false;
}
}
this.advance(i);
return true;
}
public advanceWhileChar(condition:(ch:number)=>boolean):number {
var posNow = this.position;
while (this.position < this.len && condition(this.source.charCodeAt(this.position))) {
this.position++;
}
return this.position - posNow;
}
}
var _a = 'a'.charCodeAt(0);
var _c = 'c'.charCodeAt(0);
var _e = 'e'.charCodeAt(0);
var _f = 'f'.charCodeAt(0);
var _h = 'h'.charCodeAt(0);
var _i = 'i'.charCodeAt(0);
var _l = 'l'.charCodeAt(0);
var _p = 'p'.charCodeAt(0);
var _r = 'r'.charCodeAt(0);
var _s = 's'.charCodeAt(0);
var _t = 't'.charCodeAt(0);
var _u = 'u'.charCodeAt(0);
var _x = 'x'.charCodeAt(0);
var _z = 'z'.charCodeAt(0);
var _A = 'A'.charCodeAt(0);
var _E = 'E'.charCodeAt(0);
var _F = 'F'.charCodeAt(0);
var _I = 'I'.charCodeAt(0);
var _L = 'L'.charCodeAt(0);
var _P = 'P'.charCodeAt(0);
var _R = 'R'.charCodeAt(0);
var _U = 'U'.charCodeAt(0);
var _X = 'X'.charCodeAt(0);
var _Z = 'Z'.charCodeAt(0);
var _0 = '0'.charCodeAt(0);
var _9 = '9'.charCodeAt(0);
var _TLD = '~'.charCodeAt(0);
var _HAT = '^'.charCodeAt(0);
var _EQS = '='.charCodeAt(0);
var _PIP = '|'.charCodeAt(0);
var _MIN = '-'.charCodeAt(0);
var _USC = '_'.charCodeAt(0);
var _PRC = '%'.charCodeAt(0);
var _MUL = '*'.charCodeAt(0);
var _LPA = '('.charCodeAt(0);
var _RPA = ')'.charCodeAt(0);
var _LAN = '<'.charCodeAt(0);
var _RAN = '>'.charCodeAt(0);
var _ATS = '@'.charCodeAt(0);
var _HSH = '#'.charCodeAt(0);
var _DLR = '$'.charCodeAt(0);
var _BSL = '\\'.charCodeAt(0);
var _FSL = '/'.charCodeAt(0);
var _NWL = '\n'.charCodeAt(0);
var _CAR = '\r'.charCodeAt(0);
var _LFD = '\f'.charCodeAt(0);
var _DQO = '"'.charCodeAt(0);
var _SQO = '\''.charCodeAt(0);
var _WSP = ' '.charCodeAt(0);
var _TAB = '\t'.charCodeAt(0);
var _SEM = ';'.charCodeAt(0);
var _COL = ':'.charCodeAt(0);
var _CUL = '{'.charCodeAt(0);
var _CUR = '}'.charCodeAt(0);
var _BRL = '['.charCodeAt(0);
var _BRR = ']'.charCodeAt(0);
var _CMA = ','.charCodeAt(0);
var _DOT = '.'.charCodeAt(0);
var _BNG = '!'.charCodeAt(0);
var _url = [_u, _U, _r, _R, _l, _L, _LPA, _LPA];
var _url_prefix = [_u, _U, _r, _R, _l, _L, _MIN, _MIN, _p, _P, _r, _R, _e, _E, _f, _F, _i, _I, _x, _X, _LPA, _LPA];
var staticTokenTable:{[code:number]:TokenType;} = {};
staticTokenTable[_SEM] = TokenType.SemiColon;
staticTokenTable[_COL] = TokenType.Colon;
staticTokenTable[_CUL] = TokenType.CurlyL;
staticTokenTable[_CUR] = TokenType.CurlyR;
staticTokenTable[_BRR] = TokenType.BracketR;
staticTokenTable[_BRL] = TokenType.BracketL;
staticTokenTable[_LPA] = TokenType.ParenthesisL;
staticTokenTable[_RPA] = TokenType.ParenthesisR;
staticTokenTable[_CMA] = TokenType.Comma;
var staticUnitTable:{[code:number]:TokenType;} = {};
staticUnitTable['em'] = TokenType.EMS;
staticUnitTable['ex'] = TokenType.EXS;
staticUnitTable['px'] = TokenType.Length;
staticUnitTable['cm'] = TokenType.Length;
staticUnitTable['mm'] = TokenType.Length;
staticUnitTable['in'] = TokenType.Length;
staticUnitTable['pt'] = TokenType.Length;
staticUnitTable['pc'] = TokenType.Length;
staticUnitTable['deg'] = TokenType.Angle;
staticUnitTable['rad'] = TokenType.Angle;
staticUnitTable['grad'] = TokenType.Angle;
staticUnitTable['ms'] = TokenType.Time;
staticUnitTable['s'] = TokenType.Time;
staticUnitTable['hz'] = TokenType.Freq;
staticUnitTable['khz'] = TokenType.Freq;
staticUnitTable['%'] = TokenType.Percentage;
staticUnitTable['dpi'] = TokenType.Resolution;
staticUnitTable['dpcm'] = TokenType.Resolution;
export class Scanner {
public stream: MultiLineStream;
public ignoreComment = true;
public setSource(input: string): void {
this.stream = new MultiLineStream(input);
}
public finishToken(token: IToken, type: TokenType, text?: string): IToken {
token.len = this.stream.pos() - token.offset;
token.type = type;
token.text = text || this.stream.substring(token.offset);
return token;
}
public substring(offset:number, len:number):string {
return this.stream.substring(offset, offset + len);
}
public pos():number {
return this.stream.pos();
}
public goBackTo(pos: number):void {
this.stream.goBackTo(pos);
}
public scan(ignoreWhitespace:boolean=true): IToken {
var result:IToken = {
type: undefined,
text: undefined,
offset: this.stream.pos(),
len: 0
};
// Whitespace - if asked for
if (this._whitespace()) {
if (!ignoreWhitespace) {
return this.finishToken(result, TokenType.Whitespace);
} else {
return this.scan(ignoreWhitespace);
}
}
// Comment - CSS
if (this._comment()) {
if (!this.ignoreComment) {
return this.finishToken(result, tokenType);
} else {
return this.scan(ignoreWhitespace);
}
}
// End of file/input
if (this.stream.eos()) {
return this.finishToken(result, TokenType.EOF);
}
// CDO <!--
if (this.stream.advanceIfChars([_LAN, _BNG, _MIN, _MIN])) {
return this.finishToken(result, TokenType.CDO);
}
// CDC -->
if (this.stream.advanceIfChars([_MIN, _MIN, _RAN])) {
return this.finishToken(result, TokenType.CDC);
}
// URL
var tokenType = this._url();
if (tokenType !== null) {
return this.finishToken(result, tokenType);
}
var content: string[] = [];
if (this.ident(content)) {
return this.finishToken(result, TokenType.Ident, content.join(''));
}
// at-keyword
if (this.stream.advanceIfChar(_ATS)) {
content = [ '@' ];
if (this.ident(content)) {
var keywordText = content.join('');
if (keywordText === '@charset') {
return this.finishToken(result, TokenType.Charset, keywordText);
}
return this.finishToken(result, TokenType.AtKeyword, keywordText);
} else {
return this.finishToken(result, TokenType.Delim);
}
}
// hash
if (this.stream.advanceIfChar(_HSH)) {
content = [ '#' ];
if (this._name(content)) {
return this.finishToken(result, TokenType.Hash, content.join(''));
} else {
return this.finishToken(result, TokenType.Delim);
}
}
// Important
if (this.stream.advanceIfChar(_BNG)) {
return this.finishToken(result, TokenType.Exclamation);
}
// Numbers
if (this._number()) {
var pos = this.stream.pos();
content = [ this.stream.substring(result.offset, pos) ];
if (this.stream.advanceIfChar(_PRC)) {
// Percentage 43%
return this.finishToken(result, TokenType.Percentage);
} else if (this.ident(content)) {
var dim = this.stream.substring(pos).toLowerCase();
var tokenType = <TokenType>staticUnitTable[dim];
if (typeof tokenType !== 'undefined') {
// Known dimension 43px
return this.finishToken(result, tokenType, content.join(''));
} else {
// Unknown dimension 43ft
return this.finishToken(result, TokenType.Dimension, content.join(''));
}
}
return this.finishToken(result, TokenType.Num);
}
// String, BadString
content = [];
var tokenType = this._string(content);
if (tokenType !== null) {
return this.finishToken(result, tokenType, content.join(''));
}
// single character tokens
var tokenType = <TokenType>staticTokenTable[this.stream.peekChar()];
if (typeof tokenType !== 'undefined') {
this.stream.advance(1);
return this.finishToken(result, tokenType);
}
// includes ~=
if (this.stream.peekChar(0) === _TLD && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(result, TokenType.Includes);
}
// DashMatch |=
if (this.stream.peekChar(0) === _PIP && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(result, TokenType.Dashmatch);
}
// Substring operator *=
if (this.stream.peekChar(0) === _MUL && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(result, TokenType.SubstringOperator);
}
// Substring operator ^=
if (this.stream.peekChar(0) === _HAT && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(result, TokenType.PrefixOperator);
}
// Substring operator $=
if (this.stream.peekChar(0) === _DLR && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(result, TokenType.SuffixOperator);
}
// Delim
this.stream.nextChar();
return this.finishToken(result, TokenType.Delim);
}
private _matchWordAnyCase(characters:number[]): boolean {
var index = 0;
this.stream.advanceWhileChar((ch:number) => {
var result = characters[index] === ch || characters[index+1] === ch;
if (result) {
index += 2;
}
return result;
});
if (index === characters.length) {
return true;
} else {
this.stream.goBack(index/2);
return false;
}
}
private _comment():boolean {
if (this.stream.advanceIfChars([_FSL, _MUL])) {
var success = false, hot = false;
this.stream.advanceWhileChar((ch) => {
if (hot && ch === _FSL) {
success = true;
return false;
}
hot = ch === _MUL;
return true;
});
if (success) {
this.stream.advance(1);
}
return true;
}
return false;
}
private _number():boolean {
var npeek = 0, ch:number;
if (this.stream.peekChar() === _DOT) {
npeek = 1;
}
ch = this.stream.peekChar(npeek);
if (ch >= _0 && ch <= _9) {
this.stream.advance(npeek + 1);
this.stream.advanceWhileChar((ch) => {
return ch >= _0 && ch <= _9 || npeek === 0 && ch === _DOT;
});
return true;
}
return false;
}
private _newline(result: string[]):boolean {
var ch = this.stream.peekChar();
switch (ch) {
case _CAR:
case _LFD:
case _NWL:
this.stream.advance(1);
result.push(String.fromCharCode(ch));
if (ch === _CAR && this.stream.advanceIfChar(_NWL)) {
result.push('\n');
}
return true;
}
return false;
}
private _escape(result: string[], includeNewLines?:boolean):boolean {
var ch = this.stream.peekChar();
if (ch === _BSL) {
this.stream.advance(1);
ch = this.stream.peekChar();
var hexNumCount = 0;
while (hexNumCount < 6 && (ch >= _0 && ch <= _9 || ch >= _a && ch <= _f || ch >= _A && ch <= _F)) {
this.stream.advance(1);
ch = this.stream.peekChar();
hexNumCount++;
}
if (hexNumCount > 0) {
try {
var hexVal= parseInt(this.stream.substring(this.stream.pos() - hexNumCount), 16);
if (hexVal) {
result.push(String.fromCharCode(hexVal));
}
} catch (e) {
// ignore
}
// optional whitespace or new line, not part of result text
if (ch === _WSP || ch === _TAB) {
this.stream.advance(1);
} else {
this._newline([]);
}
return true;
}
if (ch !== _CAR && ch !== _LFD && ch != _NWL) {
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
} else if (includeNewLines) {
return this._newline(result);
}
}
return false;
}
private _stringChar(closeQuote: number, result: string[]) {
// not closeQuote, not backslash, not newline
var ch = this.stream.peekChar();
if (ch !== 0 && ch !== closeQuote && ch !== _BSL && ch !== _CAR && ch !== _LFD && ch !== _NWL) {
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
};
private _string(result: string[]):TokenType {
if (this.stream.peekChar() === _SQO || this.stream.peekChar() === _DQO) {
var closeQuote = this.stream.nextChar();
result.push(String.fromCharCode(closeQuote));
while (this._stringChar(closeQuote, result) || this._escape(result, true)) {
// loop
}
if (this.stream.peekChar() === closeQuote) {
this.stream.nextChar();
result.push(String.fromCharCode(closeQuote));
return TokenType.String;
} else {
return TokenType.BadString;
}
}
return null;
}
private _url():TokenType {
if (this._matchWordAnyCase(_url) || this._matchWordAnyCase(_url_prefix)) {
this._whitespace();
var tokenType = TokenType.URI, stringType = this._string([]);
if (stringType === TokenType.BadString) {
tokenType = TokenType.BadUri;
} else if (stringType === null) {
this.stream.advanceWhileChar((ch) => {
return ch !== _RPA;
});
tokenType = TokenType.URI;
}
this._whitespace();
if (this.stream.advanceIfChar(_RPA)) {
return tokenType;
} else {
return TokenType.BadUri;
}
}
return null;
}
private _whitespace():boolean {
var n = this.stream.advanceWhileChar((ch) => {
return ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR;
});
return n > 0;
}
private _name(result:string[]):boolean {
var matched = false;
while (this._identChar(result) || this._escape(result)) {
matched = true;
}
return matched;
}
protected ident(result:string[]):boolean {
var pos = this.stream.pos();
var hasMinus = this._minus(result);
if (hasMinus && this._minus(result) /* -- */) {
var hasContent = false;
while (this._identChar(result) || this._escape(result)) {
hasContent = true;
}
if (hasContent) {
return true;
}
} else if (this._identFirstChar(result) || this._escape(result)) {
while (this._identChar(result) || this._escape(result)) {
// loop
}
return true;
}
this.stream.goBackTo(pos);
return false;
}
private _identFirstChar(result:string[]):boolean {
var ch = this.stream.peekChar();
if (ch === _USC || // _
ch >= _a && ch <= _z || // a-z
ch >= _A && ch <= _Z || // A-Z
ch >= 0x80 && ch <= 0xFFFF) { // nonascii
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
private _minus(result:string[]):boolean {
var ch = this.stream.peekChar();
if (ch === _MIN) {
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
private _identChar(result:string[]):boolean {
var ch = this.stream.peekChar();
if (ch === _USC || // _
ch === _MIN || // -
ch >= _a && ch <= _z || // a-z
ch >= _A && ch <= _Z || // A-Z
ch >= _0 && ch <= _9 || // 0/9
ch >= 0x80 && ch <= 0xFFFF) { // nonascii
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
}

View File

@@ -0,0 +1,325 @@
/*---------------------------------------------------------------------------------------------
* 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 nodes = require('./cssNodes');
import arrays = require('vs/base/common/arrays');
export class Scope {
public parent:Scope;
public children:Scope[];
public offset:number;
public length:number;
private symbols:Symbol[];
constructor(offset:number, length:number) {
this.offset = offset;
this.length = length;
this.symbols = [];
this.parent = null;
this.children = [];
}
public addChild(scope:Scope):void {
this.children.push(scope);
scope.setParent(this);
}
public setParent(scope:Scope):void {
this.parent = scope;
}
public findScope(offset:number, length:number=0):Scope {
if (this.offset <= offset && this.offset + this.length > offset + length || this.offset === offset && this.length === length) {
return this.findInScope(offset, length);
}
return null;
}
private findInScope(offset: number, length: number = 0): Scope {
// find the first scope child that has an offset larger than offset + length
var end = offset + length;
var idx = arrays.findFirst(this.children, s => s.offset > end);
if (idx === 0) {
// all scopes have offsets larger than our end
return this;
}
var res = this.children[idx-1];
if (res.offset <= offset && res.offset + res.length >= offset + length) {
return res.findInScope(offset, length);
}
return this;
}
public addSymbol(symbol:Symbol):void {
this.symbols.push(symbol);
}
public getSymbol(name:string, type: nodes.ReferenceType):Symbol {
for (var index = 0; index < this.symbols.length; index++) {
var symbol = this.symbols[index];
if (symbol.name === name && symbol.type === type) {
return symbol;
}
}
return null;
}
public getSymbols():Symbol[] {
return this.symbols;
}
}
export class GlobalScope extends Scope {
constructor() {
super(0, Number.MAX_VALUE);
}
}
export class Symbol {
public name:string;
public type:nodes.ReferenceType;
public node:nodes.Node;
constructor(name:string, node:nodes.Node, type: nodes.ReferenceType) {
this.name = name;
this.node = node;
this.type = type;
}
}
export class ScopeBuilder implements nodes.IVisitor {
public scope:Scope;
constructor(scope:Scope) {
this.scope = scope;
}
private addSymbol(node:nodes.Node, name:string, type: nodes.ReferenceType) : void {
if (node.offset !== -1) {
var current = this.scope.findScope(node.offset, node.length);
current.addSymbol(new Symbol(name, node, type));
}
}
private addScope(node:nodes.Node) : Scope {
if (node.offset !== -1) {
var current = this.scope.findScope(node.offset, node.length);
if (current.offset !== node.offset || current.length !== node.length) { // scope already known?
var newScope = new Scope(node.offset, node.length);
current.addChild(newScope);
return newScope;
}
return current;
}
return null;
}
private addSymbolToChildScope(scopeNode:nodes.Node, node:nodes.Node, name:string, type: nodes.ReferenceType): void {
if (scopeNode && scopeNode.offset !== -1 ) {
var current = this.addScope(scopeNode); // create the scope or gets the existing one
current.addSymbol(new Symbol(name, node, type));
}
}
public visitNode(node:nodes.Node):boolean {
switch (node.type) {
case nodes.NodeType.Keyframe:
this.addSymbol(node, (<nodes.Keyframe> node).getName(), nodes.ReferenceType.Keyframe);
return true;
case nodes.NodeType.VariableDeclaration:
this.addSymbol(node, (<nodes.VariableDeclaration> node).getName(), nodes.ReferenceType.Variable);
return true;
case nodes.NodeType.Ruleset:
return this.visitRuleSet(<nodes.RuleSet> node);
case nodes.NodeType.MixinDeclaration:
this.addSymbol(node, (<nodes.MixinDeclaration> node).getName(), nodes.ReferenceType.Mixin);
return true;
case nodes.NodeType.FunctionDeclaration:
this.addSymbol(node, (<nodes.FunctionDeclaration> node).getName(), nodes.ReferenceType.Function);
return true;
case nodes.NodeType.FunctionParameter:
// parameters are part of the body scope
var scopeNode = (<nodes.BodyDeclaration> node.getParent()).getDeclarations();
if (scopeNode) {
this.addSymbolToChildScope(scopeNode, node, (<nodes.FunctionParameter> node).getName(), nodes.ReferenceType.Variable);
}
return true;
case nodes.NodeType.Declarations:
this.addScope(node);
return true;
case nodes.NodeType.For:
case nodes.NodeType.Each:
var forOrEachNode = <nodes.ForStatement | nodes.EachStatement> node;
var scopeNode = forOrEachNode.getDeclarations();
if (scopeNode) {
this.addSymbolToChildScope(scopeNode, forOrEachNode.variable, forOrEachNode.variable.getName(), nodes.ReferenceType.Variable);
}
return true;
}
return true;
}
public visitRuleSet(node:nodes.RuleSet):boolean {
var current = this.scope.findScope(node.offset, node.length);
node.getSelectors().getChildren().forEach((node) => {
if (node instanceof nodes.Selector) {
if (node.getChildren().length === 1) { // only selectors with a single element can be extended
current.addSymbol(new Symbol(node.getChild(0).getText(), node, nodes.ReferenceType.Rule));
}
}
});
return true;
}
}
export class Symbols {
private global:Scope;
constructor(node:nodes.Node) {
this.global = new GlobalScope();
node.accept(new ScopeBuilder(this.global));
}
public findSymbolsAtOffset(offset:number, referenceType: nodes.ReferenceType) : Symbol[] {
var scope = this.global.findScope(offset, 0);
var result : Symbol[] = [];
var names : { [name:string]: boolean } = {};
while (scope) {
var symbols = scope.getSymbols();
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.node.offset <= offset && symbol.type === referenceType && !names[symbol.name]) {
result.push(symbol);
names[symbol.name] = true;
}
}
scope = scope.parent;
}
return result;
}
private internalFindSymbol(node:nodes.Node, referenceTypes:nodes.ReferenceType[]): Symbol {
var scopeNode = node;
if (node.parent instanceof nodes.FunctionParameter && node.parent.getParent() instanceof nodes.BodyDeclaration) {
scopeNode = (<nodes.BodyDeclaration> node.parent.getParent()).getDeclarations();
}
if (node.parent instanceof nodes.FunctionArgument && node.parent.getParent() instanceof nodes.Function) {
var funcId = (<nodes.Function> node.parent.getParent()).getIdentifier();
if (funcId) {
var functionSymbol = this.internalFindSymbol(funcId, [nodes.ReferenceType.Function]);
if (functionSymbol) {
scopeNode = (<nodes.FunctionDeclaration> functionSymbol.node).getDeclarations();
}
}
}
if (!scopeNode) {
return null;
}
var name = node.getText();
var scope = this.global.findScope(scopeNode.offset, scopeNode.length);
while (scope) {
for (var index = 0; index < referenceTypes.length; index++) {
var type = referenceTypes[index];
var symbol = scope.getSymbol(name, type);
if (symbol) {
return symbol;
}
}
scope = scope.parent;
}
return null;
}
private evaluateReferenceTypes(node: nodes.Node) : nodes.ReferenceType[] {
if (node instanceof nodes.Identifier) {
var referenceTypes = (<nodes.Identifier> node).referenceTypes;
if (referenceTypes) {
return referenceTypes;
} else {
// are a reference to a keyframe?
var decl = nodes.getParentDeclaration(node);
if (decl) {
var propertyName = decl.getNonPrefixedPropertyName();
if ((propertyName === 'animation' || propertyName === 'animation-name')
&& decl.getValue() && decl.getValue().offset === node.offset) {
return [ nodes.ReferenceType.Keyframe ];
}
}
}
} else if (node instanceof nodes.Variable) {
return [ nodes.ReferenceType.Variable ];
}
var selector = node.findParent(nodes.NodeType.Selector);
if (selector) {
return [ nodes.ReferenceType.Rule ];
}
var extendsRef = <nodes.ExtendsReference> node.findParent(nodes.NodeType.ExtendsReference);
if (extendsRef) {
return [ nodes.ReferenceType.Rule ];
}
return null;
}
public findSymbolFromNode(node: nodes.Node):Symbol {
if (!node) {
return null;
}
while (node.type === nodes.NodeType.Interpolation) {
node = node.getParent();
}
var referenceTypes = this.evaluateReferenceTypes(node);
if (referenceTypes) {
return this.internalFindSymbol(node, referenceTypes);
}
return null;
}
public matchesSymbol(node: nodes.Node, symbol: Symbol):boolean {
if (!node) {
return null;
}
while (node.type === nodes.NodeType.Interpolation) {
node = node.getParent();
}
if (symbol.name.length !== node.length || symbol.name !== node.getText()) {
return false;
}
var referenceTypes = this.evaluateReferenceTypes(node);
if (!referenceTypes || referenceTypes.indexOf(symbol.type) === -1) {
return false;
}
var nodeSymbol = this.internalFindSymbol(node, referenceTypes);
return nodeSymbol === symbol;
}
public findSymbol(name:string, type: nodes.ReferenceType, offset:number):Symbol {
var scope = this.global.findScope(offset);
while(scope) {
var symbol = scope.getSymbol(name, type);
if (symbol) {
return symbol;
}
scope = scope.parent;
}
return null;
}
}

View File

@@ -0,0 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export declare var data: any;
export declare var descriptions: any;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,159 @@
/*---------------------------------------------------------------------------------------------
* 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 network = require('vs/base/common/network');
import winjs = require('vs/base/common/winjs.base');
import nodes = require('vs/languages/css/common/parser/cssNodes');
import parser = require('vs/languages/css/common/parser/cssParser');
import eventEmitter = require('vs/base/common/eventEmitter');
import EditorCommon = require('vs/editor/common/editorCommon');
import resourceService = require('vs/editor/common/services/resourceService');
interface Entry {
node:nodes.Stylesheet;
version:number;
}
export interface ILanguageService {
join():winjs.TPromise<void>;
getStylesheet(resource:network.URL):nodes.Stylesheet;
}
class PromiseWithTrigger<T> extends winjs.TPromise<T> {
private _valueCallback:winjs.ValueCallback;
private _errorCallback:winjs.ErrorCallback;
constructor() {
super((c, e, p) => {
this._valueCallback = c;
this._errorCallback = e;
});
}
public resolve(data:T):PromiseWithTrigger<T> {
this._valueCallback(data);
return this;
}
public reject(err:any):PromiseWithTrigger<T> {
this._errorCallback(err);
return this;
}
}
export class CSSLanguageService implements ILanguageService {
private resourceService:resourceService.IResourceService;
private entries:{[url:string]:Entry;};
private activeDelay:PromiseWithTrigger<any>;
private onChangeHandle:number;
private callOnDispose:Function[];
private createParser: () => parser.Parser;
constructor(service:resourceService.IResourceService, createParser: () => parser.Parser, private _cssModeId:string) {
this.resourceService = service;
this.entries = {};
this.callOnDispose = [];
this.createParser = createParser;
this.updateResources();
this.callOnDispose.push(this.resourceService.addListener_(resourceService.ResourceEvents.ADDED, (e: resourceService.IResourceAddedEvent) => this.onResourceAdded(e)));
this.callOnDispose.push(this.resourceService.addListener_(resourceService.ResourceEvents.REMOVED, (e: resourceService.IResourceRemovedEvent) => this.onResourceRemoved(e)));
this.callOnDispose.push(this.resourceService.addListener_(resourceService.ResourceEvents.CHANGED, (e: resourceService.IResourceChangedEvent) => this.onResourceChange(e)));
}
public dispose():void {
while(this.callOnDispose.length > 0) {
this.callOnDispose.pop()();
}
clearTimeout(this.onChangeHandle);
this.onChangeHandle = null;
this.entries = null;
}
private onResourceAdded(e: resourceService.IResourceAddedEvent):void {
if (this._isMyMirrorModel(e.addedElement)) {
this._scheduleRefreshLanguageService();
}
}
private onResourceRemoved(e: resourceService.IResourceRemovedEvent):void {
var url = e.url.toString();
if (this.entries.hasOwnProperty(url)) {
delete this.entries[url];
}
}
private onResourceChange(e: resourceService.IResourceChangedEvent):void {
if (this._isMyModel(e.url)) {
this._scheduleRefreshLanguageService();
}
}
private _scheduleRefreshLanguageService(): void {
if (!this.activeDelay) {
this.activeDelay = new PromiseWithTrigger<any>();
}
if (this.onChangeHandle) {
clearTimeout(this.onChangeHandle);
}
this.onChangeHandle = setTimeout(() => {
this.updateResources();
this.activeDelay.resolve(null);
this.activeDelay = null;
this.onChangeHandle = null;
}, 50);
}
public join():winjs.TPromise<void> {
return (this.activeDelay || winjs.Promise.as(null));
}
private _isMyMirrorModel(resource:EditorCommon.IMirrorModel): boolean {
return resource.getMode().getId() === this._cssModeId;
}
private _isMyModel(url:network.URL): boolean {
return this._isMyMirrorModel(this.resourceService.get(url));
}
private updateResources():void {
var t1 = new Date().getTime(), n = 0;
this.resourceService.all().filter((element) => this._isMyMirrorModel(element)).forEach((model:EditorCommon.IMirrorModel) => {
// Reparse changes or new models
var url = model.getAssociatedResource().toString(),
entry = this.entries[url],
hasEntry = typeof entry !== 'undefined';
if(!hasEntry || entry.version !== model.getVersionId()) {
if(!hasEntry) {
entry = { node: null, version: -1 };
this.entries[url] = entry;
}
entry.node = this.createParser().parseStylesheet(model);
entry.node.setName(url);
entry.version = model.getVersionId();
n += 1;
}
});
// console.info('[less] updating ' + n + ' resources took ms' + (new Date().getTime() - t1));
}
public getStylesheet(resource:network.URL):nodes.Stylesheet {
if(this.entries.hasOwnProperty(resource.toString())) {
return this.entries[resource.toString()].node;
}
return null;
}
}

View File

@@ -0,0 +1,498 @@
/*---------------------------------------------------------------------------------------------
* 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 nodes = require('vs/languages/css/common/parser/cssNodes');
import cssSymbols = require('vs/languages/css/common/parser/cssSymbols');
import languageFacts = require('vs/languages/css/common/services/languageFacts');
import service = require('vs/languages/css/common/services/cssLanguageService');
import network = require('vs/base/common/network');
import EditorCommon = require('vs/editor/common/editorCommon');
import Modes = require('vs/editor/common/modes');
import nls = require('vs/nls');
import scanner = require('vs/languages/css/common/parser/cssScanner');
export class CSSIntellisense {
private static colorFunctions = [
{ func: 'rgb($red, $green, $blue)', desc: nls.localize('css.builtin.rgb', 'Creates a Color from red, green, and blue values.') },
{ func: 'rgba($red, $green, $blue, $alpha)', desc: nls.localize('css.builtin.rgba', 'Creates a Color from red, green, blue, and alpha values.') },
{ func: 'hsl($hue, $saturation, $lightness)', desc: nls.localize('css.builtin.hsl', 'Creates a Color from hue, saturation, and lightness values.') },
{ func: 'hsla($hue, $saturation, $lightness, $alpha)', desc: nls.localize('css.builtin.hsla', 'Creates a Color from hue, saturation, lightness, and alpha values.') }
];
variablePrefix: string;
position: EditorCommon.IPosition;
offset: number;
currentWord: string;
model: EditorCommon.IMirrorModel;
styleSheet: nodes.Stylesheet;
symbolContext: cssSymbols.Symbols;
isIncomplete: boolean;
constructor(variablePrefix: string = null) {
this.variablePrefix = variablePrefix;
}
private getSymbolContext() : cssSymbols.Symbols {
if (!this.symbolContext) {
this.symbolContext = new cssSymbols.Symbols(this.styleSheet);
}
return this.symbolContext;
}
public getCompletionsAtPosition(languageService:service.ILanguageService, model: EditorCommon.IMirrorModel, resource:network.URL, position:EditorCommon.IPosition):Modes.ISuggestions {
this.offset = model.getOffsetFromPosition(position);
this.position = position;
this.currentWord = model.getWordUntilPosition(position).word;
this.model = model;
this.styleSheet = languageService.getStylesheet(resource);
var result : Modes.ISuggestion[] = [];
var nodepath = nodes.getNodePath(this.styleSheet, this.offset);
this.isIncomplete = false;
for (var i = nodepath.length - 1; i >= 0; i--) {
var node = nodepath[i];
if (node instanceof nodes.Property) {
this.getCompletionsForDeclarationProperty(result);
} else if (node instanceof nodes.Expression) {
this.getCompletionsForExpression(<nodes.Expression> node, result);
} else if (node instanceof nodes.SimpleSelector) {
var parentRuleSet = <nodes.RuleSet> node.findParent(nodes.NodeType.Ruleset);
this.getCompletionsForSelector(parentRuleSet, result);
} else if (node instanceof nodes.Declarations) {
this.getCompletionsForDeclarations(<nodes.Declarations> node, result);
} else if (node instanceof nodes.VariableDeclaration) {
this.getCompletionsForVariableDeclaration(<nodes.VariableDeclaration> node, result);
} else if (node instanceof nodes.RuleSet) {
this.getCompletionsForRuleSet(<nodes.RuleSet> node, result);
} else if (node instanceof nodes.Interpolation) {
this.getCompletionsForInterpolation(<nodes.Interpolation> node, result);
} else if (node instanceof nodes.FunctionArgument) {
this.getCompletionsForFunctionArguments(<nodes.FunctionArgument> node, result);
} else if (node instanceof nodes.FunctionDeclaration) {
this.getCompletionsForFunctionDeclaration(<nodes.FunctionDeclaration> node, result);
}
if (result.length > 0) {
return { currentWord: this.currentWord, suggestions: result, incomplete: this.isIncomplete };
}
}
this.getCompletionsForStylesheet(result);
if (result.length > 0) {
return { currentWord: this.currentWord, suggestions: result };
}
if (this.variablePrefix && this.currentWord.indexOf(this.variablePrefix) === 0) {
this.getVariableProposals(result);
if (result.length > 0) {
return { currentWord: this.currentWord, suggestions: result };
}
model.getAllUniqueWords(this.currentWord).forEach((word) => {
result.push({ type: 'text', label: word, codeSnippet: word });
});
}
// no match, don't show text matches
return {
currentWord: this.currentWord,
suggestions: result
};
}
public getCompletionsForDeclarationProperty(result:Modes.ISuggestion[]):Modes.ISuggestion[] {
var properties = languageFacts.getProperties();
for (var key in properties) {
if (properties.hasOwnProperty(key)) {
var entry = properties[key];
if (entry.browsers.count > 1) { // only show if supported by more than one browser
result.push({
label: entry.name,
documentationLabel: languageFacts.getEntryDescription(entry),
codeSnippet: entry.name + ': ',
type: 'property'
});
}
}
}
return result;
}
public getCompletionsForDeclarationValue(node: nodes.Declaration, result:Modes.ISuggestion[]):Modes.ISuggestion[] {
var propertyName = node.getFullPropertyName();
var entry = languageFacts.getProperties()[propertyName];
if (entry) {
this.getColorProposals(entry, result);
this.getValueEnumProposals(entry, result);
this.getUnitProposals(entry, result);
} else {
var existingValues = new Set();
this.styleSheet.accept(new ValuesCollector(propertyName, existingValues));
existingValues.getEntries().forEach((existingValue) => {
result.push({
label: existingValue,
codeSnippet: existingValue,
type: 'value'
});
});
}
this.getVariableProposals(result);
this.getTermProposals(result);
return result;
}
public getValueEnumProposals(entry:languageFacts.IEntry, result:Modes.ISuggestion[]):Modes.ISuggestion[]{
if (entry.values) {
var type = 'value';
entry.values.forEach((value) => {
if (languageFacts.isCommonValue(value)) { // only show if supported by more than one browser
result.push({
label: value.name,
documentationLabel: languageFacts.getEntryDescription(value),
codeSnippet: value.name,
type: type
});
}
});
}
return result;
}
public getCompletionsForInterpolation(node: nodes.Interpolation, result:Modes.ISuggestion[]):Modes.ISuggestion[] {
if (this.offset >= node.offset + 2) {
this.getVariableProposals(result);
}
return result;
}
public getVariableProposals(result:Modes.ISuggestion[]):Modes.ISuggestion[]{
var symbols = this.getSymbolContext().findSymbolsAtOffset(this.offset, nodes.ReferenceType.Variable);
symbols.forEach((symbol) => {
result.push({
label: symbol.name,
codeSnippet: symbol.name,
type: 'variable'
});
});
return result;
}
public getUnitProposals(entry: languageFacts.IEntry, result: Modes.ISuggestion[]): Modes.ISuggestion[]{
var currentWord = '0';
if (this.currentWord.length > 0) {
var numMatch = this.currentWord.match(/-?\d[\.\d+]*/);
if (numMatch) {
currentWord = numMatch[0];
}
}
entry.restrictions.forEach((restriction) => {
var units= languageFacts.units[restriction];
if (units) {
units.forEach(function(unit:string) {
result.push({
label: currentWord + unit,
codeSnippet: currentWord + unit,
type: 'unit'
});
});
}
});
this.isIncomplete = true;
return result;
}
protected getColorProposals(entry:languageFacts.IEntry, result:Modes.ISuggestion[]):Modes.ISuggestion[] {
if (entry.restrictions.indexOf('color') !== -1) {
for (var color in languageFacts.colors) {
result.push({
label: color,
documentationLabel: languageFacts.colors[color],
codeSnippet: color,
type: '#' + languageFacts.colors[color]
});
}
for (var color in languageFacts.colorKeywords) {
result.push({
label: color,
documentationLabel: languageFacts.colorKeywords[color],
codeSnippet: color,
type: 'function'
});
}
var colorValues = new Set();
this.styleSheet.accept(new ColorValueCollector(colorValues));
colorValues.getEntries().forEach((color) => {
result.push({
label: color,
codeSnippet: color,
type: '#' + color
});
});
CSSIntellisense.colorFunctions.forEach((p) => {
result.push({
label: p.func.substr(0, p.func.indexOf('(')),
typeLabel: p.func,
documentationLabel: p.desc,
codeSnippet: p.func.replace(/\[?\$(\w+)\]?/g, '{{$1}}'),
type: 'function'
});
});
};
return result;
}
public getCompletionsForStylesheet(result:Modes.ISuggestion[]):Modes.ISuggestion[] {
var node = this.styleSheet.findFirstChildBeforeOffset(this.offset);
if (!node) {
return this.getCompletionForTopLevel(result);
}
if (node instanceof nodes.RuleSet) {
return this.getCompletionsForRuleSet(<nodes.RuleSet> node, result);
}
return result;
}
public getCompletionForTopLevel(result:Modes.ISuggestion[]):Modes.ISuggestion[] {
languageFacts.getAtDirectives().forEach(function(entry) {
if (entry.browsers.count > 0) {
result.push({
label: entry.name,
codeSnippet: entry.name,
documentationLabel: languageFacts.getEntryDescription(entry),
type: 'keyword'
});
}
});
this.getCompletionsForSelector(null, result);
return result;
}
public getCompletionsForRuleSet(ruleSet: nodes.RuleSet, result: Modes.ISuggestion[]): Modes.ISuggestion[]{
var declarations = ruleSet.getDeclarations();
var isAfter = declarations && declarations.endsWith('}') && this.offset >= declarations.offset + declarations.length;
if (isAfter) {
return this.getCompletionForTopLevel(result);
}
var isInSelectors = !declarations || this.offset <= declarations.offset;
if (isInSelectors) {
return this.getCompletionsForSelector(ruleSet, result);
}
ruleSet.findParent(nodes.NodeType.Ruleset);
return this.getCompletionsForDeclarations(ruleSet.getDeclarations(), result);
}
public getCompletionsForSelector(ruleSet: nodes.RuleSet, result:Modes.ISuggestion[]): Modes.ISuggestion[] {
languageFacts.getPseudoClasses().forEach((entry) => {
if (entry.browsers.count > 1) { // only show if supported by all browsers
result.push({
label: entry.name,
codeSnippet: entry.name,
documentationLabel: languageFacts.getEntryDescription(entry),
type: 'function'
});
}
});
languageFacts.getPseudoElements().forEach((entry) => {
if (entry.browsers.count > 1) { // only show if supported by all browsers
result.push({
label: entry.name,
codeSnippet: entry.name,
documentationLabel: languageFacts.getEntryDescription(entry),
type: 'function'
});
}
});
languageFacts.html5Tags.forEach((entry) => {
result.push({
label: entry,
codeSnippet: entry,
type: 'keyword'
});
});
var visited: { [name: string]: boolean } = {};
visited[this.currentWord] = true;
var textProvider = this.styleSheet.getTextProvider();
this.styleSheet.accept(n => {
if (n.type === nodes.NodeType.SimpleSelector && n.length > 0) {
var selector = textProvider(n.offset, n.length);
if (selector.charAt(0) === '.' && !visited[selector]) {
visited[selector] = true;
result.push({
label: selector,
codeSnippet: selector,
type: 'keyword'
});
}
return false;
}
return true;
});
if (ruleSet && ruleSet.isNested()) {
var selector = ruleSet.getSelectors().findFirstChildBeforeOffset(this.offset);
if (selector && ruleSet.getSelectors().getChildren().indexOf(selector) === 0) {
this.getCompletionsForDeclarationProperty(result);
}
}
return result;
}
public getCompletionsForDeclarations(declarations: nodes.Declarations, result: Modes.ISuggestion[]): Modes.ISuggestion[]{
if (!declarations) { // incomplete nodes
return result;
}
var node = declarations.findFirstChildBeforeOffset(this.offset);
if (!node) {
return this.getCompletionsForDeclarationProperty(result);
}
if (node instanceof nodes.Declaration) {
var declaration = <nodes.Declaration> node;
if ((!isDefined(declaration.colonPosition) || this.offset <= declaration.colonPosition) || (isDefined(declaration.semicolonPosition) && declaration.semicolonPosition < this.offset)) {
if (this.offset === declaration.semicolonPosition + 1) {
return result; // don't show new properties right after semicolon (see Bug 15421:[intellisense] [css] Be less aggressive when manually typing CSS)
}
// complete property
return this.getCompletionsForDeclarationProperty(result);
}
// complete value
return this.getCompletionsForDeclarationValue(declaration, result);
}
return result;
}
public getCompletionsForVariableDeclaration(declaration: nodes.VariableDeclaration, result:Modes.ISuggestion[]):Modes.ISuggestion[] {
if (this.offset > declaration.colonPosition) {
this.getVariableProposals(result);
}
return result;
}
public getCompletionsForExpression(expression: nodes.Expression, result:Modes.ISuggestion[]):Modes.ISuggestion[]{
var declaration = <nodes.Declaration> expression.findParent(nodes.NodeType.Declaration);
if (!declaration) {
this.getTermProposals(result);
return result;
}
var node = expression.findChildAtOffset(this.offset, true);
if (!node) {
return this.getCompletionsForDeclarationValue(declaration, result);
}
if (node instanceof nodes.NumericValue || node instanceof nodes.Identifier) {
return this.getCompletionsForDeclarationValue(declaration, result);
}
return result;
}
public getCompletionsForFunctionArguments(arg: nodes.FunctionArgument, result: Modes.ISuggestion[]): Modes.ISuggestion[] {
return result;
}
public getCompletionsForFunctionDeclaration(decl: nodes.FunctionDeclaration, result: Modes.ISuggestion[]): Modes.ISuggestion[]{
var declarations = decl.getDeclarations();
if (declarations && this.offset > declarations.offset && this.offset < declarations.offset + declarations.length) {
this.getTermProposals(result);
}
return result;
}
public getTermProposals(result: Modes.ISuggestion[]): Modes.ISuggestion[]{
var allFunctions = this.getSymbolContext().findSymbolsAtOffset(this.offset, nodes.ReferenceType.Function);
allFunctions.forEach((functionSymbol) => {
if (functionSymbol.node instanceof nodes.FunctionDeclaration) {
var functionDecl = <nodes.FunctionDeclaration> functionSymbol.node;
var params = functionDecl.getParameters().getChildren().map((c) => {
return (c instanceof nodes.FunctionParameter) ? (<nodes.FunctionParameter> c).getName() : c.getText();
});
result.push({
label: functionSymbol.name,
typeLabel: functionSymbol.name + '(' + params.join(', ') + ')',
codeSnippet: functionSymbol.name + '(' + params.map((p) => '{{' + p + '}}').join(', ') + ')',
type: 'function'
});
}
});
return result;
}
}
class Set {
private entries: { [key:string]: boolean } = {};
public add(entry: string) : void {
this.entries[entry] = true;
}
public getEntries() : string[] {
return Object.keys(this.entries);
}
}
class InternalValueCollector implements nodes.IVisitor {
constructor(public entries:Set) {
// nothing to do
}
public visitNode(node:nodes.Node):boolean {
if (node instanceof nodes.Identifier || node instanceof nodes.NumericValue || node instanceof nodes.HexColorValue) {
this.entries.add(node.getText());
}
return true;
}
}
class ValuesCollector implements nodes.IVisitor {
constructor(public propertyName: string, public entries:Set) {
// nothing to do
}
private matchesProperty(decl : nodes.Declaration) : boolean {
var propertyName = decl.getFullPropertyName();
return this.propertyName === propertyName;
}
public visitNode(node:nodes.Node):boolean {
if (node instanceof nodes.Declaration) {
if (this.matchesProperty(<nodes.Declaration> node)) {
var value = (<nodes.Declaration> node).getValue();
if (value) {
value.accept(new InternalValueCollector(this.entries));
}
}
}
return true;
}
}
class ColorValueCollector implements nodes.IVisitor {
constructor(public entries:Set) {
// nothing to do
}
public visitNode(node:nodes.Node): boolean {
if (node instanceof nodes.HexColorValue || (node instanceof nodes.Function && languageFacts.isColorConstructor(<nodes.Function> node))) {
this.entries.add(node.getText());
}
return true;
}
}
function isDefined(obj: any) : boolean {
return typeof obj !== 'undefined';
}

View File

@@ -0,0 +1,483 @@
/*---------------------------------------------------------------------------------------------
* 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 nodes = require('vs/languages/css/common/parser/cssNodes');
import browsers = require('vs/languages/css/common/services/browsers');
import strings = require('vs/base/common/strings');
export var colors : { [name:string]:string } = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgrey: '#a9a9a9',
darkgreen: '#006400',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkslategrey: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
grey: '#808080',
green: '#008000',
greenyellow: '#adff2f',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgrey: '#d3d3d3',
lightgreen: '#90ee90',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370d8',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#d87093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
red: '#ff0000',
rebeccapurple: '#663399',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
slategrey: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd32'
};
export var colorKeywords : { [name:string]:string } = {
'ActiveBorder': 'Active window border.',
'ActiveCaption': 'Active window caption.',
'AppWorkspace': 'Background color of multiple document interface.',
'Background': 'Desktop background.',
'ButtonFace': 'The face background color for 3-D elements that appear 3-D due to one layer of surrounding border.',
'ButtonHighlight': 'The color of the border facing the light source for 3-D elements that appear 3-D due to one layer of surrounding border.',
'ButtonShadow': 'The color of the border away from the light source for 3-D elements that appear 3-D due to one layer of surrounding border.',
'ButtonText': 'Text on push buttons.',
'CaptionText': 'Text in caption, size box, and scrollbar arrow box.',
'currentColor': 'The value of the \'color\' property. The computed value of the \'currentColor\' keyword is the computed value of the \'color\' property. If the \'currentColor\' keyword is set on the \'color\' property itself, it is treated as \'color:inherit\' at parse time.',
'GrayText': 'Grayed (disabled) text. This color is set to #000 if the current display driver does not support a solid gray color.',
'Highlight': 'Item(s) selected in a control.',
'HighlightText': 'Text of item(s) selected in a control.',
'InactiveBorder': 'Inactive window border.',
'InactiveCaption': 'Inactive window caption.',
'InactiveCaptionText': 'Color of text in an inactive caption.',
'InfoBackground': 'Background color for tooltip controls.',
'InfoText': 'Text color for tooltip controls.',
'Menu': 'Menu background.',
'MenuText': 'Text in menus.',
'Scrollbar': 'Scroll bar gray area.',
'ThreeDDarkShadow': 'The color of the darker (generally outer) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.',
'ThreeDFace': 'The face background color for 3-D elements that appear 3-D due to two concentric layers of surrounding border.',
'ThreeDHighlight': 'The color of the lighter (generally outer) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.',
'ThreeDLightShadow': 'The color of the darker (generally inner) of the two borders facing the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.',
'ThreeDShadow': 'The color of the lighter (generally inner) of the two borders away from the light source for 3-D elements that appear 3-D due to two concentric layers of surrounding border.',
'transparent': 'Fully transparent. This keyword can be considered a shorthand for rgba(0,0,0,0) which is its computed value.',
'Window': 'Window background.',
'WindowFrame': 'Window frame.',
'WindowText': 'Text in windows.',
'none': ''
};
export var units : { [unitName:string]:string[] } = {
'length': ['em', 'rem', 'ex', 'px', 'cm', 'mm', 'in', 'pt', 'pc', 'cc'],
'angle': ['deg', 'rad', 'grad'],
'time': ['ms', 's'],
'frequency': ['hz', 'khz'],
'resolution': ['dpi', 'dpcm'],
'percentage': ['%']
};
export var html5Tags = ['a', 'abbr', 'address', 'area', 'article', 'aside', 'audio', 'b', 'base', 'bdi', 'bdo', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption',
'cite', 'code', 'col', 'colgroup', 'data', 'datalist', 'dd', 'del', 'details', 'dfn', 'dialog', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption', 'figure', 'footer',
'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe', 'img', 'input', 'ins', 'kbd', 'keygen', 'label', 'legend', 'li', 'link',
'main', 'map', 'mark', 'menu', 'menuitem', 'meta', 'meter', 'nav', 'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param', 'pre', 'progress', 'q', 'rb',
'rp', 'rt', 'rtc', 'ruby', 's', 'samp', 'script', 'section', 'select', 'small', 'source', 'span', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template',
'textarea', 'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'track', 'u', 'ul', 'var', 'video', 'wbr' ];
export function isColorConstructor(node:nodes.Function): boolean {
var name = node.getName();
if (!name) {
return false;
}
return strings.equalsIgnoreCase(name, 'rgb') ||
strings.equalsIgnoreCase(name, 'rgba') ||
strings.equalsIgnoreCase(name, 'hsl') ||
strings.equalsIgnoreCase(name, 'hsla');
}
/**
* Returns true if the node is a color value - either
* defined a hex number, as rgb or rgba function, or
* as color name.
*/
export function isColorValue(node:nodes.Node):boolean {
if(node.type === nodes.NodeType.HexColorValue) {
return true;
} else if(node.type === nodes.NodeType.Function) {
return this.isColorConstructor(<nodes.Function> node);
} else if (node.type === nodes.NodeType.Identifier) {
if (node.parent && node.parent.type !== nodes.NodeType.Term) {
return false;
}
var candidateColor = node.getText().toLowerCase();
if(candidateColor === 'none') {
return false;
}
if (colors[candidateColor]) {
return true;
}
}
return false;
}
/**
* Returns true if the given name is a known property.
*/
export function isKnownProperty(name: string):boolean {
if(!name) {
return false;
} else {
name = name.toLowerCase();
return getProperties().hasOwnProperty(name);
}
}
export function isCommonValue(entry:Value):boolean {
return entry.browsers.count > 1;
}
export function getPageBoxDirectives():string[] {
return [
'@bottom-center', '@bottom-left', '@bottom-left-corner', '@bottom-right', '@bottom-right-corner',
'@left-bottom', '@left-middle', '@left-top', '@right-bottom', '@right-middle', '@right-top',
'@top-center', '@top-left', '@top-left-corner', '@top-right', '@top-right-corner'
];
}
export function getEntryDescription(entry:{description: string; browsers: Browsers}): string {
var desc = entry.description || '';
var browserLabel = this.getBrowserLabel(entry.browsers);
if (browserLabel) {
if (desc) {
desc = desc + '\n';
}
desc= desc + '(' + browserLabel + ')';
}
return desc;
}
export function getBrowserLabel(b: Browsers): string {
var result = '';
if (!b || b.all || b.count === 0) {
return null;
}
for (var curr in browserNames) {
if ((<any> b)[curr]) {
if (result.length > 0) {
result = result + ', ';
}
result = result + (<any> browserNames)[curr];
var version = (<any> b)[curr];
if (version.length > 0) {
result = result + ' ' + version;
}
}
}
return result;
}
export interface Browsers {
FF:string;
IE:string;
O:string;
C:string;
S:string;
count:number;
all:boolean;
}
export interface Value {
name:string;
description:string;
browsers:Browsers;
}
export interface IEntry {
name:string;
restrictions:string[];
browsers:Browsers;
description:string;
values:Value[];
}
function evalBrowserEntry(browsers: string) {
var browserEntry : Browsers = { all: false, FF: '', S: '', C: '', IE: '', O: '', count: 0};
var count = 0;
if (browsers) {
browsers.split(',').forEach(
(s: string) => {
s = s.trim();
if (s === 'all') {
browserEntry.all= true;
count = Number.MAX_VALUE;
} else if (s !== 'none') {
for (var key in browserNames) {
if (s.indexOf(key) === 0) {
(<any> browserEntry)[key] = s.substring(key.length).trim();
count++;
}
}
}
}
);
} else {
browserEntry.all = true;
count = Number.MAX_VALUE;
}
browserEntry.count = count;
return browserEntry;
};
class ValueImpl implements Value {
private browserEntry: Browsers;
constructor(public data: any) {
}
get name() : string {
return this.data.name;
}
get description() : string {
return this.data.desc || browsers.descriptions[this.data.name];
}
get browsers() : Browsers {
if (!this.browserEntry) {
this.browserEntry = evalBrowserEntry(this.data.browsers);
}
return this.browserEntry;
}
}
class EntryImpl implements IEntry {
private browserEntry: Browsers;
constructor(public data: any) {
}
get name(): string {
return this.data.name;
}
get description(): string {
return this.data.desc || browsers.descriptions[this.data.name];
}
get browsers(): Browsers {
if (!this.browserEntry) {
this.browserEntry = evalBrowserEntry(this.data.browsers);
}
return this.browserEntry;
}
get restrictions(): string[] {
if (this.data.restriction) {
return this.data.restriction.split(',').map(function(s: string) { return s.trim(); });
} else {
return [];
}
}
get values(): Value[] {
if(!this.data.values) {
return [];
}
if(!Array.isArray(this.data.values)) {
return [new ValueImpl(this.data.values.value)];
}
return this.data.values.map(function (v: string) {
return new ValueImpl(v);
});
}
}
var propertySet: { [key: string]: IEntry };
var properties = browsers.data.css.properties;
export function getProperties(): { [name: string]: IEntry; } {
if(!propertySet) {
propertySet = {
};
for(var i = 0, len = properties.length; i < len; i++) {
var rawEntry = properties[i];
propertySet[rawEntry.name] = new EntryImpl(rawEntry);
}
}
return propertySet;
}
var atDirectives = browsers.data.css.atdirectives;
var atDirectiveList: IEntry[];
export function getAtDirectives(): IEntry[] {
if (!atDirectiveList) {
atDirectiveList = [];
for (var i = 0, len = atDirectives.length; i < len; i++) {
var rawEntry = atDirectives[i];
atDirectiveList.push(new EntryImpl(rawEntry));
}
}
return atDirectiveList;
}
var pseudoElements = browsers.data.css.pseudoelements;
var pseudoElementList: IEntry[];
export function getPseudoElements(): IEntry[] {
if (!pseudoElementList) {
pseudoElementList = [];
for (var i = 0, len = pseudoElements.length; i < len; i++) {
var rawEntry = pseudoElements[i];
pseudoClassesList.push(new EntryImpl(rawEntry));
}
}
return pseudoElementList;
}
var pseudoClasses = browsers.data.css.pseudoclasses;
var pseudoClassesList: IEntry[];
export function getPseudoClasses(): IEntry[]{
if (!pseudoClassesList) {
pseudoClassesList = [];
for (var i = 0, len = pseudoClasses.length; i < len; i++) {
var rawEntry = pseudoClasses[i];
pseudoClassesList.push(new EntryImpl(rawEntry));
}
}
return pseudoClassesList;
}
export var browserNames = {
FF : 'Firefox',
S : 'Safari',
C : 'Chrome',
IE : 'IE',
O : 'Opera'
};

View File

@@ -0,0 +1,539 @@
/*---------------------------------------------------------------------------------------------
* 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 languageFacts = require('vs/languages/css/common/services/languageFacts');
import lintRules = require('vs/languages/css/common/services/lintRules');
import nodes = require('vs/languages/css/common/parser/cssNodes');
import nls = require('vs/nls');
import _level = require('vs/languages/css/common/level');
class Element {
public name: string;
public node: nodes.Declaration;
constructor(text:string, data:nodes.Declaration){
this.name = text;
this.node = data;
}
}
class NodesByRootMap {
public data:{[name:string]:{nodes:nodes.Node[];names:string[]}} = {};
public add(root:string, name:string, node:nodes.Node) : void {
var entry = this.data[root];
if (!entry) {
entry = { nodes: [], names: []};
this.data[root] = entry;
}
entry.names.push(name);
if (node) {
entry.nodes.push(node);
}
}
}
export class LintVisitor implements nodes.IVisitor {
static entries(node:nodes.Node, settings: lintRules.IConfigurationSettings):nodes.IMarker[] {
var visitor = new LintVisitor(settings);
node.accept(visitor);
return visitor.getEntries();
}
static prefixes = [
'-ms-', '-moz-', '-o-', '-webkit-', // Quite common
// '-xv-', '-atsc-', '-wap-', '-khtml-', 'mso-', 'prince-', '-ah-', '-hp-', '-ro-', '-rim-', '-tc-' // Quite un-common
];
private warnings:nodes.IMarker[] = [];
private configuration:{ [id:string] : _level.Level };
constructor(settings: lintRules.IConfigurationSettings = {}) {
this.configuration = {};
for (var ruleKey in lintRules.Rules) {
var rule = lintRules.Rules[ruleKey];
var level = settings[rule.id] || _level.toLevel(rule.defaultValue);
this.configuration[rule.id] = level;
}
}
private fetch(input: Element[], s:string): Element[] {
var elements: Element[] = [];
for (var i = 0; i < input.length; i++) {
if (input[i].name.toLowerCase() === s) {
elements.push(input[i]);
}
}
return elements;
}
private fetchWithValue(input: Element[], s:string, v:string): Element[] {
var elements: Element[] = [];
for (var i = 0; i < input.length; i++) {
if (input[i].name.toLowerCase() === s) {
var expression = input[i].node.getValue();
if (expression && this.findValueInExpression(expression, v)) {
elements.push(input[i]);
}
}
}
return elements;
}
private findValueInExpression(expression: nodes.Expression, v:string):boolean {
var found= false;
expression.accept(function(node) {
if (node.type === nodes.NodeType.Identifier && node.getText() === v) {
found= true;
}
return !found;
});
return found;
}
private fetchWithin(input: Element[], s:string): Element[] {
var elements: Element[] = [];
for (var i = 0; i < input.length; i++) {
if (input[i].name.toLowerCase().indexOf(s) >= 0) {
elements.push(input[i]);
}
}
return elements;
}
public getEntries(filter:number=(_level.Level.Warning | _level.Level.Error)):nodes.IMarker[] {
return this.warnings.filter((entry) => {
return (entry.getLevel() & filter) !== 0;
});
}
private addEntry(node:nodes.Node, rule:lintRules.Rule, details?:string):void {
var entry = new nodes.Marker(node, rule, this.configuration[rule.id], details);
this.warnings.push(entry);
}
private getMissingNames(expected:string[], actual: string[]) : string {
expected = expected.slice(0); // clone
for (var i = 0; i < actual.length; i++) {
var k = expected.indexOf(actual[i]);
if (k !== -1) {
expected[k] = null;
}
}
var result: string = null;
for (var i = 0; i < expected.length; i++) {
var curr = expected[i];
if (curr) {
if (result === null) {
result = nls.localize('namelist.single', "'{0}'", curr);
} else {
result = nls.localize('namelist.concatenated', "{0}, '{1}'", result, curr);
}
}
}
return result;
}
public visitNode(node:nodes.Node):boolean {
switch (node.type) {
case nodes.NodeType.Stylesheet:
return this.visitStylesheet(<nodes.Stylesheet> node);
case nodes.NodeType.FontFace:
return this.visitFontFace(<nodes.FontFace> node);
case nodes.NodeType.Ruleset:
return this.visitRuleSet(<nodes.RuleSet> node);
case nodes.NodeType.SimpleSelector:
return this.visitSimpleSelector(<nodes.SimpleSelector> node);
case nodes.NodeType.Function:
return this.visitFunction(<nodes.Function> node);
case nodes.NodeType.NumericValue:
return this.visitNumericValue(<nodes.NumericValue> node);
case nodes.NodeType.Import:
return this.visitImport(<nodes.Import> node);
}
return this.visitUnknownNode(node);
}
private visitStylesheet(node: nodes.Stylesheet):boolean {
// @keyframe and it's vendor specific alternatives
// @keyframe should be included
var keyframes = new NodesByRootMap();
node.accept((node) => {
if (node instanceof nodes.Keyframe) {
var keyword = (<nodes.Keyframe> node).getKeyword();
var text = keyword.getText();
keyframes.add((<nodes.Keyframe> node).getName(), text, (text !== '@keyframes') ? keyword : null);
}
return true;
});
var expected = ['@-webkit-keyframes', '@-moz-keyframes', '@-o-keyframes'];
var addVendorSpecificWarnings = (node: nodes.Node) => {
if (needsStandard) {
var message = nls.localize('keyframes.standardrule.missing', "Always define standard rule '@keyframes' when defining keyframes.");
this.addEntry(node, lintRules.Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
}
if (missingVendorSpecific) {
var message = nls.localize('keyframes.vendorspecific.missing', "Always include all vendor specific rules: Missing: {0}", missingVendorSpecific);
this.addEntry(node, lintRules.Rules.AllVendorPrefixes, message);
}
};
for (var name in keyframes.data) {
var actual = keyframes.data[name].names;
var needsStandard = (actual.indexOf('@keyframes') === -1);
if (!needsStandard && actual.length === 1) {
continue; // only the non-vendor specific keyword is used, that's fine, no warning
}
var missingVendorSpecific = this.getMissingNames(expected, actual);
if (missingVendorSpecific || needsStandard) {
keyframes.data[name].nodes.forEach(addVendorSpecificWarnings);
}
};
return true;
}
private visitSimpleSelector(node: nodes.SimpleSelector): boolean {
var text = node.getText();
/////////////////////////////////////////////////////////////
// Lint - The universal selector (*) is known to be slow.
/////////////////////////////////////////////////////////////
if (text === '*') {
this.addEntry(node, lintRules.Rules.UniversalSelector);
}
/////////////////////////////////////////////////////////////
// Lint - Avoid id selectors
/////////////////////////////////////////////////////////////
if (text.indexOf('#') === 0) {
this.addEntry(node, lintRules.Rules.AvoidIdSelector);
}
return true;
}
private visitImport(node: nodes.Import): boolean {
/////////////////////////////////////////////////////////////
// Lint - Import statements shouldn't be used, because they aren't offering parallel downloads.
/////////////////////////////////////////////////////////////
this.addEntry(node, lintRules.Rules.ImportStatemement);
return true;
}
private visitRuleSet(node: nodes.RuleSet): boolean {
/////////////////////////////////////////////////////////////
// Lint - Don't use empty rulesets.
/////////////////////////////////////////////////////////////
var declarations = node.getDeclarations();
if (!declarations) {
// syntax error
return false;
}
if (!declarations.hasChildren()) {
this.addEntry(node.getSelectors(), lintRules.Rules.EmptyRuleSet);
}
var self = this;
var propertyTable: Element[] = [];
declarations.getChildren().forEach(function(element) {
if (element instanceof nodes.Declaration) {
var decl = <nodes.Declaration> element;
propertyTable.push(new Element(decl.getFullPropertyName(), decl));
}
}, this);
/////////////////////////////////////////////////////////////
// Don't use width or height when using padding or border.
/////////////////////////////////////////////////////////////
if ((this.fetch(propertyTable, 'width').length > 0 || this.fetch(propertyTable, 'height').length > 0) && (this.fetchWithin(propertyTable, 'padding').length > 0 || this.fetchWithin(propertyTable, 'border').length > 0)) {
var elements: Element[] = this.fetch(propertyTable, 'width');
for (var index = 0; index < elements.length; index++) {
this.addEntry(elements[index].node, lintRules.Rules.NoWidthOrHeightWhenPaddingOrBorder);
}
elements = this.fetch(propertyTable, 'height');
for (var index = 0; index < elements.length; index++) {
this.addEntry(elements[index].node, lintRules.Rules.NoWidthOrHeightWhenPaddingOrBorder);
}
elements = this.fetchWithin(propertyTable, 'padding');
for (var index = 0; index < elements.length; index++) {
this.addEntry(elements[index].node, lintRules.Rules.NoWidthOrHeightWhenPaddingOrBorder);
}
elements = this.fetchWithin(propertyTable, 'border');
for (var index = 0; index < elements.length; index++) {
this.addEntry(elements[index].node, lintRules.Rules.NoWidthOrHeightWhenPaddingOrBorder);
}
}
/////////////////////////////////////////////////////////////
// Properties ignored due to display
/////////////////////////////////////////////////////////////
// With 'display: inline', the width, height, margin-top, margin-bottom, and float properties have no effect
var displayElems = this.fetchWithValue(propertyTable, 'display', 'inline');
if (displayElems.length > 0) {
[ 'width', 'height', 'margin-top', 'margin-bottom', 'float'].forEach(function(prop) {
var elem = self.fetch(propertyTable, prop);
for (var index = 0; index < elem.length; index++) {
self.addEntry(elem[index].node, lintRules.Rules.PropertyIgnoredDueToDisplay);
}
});
}
// With 'display: inline-block', 'float' has no effect
var displayElems = this.fetchWithValue(propertyTable, 'display', 'inline-block');
if (displayElems.length > 0) {
var elem = this.fetch(propertyTable, 'float');
for (var index = 0; index < elem.length; index++) {
this.addEntry(elem[index].node, lintRules.Rules.PropertyIgnoredDueToDisplay);
}
}
// With 'display: block', 'vertical-align' has no effect
var displayElems = this.fetchWithValue(propertyTable, 'display', 'block');
if (displayElems.length > 0) {
var elem = this.fetch(propertyTable, 'vertical-align');
for (var index = 0; index < elem.length; index++) {
this.addEntry(elem[index].node, lintRules.Rules.PropertyIgnoredDueToDisplay);
}
}
/////////////////////////////////////////////////////////////
// Don't use !important
/////////////////////////////////////////////////////////////
node.accept(function(n:nodes.Node) {
if (n.type === nodes.NodeType.Prio) {
self.addEntry(n, lintRules.Rules.AvoidImportant);
}
return true;
});
/////////////////////////////////////////////////////////////
// Avoid 'float'
/////////////////////////////////////////////////////////////
var elements: Element[] = this.fetch(propertyTable, 'float');
for (var index = 0; index < elements.length; index++) {
this.addEntry(elements[index].node, lintRules.Rules.AvoidFloat);
}
/////////////////////////////////////////////////////////////
// Don't use duplicate declarations.
/////////////////////////////////////////////////////////////
for (var i = 0; i < propertyTable.length; i++) {
var element = propertyTable[i];
if (element.name.toLowerCase() !== 'background') {
var value = element.node.getValue();
if (value && value.getText()[0] !== '-') {
var elements = this.fetch(propertyTable, element.name);
if (elements.length > 1) {
for (var k = 0; k < elements.length; k++) {
var value = elements[k].node.getValue();
if (value && value.getText()[0] !== '-' && elements[k] !== element) {
this.addEntry(element.node, lintRules.Rules.DuplicateDeclarations);
}
}
}
}
}
}
/////////////////////////////////////////////////////////////
// Unknown propery & When using a vendor-prefixed gradient, make sure to use them all.
/////////////////////////////////////////////////////////////
var propertiesBySuffix = new NodesByRootMap();
var containsUnknowns = false;
declarations.getChildren().forEach((node) => {
if (this.isCSSDeclaration(node)) {
var decl = <nodes.Declaration> node;
var name = decl.getFullPropertyName();
var firstChar = name.charAt(0);
if (firstChar === '-') {
if (name.charAt(1) !== '-') { // avoid css variables
if (!languageFacts.isKnownProperty(name)) {
this.addEntry(decl.getProperty(), lintRules.Rules.UnknownVendorSpecificProperty);
}
var nonPrefixedName = decl.getNonPrefixedPropertyName();
propertiesBySuffix.add(nonPrefixedName, name, decl.getProperty());
}
} else {
if (firstChar === '*' || firstChar === '_') {
this.addEntry(decl.getProperty(), lintRules.Rules.IEStarHack);
name = name.substr(1);
}
if (!languageFacts.isKnownProperty(name)) {
this.addEntry(decl.getProperty(), lintRules.Rules.UnknownProperty);
}
propertiesBySuffix.add(name, name, null); // don't pass the node as we don't show errors on the standard
}
} else {
containsUnknowns = true;
}
});
if (!containsUnknowns) { // don't perform this test if there are
var addVendorSpecificWarnings = (node: nodes.Node) => {
if (needsStandard) {
var message = nls.localize('property.standard.missing', "Also define the standard property '{0}' for compatibility", suffix);
this.addEntry(node, lintRules.Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
}
if (missingVendorSpecific) {
var message = nls.localize('property.vendorspecific.missing', "Always include all vendor specific properties: Missing: {0}", missingVendorSpecific);
this.addEntry(node, lintRules.Rules.AllVendorPrefixes, message);
}
};
for (var suffix in propertiesBySuffix.data) {
var entry = propertiesBySuffix.data[suffix];
var actual = entry.names;
var needsStandard = languageFacts.isKnownProperty(suffix) && (actual.indexOf(suffix) === -1);
if (!needsStandard && actual.length === 1) {
continue; // only the non-vendor specific rule is used, that's fine, no warning
}
var expected : string[] = [];
for (var i = 0, len = LintVisitor.prefixes.length; i < len; i++) {
var prefix = LintVisitor.prefixes[i];
if (languageFacts.isKnownProperty(prefix + suffix)) {
expected.push(prefix + suffix);
}
}
var missingVendorSpecific = this.getMissingNames(expected, actual);
if (missingVendorSpecific || needsStandard) {
entry.nodes.forEach(addVendorSpecificWarnings);
}
}
}
return true;
}
private visitNumericValue(node:nodes.NumericValue): boolean {
/////////////////////////////////////////////////////////////
// 0 has no following unit
/////////////////////////////////////////////////////////////
var value = node.getValue();
if(value.unit === '%') {
return true;
}
if(parseFloat(value.value) === 0.0 && !!value.unit) {
this.addEntry(node, lintRules.Rules.ZeroWithUnit);
}
return true;
}
private visitFontFace(node: nodes.FontFace): boolean {
var declarations = node.getDeclarations();
if (!declarations) {
// syntax error
return;
}
var definesSrc = false, definesFontFamily = false;
var containsUnknowns = false;
declarations.getChildren().forEach((node) => {
if (this.isCSSDeclaration(node)) {
var name = ((<nodes.Declaration> node).getProperty().getName().toLocaleLowerCase());
if (name === 'src') { definesSrc = true; }
if (name === 'font-family') { definesFontFamily = true; }
} else {
containsUnknowns = true;
}
});
if (!containsUnknowns && (!definesSrc || !definesFontFamily)) {
this.addEntry(node, lintRules.Rules.RequiredPropertiesForFontFace);
}
return true;
}
private isCSSDeclaration(node:nodes.Node): boolean {
if (node instanceof nodes.Declaration) {
if (!(<nodes.Declaration> node).getValue()) {
return false;
}
var property = (<nodes.Declaration> node).getProperty();
if (!property || property.getIdentifier().containsInterpolation()) {
return false;
}
return true;
}
return false;
}
private visitUnknownNode(node:nodes.Node):boolean {
// Rule: #eeff00 or #ef0
if(node.type === nodes.NodeType.HexColorValue) {
var text = node.getText();
if(text.length !== 7 && text.length !== 4) {
this.addEntry(node, lintRules.Rules.HexColorLength);
}
}
return true;
}
private visitFunction(node:nodes.Function):boolean {
var fnName = node.getName().toLowerCase(),
expectedAttrCount = -1,
actualAttrCount = 0;
switch(fnName) {
case 'rgb(':
case 'hsl(':
expectedAttrCount = 3;
break;
case 'rgba(':
case 'hsla(':
expectedAttrCount = 4;
break;
}
if(expectedAttrCount !== -1) {
node.getArguments().accept((n) => {
if(n instanceof nodes.BinaryExpression) {
actualAttrCount += 1;
return false;
}
return true;
});
if(actualAttrCount !== expectedAttrCount) {
this.addEntry(node, lintRules.Rules.ArgsInColorFunction);
}
}
return true;
}
}

View File

@@ -0,0 +1,148 @@
/*---------------------------------------------------------------------------------------------
* 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 { IJSONSchema } from 'vs/base/common/jsonSchema';
import nodes = require('vs/languages/css/common/parser/cssNodes');
import nls = require('vs/nls');
import configurationRegistry = require('vs/platform/configuration/common/configurationRegistry');
import _level = require('vs/languages/css/common/level');
var Warning = 'warning';
var Error = 'error';
var Ignore = 'ignore';
export class Rule implements nodes.IRule {
public constructor(public id: string, public message: string, public defaultValue: string) {
// nothing to do
}
public getConfiguration(): IJSONSchema {
return {
type: 'string',
enum: [Ignore, Warning, Error],
default: this.defaultValue,
description: this.message
};
}
}
export var Rules = {
AllVendorPrefixes: new Rule('compatibleVendorPrefixes', nls.localize('rule.vendorprefixes.all', "When using a vendor-specific prefix make sure to also include all other vendor-specific properties"), Ignore),
IncludeStandardPropertyWhenUsingVendorPrefix: new Rule('vendorPrefix', nls.localize('rule.standardvendorprefix.all', "When using a vendor-specific prefix also include the standard property"), Warning),
DuplicateDeclarations: new Rule('duplicateProperties', nls.localize('rule.duplicateDeclarations', "Do not use duplicate style definitions"), Ignore),
EmptyRuleSet: new Rule('emptyRules', nls.localize('rule.emptyRuleSets', "Do not use empty rulesets"), Warning),
ImportStatemement: new Rule('importStatement', nls.localize('rule.importDirective', "Import statements do not load in parallel"), Ignore),
NoWidthOrHeightWhenPaddingOrBorder: new Rule('boxModel', nls.localize('rule.withHeightAndBorderPadding', "Do not use width or height when using padding or border"), Ignore),
UniversalSelector: new Rule('universalSelector', nls.localize('rule.universalSelector', "The universal selector (*) is known to be slow"), Ignore),
ZeroWithUnit: new Rule('zeroUnits', nls.localize('rule.zeroWidthUnit', "No unit for zero needed"), Ignore),
RequiredPropertiesForFontFace: new Rule('fontFaceProperties', nls.localize('rule.fontFaceProperties', "@font-face rule must define 'src' and 'font-family' properties"), Warning),
HexColorLength: new Rule('hexColorLength', nls.localize('rule.hexColor', "Hex colors must consist of three or six hex numbers"), Error),
ArgsInColorFunction: new Rule('argumentsInColorFunction', nls.localize('rule.colorFunction', "Invalid number of parameters"), Error),
UnknownProperty: new Rule('unknownProperties', nls.localize('rule.unknownProperty', "Unknown property."), Warning),
IEStarHack: new Rule('ieHack', nls.localize('rule.ieHack', "IE hacks are only necessary when supporting IE7 and older"), Ignore),
UnknownVendorSpecificProperty: new Rule('unknownVendorSpecificProperties', nls.localize('rule.unknownVendorSpecificProperty', "Unknown vendor specific property."), Ignore),
PropertyIgnoredDueToDisplay: new Rule('propertyIgnoredDueToDisplay', nls.localize('rule.propertyIgnoredDueToDisplay', "Property is ignored due to the display. E.g. with 'display: inline', the width, height, margin-top, margin-bottom, and float properties have no effect"), Warning),
AvoidImportant: new Rule('important', nls.localize('rule.avoidImportant', "Avoid using !important. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored."), Ignore),
AvoidFloat: new Rule('float', nls.localize('rule.avoidFloat', "Avoid using 'float'. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes."), Ignore),
AvoidIdSelector: new Rule('idSelector', nls.localize('rule.avoidIdSelector', "Selectors should not contain IDs because these rules are too tightly coupled with the HTML."), Ignore),
};
export function getConfigurationProperties(keyPrefix: string): { [path:string]:configurationRegistry.IConfigurationNode } {
var properties: { [path:string]:configurationRegistry.IConfigurationNode; } = {};
properties[keyPrefix + '.validate'] = {
type: 'boolean',
default: true,
description: nls.localize('enableValidation', 'Enables or disables all validations')
}
for (var ruleName in Rules) {
var rule = Rules[ruleName];
properties[keyPrefix + '.lint.' + rule.id] = rule.getConfiguration();
}
return properties;
}
export interface IConfigurationSettings {
[ruleId:string] : _level.Level
}
export function sanitize(conf:any): IConfigurationSettings {
var settings: IConfigurationSettings = {};
for (var ruleName in Rules) {
var rule = Rules[ruleName];
var level = _level.toLevel(conf[rule.id]);
if (level) {
settings[rule.id] = level;
}
}
return settings;
}
/* old rules
'duplicate-background-images' : {
'type': 'string',
'enum': ['ignore', 'warning', 'error'],
'default': 'ignore',
'description': nls.localize('duplicateBackgroundImages', "Every background-image should be unique. Use a common class for e.g. sprites.")
},
'gradients' : {
'type': 'string',
'enum': ['ignore', 'warning', 'error'],
'default': 'warning',
'description': nls.localize('gradients', "When using a vendor-prefixed gradient, make sure to use them all.")
},
'outline-none' : {
'type': 'string',
'enum': ['ignore', 'warning', 'error'],
'default': 'warning',
'description': nls.localize('outlineNone', "Use of outline: none or outline: 0 should be limited to :focus rules.")
},
'overqualified-elements' : {
'type': 'string',
'enum': ['ignore', 'warning', 'error'],
'default': 'ignore',
'description': nls.localize('overqualifiedElements', "Don't use classes or IDs with elements (a.foo or a#foo).")
},
'qualified-headings' : {
'type': 'string',
'enum': ['ignore', 'warning', 'error'],
'default': 'ignore',
'description': nls.localize('qualifiedHeadings', "Headings should not be qualified (namespaced).")
},
'regex-selectors' : {
'type': 'string',
'enum': ['ignore', 'warning', 'error'],
'default': 'ignore',
'description': nls.localize('regexSelectors', "Selectors that look like regular expressions are slow and should be avoided.")
},
'shorthand' : {
'type': 'string',
'enum': ['ignore', 'warning', 'error'],
'default': 'ignore',
'description': nls.localize('shorthand', "Use shorthand properties where possible.")
},
'text-indent' : {
'type': 'string',
'enum': ['ignore', 'warning', 'error'],
'default': 'ignore',
'description': nls.localize('textIndent', "Checks for text indent less than -99px.")
},
'unique-headings' : {
'type': 'string',
'enum': ['ignore', 'warning', 'error'],
'default': 'ignore',
'description': nls.localize('uniqueHeadings', "Headings should be defined only once.")
},
'unqualified-attributes' : {
'type': 'string',
'enum': ['ignore', 'warning', 'error'],
'default': 'ignore',
'description': nls.localize('unqualifiedAttributes', "Unqualified attribute selectors are known to be slow.")
},
*/

View File

@@ -0,0 +1,86 @@
/*---------------------------------------------------------------------------------------------
* 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 nodes = require('vs/languages/css/common/parser/cssNodes');
import _symbols = require('vs/languages/css/common/parser/cssSymbols');
export function findDeclaration(stylesheet:nodes.Node, offset:number):nodes.Node {
var symbols = new _symbols.Symbols(stylesheet);
var node = nodes.getNodeAtOffset(stylesheet, offset);
if (!node) {
return null;
}
var symbol = symbols.findSymbolFromNode(node);
if(!symbol) {
return null;
}
return symbol.node;
}
export interface IOccurrence {
kind:string;
type:nodes.ReferenceType;
node:nodes.Node;
}
export function findOccurrences(stylesheet:nodes.Node, offset:number):IOccurrence[] {
var result:IOccurrence[] = [];
var node = nodes.getNodeAtOffset(stylesheet, offset);
if (!node || node.type === nodes.NodeType.Stylesheet) {
return result;
}
var symbols = new _symbols.Symbols(stylesheet);
var symbol = symbols.findSymbolFromNode(node);
var name = node.getText();
stylesheet.accept((candidate) => {
if (symbol) {
if (symbols.matchesSymbol(candidate, symbol)) {
result.push({
kind: getKind(candidate),
type: symbol.type,
node: candidate
});
return false;
}
} else if (node.type === candidate.type && node.length === candidate.length && name === candidate.getText()) {
// Same node type and data
result.push({
kind: getKind(candidate),
node: candidate,
type:nodes.ReferenceType.Unknown
});
}
return true;
});
return result;
}
function getKind(node:nodes.Node):string {
if (node.type === nodes.NodeType.Selector) {
return 'write';
}
if (node.parent) {
switch (node.parent.type) {
case nodes.NodeType.FunctionDeclaration:
case nodes.NodeType.MixinDeclaration:
case nodes.NodeType.Keyframe:
case nodes.NodeType.VariableDeclaration:
case nodes.NodeType.FunctionParameter:
return 'write';
}
}
return null;
}

View File

@@ -0,0 +1,431 @@
/*---------------------------------------------------------------------------------------------
* 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 nodes = require('vs/languages/css/common/parser/cssNodes');
import strings = require('vs/base/common/strings');
import HtmlContent = require('vs/base/common/htmlContent');
export interface IElement {
name?:string;
children?:IElement[];
attributes?:{[name:string]:string;};
}
export class Element implements IElement {
public name:string;
public parent:Element;
public children:Element[];
public attributes:{[name:string]:string;};
public addChild(child:Element):void {
if(child instanceof Element) {
(<Element> child).parent = this;
}
if(!this.children) {
this.children = [];
}
this.children.push(child);
}
public findRoot() : Element {
var curr = this;
while (curr.parent && !(curr.parent instanceof RootElement)) {
curr = curr.parent;
}
return curr;
}
public removeChild(child: Element) : boolean {
if (this.children) {
var index = this.children.indexOf(child);
if (index !== -1) {
this.children.splice(index, 1);
return true;
}
}
return false;
}
public addAttr(name:string, value:string):void {
if(!this.attributes) {
this.attributes = {};
}
if(this.attributes.hasOwnProperty(name)) {
this.attributes[name] += ' ' + value;
} else {
this.attributes[name] = value;
}
}
public clone(cloneChildren: boolean = true): Element {
var elem = new Element();
elem.name = this.name;
if (this.attributes) {
elem.attributes = {};
for (var key in this.attributes) {
elem.addAttr(key, this.attributes[key]);
}
}
if (cloneChildren && this.children) {
elem.children = [];
for (var index = 0; index < this.children.length; index++) {
elem.addChild(this.children[index].clone());
}
}
return elem;
}
public cloneWithParent(): Element {
var clone = this.clone(false);
if (this.parent && !(this.parent instanceof RootElement)) {
var parentClone = this.parent.cloneWithParent();
parentClone.addChild(clone);
}
return clone;
}
}
export class RootElement extends Element {
}
export class LabelElement extends Element {
constructor(label:string) {
super();
this.name = label;
}
}
class HtmlPrinter {
constructor(public quote:string) {
// empty
}
public print(element:IElement):HtmlContent.IHTMLContentElement[] {
if(element instanceof RootElement) {
return this.doPrint(element.children);
} else {
return this.doPrint([element]);
}
}
private doPrint(elements:IElement[]):HtmlContent.IHTMLContentElement[] {
var root:HtmlContent.IHTMLContentElement = { children: <HtmlContent.IHTMLContentElement[]> []},
parent = root;
while(elements.length > 0) {
var element = elements.shift(),
content = this.doPrintElement(element);
parent.children.push(content);
if(element.children) {
elements.push.apply(elements, element.children);
parent = content;
}
}
return root.children;
}
private doPrintElement(element:IElement):HtmlContent.IHTMLContentElement {
// special case: a simple label
if(element instanceof LabelElement) {
return {
tagName: 'ul',
children: [{
tagName: 'li',
children: [{
tagName: 'span',
className: 'label',
text: element.name
}]
}]
};
}
// the real deal
var children:HtmlContent.IHTMLContentElement[] = [{
isText: true,
text: '<'
}];
// element name
if(element.name) {
children.push({
tagName: 'span',
className: 'name',
text: element.name
});
} else {
children.push({
tagName: 'span',
text: 'element'
});
}
// attributes
if(element.attributes) {
Object.keys(element.attributes).forEach((attr) => {
children.push({
isText: true,
text: ' '
});
children.push({
tagName: 'span',
className: 'key',
text: attr
});
var value = element.attributes[attr];
if(value) {
children.push({
isText: true,
text: '='
});
children.push({
tagName: 'span',
className: 'value',
text: quotes.ensure(value, this.quote)
});
}
});
}
children.push({
isText: true,
text: '>'
});
return {
tagName: 'ul',
children: [{
tagName: 'li',
children: children
}]
};
}
}
namespace quotes {
export function ensure(value:string, which:string):string {
return which + remove(value) + which;
}
export function remove(value:string):string {
value = strings.trim(value, '\'');
value = strings.trim(value, '"');
return value;
}
}
export function toElement(node:nodes.SimpleSelector, parentElement?: Element):Element {
var result = new Element();
node.getChildren().forEach((child) => {
switch(child.type) {
case nodes.NodeType.SelectorCombinator:
if (parentElement) {
var segments = child.getText().split('&');
if (segments.length === 1) {
// should not happen
result.name = segments[0];
break;
}
result = parentElement.cloneWithParent();
if (segments[0]) {
var root = result.findRoot();
root.name = segments[0] + root.name;
}
for (var i = 1; i < segments.length; i++) {
if (i > 1) {
var clone = parentElement.cloneWithParent();
result.addChild(clone.findRoot());
result = clone;
}
result.name += segments[i];
}
}
break;
case nodes.NodeType.SelectorPlaceholder:
case nodes.NodeType.ElementNameSelector:
var text = child.getText();
result.name = text === '*' ? 'element' : text;
break;
case nodes.NodeType.ClassSelector:
result.addAttr('class', child.getText().substring(1));
break;
case nodes.NodeType.IdentifierSelector:
result.addAttr('id', child.getText().substring(1));
break;
case nodes.NodeType.MixinDeclaration:
result.addAttr('class', (<nodes.MixinDeclaration> child).getName());
break;
case nodes.NodeType.PseudoSelector:
result.addAttr(child.getText(), strings.empty);
break;
case nodes.NodeType.AttributeSelector:
var expr = <nodes.BinaryExpression> child.getChildren()[0];
if (expr) {
if (expr.getRight()) {
var value:string;
switch (expr.getOperator().getText()) {
case '|=':
// excatly or followed by -words
value = strings.format('{0}-\u2026', quotes.remove(expr.getRight().getText()));
break;
case '^=':
// prefix
value = strings.format('{0}\u2026', quotes.remove(expr.getRight().getText()));
break;
case '$=':
// suffix
value = strings.format('\u2026{0}', quotes.remove(expr.getRight().getText()));
break;
case '~=':
// one of a list of words
value = strings.format(' \u2026 {0} \u2026 ', quotes.remove(expr.getRight().getText()));
break;
case '*=':
// substring
value = strings.format('\u2026{0}\u2026', quotes.remove(expr.getRight().getText()));
break;
default:
value = quotes.remove(expr.getRight().getText());
break;
}
}
result.addAttr(expr.getLeft().getText(), value);
}
break;
}
});
return result;
}
export function simpleSelectorToHtml(node:nodes.SimpleSelector):HtmlContent.IHTMLContentElement {
var element = toElement(node);
var body = new HtmlPrinter('"').print(element);
return {
tagName: 'span',
className: 'css-selector-hover',
children: body
};
}
class SelectorElementBuilder {
private prev:nodes.Node;
private element:Element;
public constructor(element:Element) {
this.prev = null;
this.element = element;
}
public processSelector(selector:nodes.Selector) : void {
var parentElement : Element = null;
if (!(this.element instanceof RootElement)) {
if (selector.getChildren().some((c) => c.hasChildren() && c.getChild(0).type === nodes.NodeType.SelectorCombinator)) {
var curr = this.element.findRoot();
if (curr.parent instanceof RootElement) {
parentElement = this.element;
this.element = curr.parent;
this.element.removeChild(curr);
this.prev = null;
}
}
}
selector.getChildren().forEach((selectorChild) => {
if (selectorChild instanceof nodes.SimpleSelector) {
if (this.prev instanceof nodes.SimpleSelector) {
var labelElement = new LabelElement('\u2026');
this.element.addChild(labelElement);
this.element = labelElement;
} else if (this.prev && (this.prev.matches('+') || this.prev.matches('~'))) {
this.element = <Element> this.element.parent;
}
if (this.prev && this.prev.matches('~')) {
this.element.addChild(toElement(<nodes.SimpleSelector> selectorChild));
this.element.addChild(new LabelElement('\u22EE'));
}
var thisElement = toElement(<nodes.SimpleSelector> selectorChild, parentElement);
var root = thisElement.findRoot();
this.element.addChild(root);
this.element = thisElement;
}
if (selectorChild instanceof nodes.SimpleSelector ||
selectorChild.type === nodes.NodeType.SelectorCombinatorParent ||
selectorChild.type === nodes.NodeType.SelectorCombinatorSibling ||
selectorChild.type === nodes.NodeType.SelectorCombinatorAllSiblings) {
this.prev = selectorChild;
}
});
}
}
function isNewSelectorContext(node: nodes.Node): boolean {
switch (node.type) {
case nodes.NodeType.MixinDeclaration:
case nodes.NodeType.Stylesheet:
return true;
}
return false;
}
export function selectorToElement(node:nodes.Selector):Element {
var root: Element = new RootElement();
var parentRuleSets: nodes.RuleSet[] = [];
if (node.getParent() instanceof nodes.RuleSet) {
var parent = node.getParent().getParent(); // parent of the selector's ruleset
while (parent && !isNewSelectorContext(parent)) {
if (parent instanceof nodes.RuleSet) {
parentRuleSets.push(<nodes.RuleSet> parent);
}
parent = parent.getParent();
}
}
var builder = new SelectorElementBuilder(root);
for (var i = parentRuleSets.length - 1; i >= 0; i--) {
var selector = parentRuleSets[i].getSelectors().getChild(0);
if (selector) {
builder.processSelector(selector);
}
}
builder.processSelector(node);
return root;
}
export function selectorToHtml(node:nodes.Selector):HtmlContent.IHTMLContentElement {
var root = selectorToElement(node);
var body = new HtmlPrinter('"').print(root);
return {
tagName: 'span',
className: 'css-selector-hover',
children: body
};
}

View File

@@ -0,0 +1,180 @@
/*---------------------------------------------------------------------------------------------
* 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 nodes = require('vs/languages/css/common/parser/cssNodes');
import service = require('vs/languages/css/common/services/cssLanguageService');
import network = require('vs/base/common/network');
import languageFacts = require('vs/languages/css/common/services/languageFacts');
export enum Type {
Url,
Percentage,
Length,
Number,
Time,
Angle,
Color,
Identifier,
Enum,
Unknown
}
export interface IType {
isSimpleType():boolean;
}
export class SimpleType implements IType {
constructor(public type:Type) {
// empty
}
public isSimpleType():boolean {
return true;
}
public static Color = new SimpleType(Type.Color);
public static Identifier = new SimpleType(Type.Identifier);
public static Url = new SimpleType(Type.Url);
public static Unknown = new SimpleType(Type.Unknown);
}
export class MultiType implements IType {
constructor(public types:IType[]) {
// empty
}
public isSimpleType():boolean {
return false;
}
}
export function typeAtPosition(service:service.ILanguageService, resource:network.URL, offset:number):IType {
return null;
}
export function typeFromNode(node:nodes.Node):IType {
if(!node) {
return SimpleType.Unknown;
}
switch(node.type) {
case nodes.NodeType.Expression: return typeFromExpression(<nodes.Expression> node);
case nodes.NodeType.BinaryExpression: return typeFromBinaryExpression(<nodes.BinaryExpression> node);
case nodes.NodeType.Term: return typeFromTerm(<nodes.Term> node);
case nodes.NodeType.Function: return typeFromFunction(<nodes.Function> node);
case nodes.NodeType.NumericValue: return typeFromNumeric(<nodes.NumericValue> node);
case nodes.NodeType.HexColorValue: return SimpleType.Color;
case nodes.NodeType.Identifier: return typeFromLiteral(node);
case nodes.NodeType.Function: return typeFromFunction(<nodes.Function> node);
case nodes.NodeType.FunctionArgument: return typeFromFunctionArgument(<nodes.FunctionArgument> node);
}
return SimpleType.Unknown;
}
function typeFromExpression(node:nodes.Expression):IType {
var types:IType[] = node.getChildren().map((node) => {
return typeFromNode(node);
});
if(types.length === 0) {
return SimpleType.Unknown;
} else if(types.length === 1) {
return types[0];
} else {
return new MultiType(types);
}
}
function typeFromBinaryExpression(node:nodes.BinaryExpression):IType {
if(node.getRight()) {
return new MultiType([typeFromNode(node.getLeft()), typeFromNode(node.getRight())]);
} else {
return typeFromNode(node.getLeft());
}
}
function typeFromTerm(node:nodes.Term):IType {
if(!node.getExpression()) {
return SimpleType.Unknown;
} else {
return typeFromNode(node.getExpression());
}
}
function typeFromFunctionArgument(node:nodes.FunctionArgument):IType {
if(!node.getValue()) {
return SimpleType.Unknown;
} else {
return typeFromNode(node.getValue());
}
}
function typeFromFunction(node:nodes.Function):IType {
switch(node.getName()) {
case 'rgb':
case 'rgba':
case 'hsl':
case 'hsla':
return SimpleType.Color;
case 'url':
return SimpleType.Url;
}
var types:IType[] = node.getArguments().getChildren().map((node) => {
return typeFromNode(node);
});
if(types.length === 0) {
return SimpleType.Unknown;
} else if(types.length === 1) {
return types[0];
} else {
return new MultiType(types);
}
}
function typeFromNumeric(node:nodes.NumericValue):IType {
return new SimpleType((function(){
var value = node.getValue();
switch(value.unit) {
case '%':
return Type.Percentage;
case 'px':
case 'cm':
case 'mm':
case 'in':
case 'pt':
case 'pc':
return Type.Length;
case 's':
case 'ms':
return Type.Time;
case 'deg':
case 'rad':
case 'grad':
return Type.Angle;
}
return Type.Number;
}()));
}
function isColor(name:string):boolean {
return !!languageFacts.colors[name];
}
function typeFromLiteral(node:nodes.Node):IType {
if(isColor(node.getText())) {
return SimpleType.Color;
} else {
return SimpleType.Identifier;
}
}