mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-29 21:11:38 +01:00
Hello Code
This commit is contained in:
7
src/vs/languages/css/common/services/browsers.d.ts
vendored
Normal file
7
src/vs/languages/css/common/services/browsers.d.ts
vendored
Normal 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;
|
||||
10436
src/vs/languages/css/common/services/browsers.js
Normal file
10436
src/vs/languages/css/common/services/browsers.js
Normal file
File diff suppressed because it is too large
Load Diff
159
src/vs/languages/css/common/services/cssLanguageService.ts
Normal file
159
src/vs/languages/css/common/services/cssLanguageService.ts
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
498
src/vs/languages/css/common/services/intelliSense.ts
Normal file
498
src/vs/languages/css/common/services/intelliSense.ts
Normal 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';
|
||||
}
|
||||
483
src/vs/languages/css/common/services/languageFacts.ts
Normal file
483
src/vs/languages/css/common/services/languageFacts.ts
Normal 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'
|
||||
};
|
||||
|
||||
539
src/vs/languages/css/common/services/lint.ts
Normal file
539
src/vs/languages/css/common/services/lint.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
148
src/vs/languages/css/common/services/lintRules.ts
Normal file
148
src/vs/languages/css/common/services/lintRules.ts
Normal 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.")
|
||||
},
|
||||
*/
|
||||
|
||||
86
src/vs/languages/css/common/services/occurrences.ts
Normal file
86
src/vs/languages/css/common/services/occurrences.ts
Normal 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;
|
||||
}
|
||||
431
src/vs/languages/css/common/services/selectorPrinting.ts
Normal file
431
src/vs/languages/css/common/services/selectorPrinting.ts
Normal 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
|
||||
};
|
||||
}
|
||||
180
src/vs/languages/css/common/services/typeResolution.ts
Normal file
180
src/vs/languages/css/common/services/typeResolution.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user