🎉 initiate project *astro_rewrite*

This commit is contained in:
sindrekjelsrud 2023-07-19 21:31:30 +02:00
parent ffd4d5e86c
commit 2ba37bfbe3
8658 changed files with 2268794 additions and 2538 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,39 @@
import { LanguageSettings, ICompletionParticipant, DocumentContext, LanguageServiceOptions, Diagnostic, Position, CompletionList, Hover, Location, DocumentHighlight, DocumentLink, SymbolInformation, Range, CodeActionContext, Command, CodeAction, ColorInformation, Color, ColorPresentation, WorkspaceEdit, FoldingRange, SelectionRange, TextDocument, ICSSDataProvider, CSSDataV1, HoverSettings, CompletionSettings, TextEdit, CSSFormatConfiguration, DocumentSymbol } from './cssLanguageTypes';
export type Stylesheet = {};
export * from './cssLanguageTypes';
export interface LanguageService {
configure(raw?: LanguageSettings): void;
setDataProviders(useDefaultDataProvider: boolean, customDataProviders: ICSSDataProvider[]): void;
doValidation(document: TextDocument, stylesheet: Stylesheet, documentSettings?: LanguageSettings): Diagnostic[];
parseStylesheet(document: TextDocument): Stylesheet;
doComplete(document: TextDocument, position: Position, stylesheet: Stylesheet, settings?: CompletionSettings): CompletionList;
doComplete2(document: TextDocument, position: Position, stylesheet: Stylesheet, documentContext: DocumentContext, settings?: CompletionSettings): Promise<CompletionList>;
setCompletionParticipants(registeredCompletionParticipants: ICompletionParticipant[]): void;
doHover(document: TextDocument, position: Position, stylesheet: Stylesheet, settings?: HoverSettings): Hover | null;
findDefinition(document: TextDocument, position: Position, stylesheet: Stylesheet): Location | null;
findReferences(document: TextDocument, position: Position, stylesheet: Stylesheet): Location[];
findDocumentHighlights(document: TextDocument, position: Position, stylesheet: Stylesheet): DocumentHighlight[];
findDocumentLinks(document: TextDocument, stylesheet: Stylesheet, documentContext: DocumentContext): DocumentLink[];
/**
* Return statically resolved links, and dynamically resolved links if `fsProvider` is proved.
*/
findDocumentLinks2(document: TextDocument, stylesheet: Stylesheet, documentContext: DocumentContext): Promise<DocumentLink[]>;
findDocumentSymbols(document: TextDocument, stylesheet: Stylesheet): SymbolInformation[];
findDocumentSymbols2(document: TextDocument, stylesheet: Stylesheet): DocumentSymbol[];
doCodeActions(document: TextDocument, range: Range, context: CodeActionContext, stylesheet: Stylesheet): Command[];
doCodeActions2(document: TextDocument, range: Range, context: CodeActionContext, stylesheet: Stylesheet): CodeAction[];
findDocumentColors(document: TextDocument, stylesheet: Stylesheet): ColorInformation[];
getColorPresentations(document: TextDocument, stylesheet: Stylesheet, color: Color, range: Range): ColorPresentation[];
prepareRename(document: TextDocument, position: Position, stylesheet: Stylesheet): Range | undefined;
doRename(document: TextDocument, position: Position, newName: string, stylesheet: Stylesheet): WorkspaceEdit;
getFoldingRanges(document: TextDocument, context?: {
rangeLimit?: number;
}): FoldingRange[];
getSelectionRanges(document: TextDocument, positions: Position[], stylesheet: Stylesheet): SelectionRange[];
format(document: TextDocument, range: Range | undefined, options: CSSFormatConfiguration): TextEdit[];
}
export declare function getDefaultCSSDataProvider(): ICSSDataProvider;
export declare function newCSSDataProvider(data: CSSDataV1): ICSSDataProvider;
export declare function getCSSLanguageService(options?: LanguageServiceOptions): LanguageService;
export declare function getSCSSLanguageService(options?: LanguageServiceOptions): LanguageService;
export declare function getLESSLanguageService(options?: LanguageServiceOptions): LanguageService;

View file

@ -0,0 +1,74 @@
/*---------------------------------------------------------------------------------------------
* 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 { Parser } from './parser/cssParser';
import { CSSCompletion } from './services/cssCompletion';
import { CSSHover } from './services/cssHover';
import { CSSNavigation } from './services/cssNavigation';
import { CSSCodeActions } from './services/cssCodeActions';
import { CSSValidation } from './services/cssValidation';
import { SCSSParser } from './parser/scssParser';
import { SCSSCompletion } from './services/scssCompletion';
import { LESSParser } from './parser/lessParser';
import { LESSCompletion } from './services/lessCompletion';
import { getFoldingRanges } from './services/cssFolding';
import { format } from './services/cssFormatter';
import { CSSDataManager } from './languageFacts/dataManager';
import { CSSDataProvider } from './languageFacts/dataProvider';
import { getSelectionRanges } from './services/cssSelectionRange';
import { SCSSNavigation } from './services/scssNavigation';
import { cssData } from './data/webCustomData';
export * from './cssLanguageTypes';
export function getDefaultCSSDataProvider() {
return newCSSDataProvider(cssData);
}
export function newCSSDataProvider(data) {
return new CSSDataProvider(data);
}
function createFacade(parser, completion, hover, navigation, codeActions, validation, cssDataManager) {
return {
configure: (settings) => {
validation.configure(settings);
completion.configure(settings?.completion);
hover.configure(settings?.hover);
},
setDataProviders: cssDataManager.setDataProviders.bind(cssDataManager),
doValidation: validation.doValidation.bind(validation),
parseStylesheet: parser.parseStylesheet.bind(parser),
doComplete: completion.doComplete.bind(completion),
doComplete2: completion.doComplete2.bind(completion),
setCompletionParticipants: completion.setCompletionParticipants.bind(completion),
doHover: hover.doHover.bind(hover),
format,
findDefinition: navigation.findDefinition.bind(navigation),
findReferences: navigation.findReferences.bind(navigation),
findDocumentHighlights: navigation.findDocumentHighlights.bind(navigation),
findDocumentLinks: navigation.findDocumentLinks.bind(navigation),
findDocumentLinks2: navigation.findDocumentLinks2.bind(navigation),
findDocumentSymbols: navigation.findSymbolInformations.bind(navigation),
findDocumentSymbols2: navigation.findDocumentSymbols.bind(navigation),
doCodeActions: codeActions.doCodeActions.bind(codeActions),
doCodeActions2: codeActions.doCodeActions2.bind(codeActions),
findDocumentColors: navigation.findDocumentColors.bind(navigation),
getColorPresentations: navigation.getColorPresentations.bind(navigation),
prepareRename: navigation.prepareRename.bind(navigation),
doRename: navigation.doRename.bind(navigation),
getFoldingRanges,
getSelectionRanges
};
}
const defaultLanguageServiceOptions = {};
export function getCSSLanguageService(options = defaultLanguageServiceOptions) {
const cssDataManager = new CSSDataManager(options);
return createFacade(new Parser(), new CSSCompletion(null, options, cssDataManager), new CSSHover(options && options.clientCapabilities, cssDataManager), new CSSNavigation(options && options.fileSystemProvider, false), new CSSCodeActions(cssDataManager), new CSSValidation(cssDataManager), cssDataManager);
}
export function getSCSSLanguageService(options = defaultLanguageServiceOptions) {
const cssDataManager = new CSSDataManager(options);
return createFacade(new SCSSParser(), new SCSSCompletion(options, cssDataManager), new CSSHover(options && options.clientCapabilities, cssDataManager), new SCSSNavigation(options && options.fileSystemProvider), new CSSCodeActions(cssDataManager), new CSSValidation(cssDataManager), cssDataManager);
}
export function getLESSLanguageService(options = defaultLanguageServiceOptions) {
const cssDataManager = new CSSDataManager(options);
return createFacade(new LESSParser(), new LESSCompletion(options, cssDataManager), new CSSHover(options && options.clientCapabilities, cssDataManager), new CSSNavigation(options && options.fileSystemProvider, true), new CSSCodeActions(cssDataManager), new CSSValidation(cssDataManager), cssDataManager);
}

View file

@ -0,0 +1,238 @@
import { Range, Position, DocumentUri, MarkupContent, MarkupKind, Color, ColorInformation, ColorPresentation, FoldingRange, FoldingRangeKind, SelectionRange, Diagnostic, DiagnosticSeverity, CompletionItem, CompletionItemKind, CompletionList, CompletionItemTag, InsertTextFormat, DefinitionLink, SymbolInformation, SymbolKind, DocumentSymbol, Location, Hover, MarkedString, CodeActionContext, Command, CodeAction, DocumentHighlight, DocumentLink, WorkspaceEdit, TextEdit, CodeActionKind, TextDocumentEdit, VersionedTextDocumentIdentifier, DocumentHighlightKind } from 'vscode-languageserver-types';
import { TextDocument } from 'vscode-languageserver-textdocument';
export { TextDocument, Range, Position, DocumentUri, MarkupContent, MarkupKind, Color, ColorInformation, ColorPresentation, FoldingRange, FoldingRangeKind, SelectionRange, Diagnostic, DiagnosticSeverity, CompletionItem, CompletionItemKind, CompletionList, CompletionItemTag, InsertTextFormat, DefinitionLink, SymbolInformation, SymbolKind, DocumentSymbol, Location, Hover, MarkedString, CodeActionContext, Command, CodeAction, DocumentHighlight, DocumentLink, WorkspaceEdit, TextEdit, CodeActionKind, TextDocumentEdit, VersionedTextDocumentIdentifier, DocumentHighlightKind };
export type LintSettings = {
[key: string]: any;
};
export interface CompletionSettings {
triggerPropertyValueCompletion: boolean;
completePropertyWithSemicolon?: boolean;
}
export interface LanguageSettings {
validate?: boolean;
lint?: LintSettings;
completion?: CompletionSettings;
hover?: HoverSettings;
}
export interface HoverSettings {
documentation?: boolean;
references?: boolean;
}
export interface PropertyCompletionContext {
propertyName: string;
range: Range;
}
export interface PropertyValueCompletionContext {
propertyName: string;
propertyValue?: string;
range: Range;
}
export interface URILiteralCompletionContext {
uriValue: string;
position: Position;
range: Range;
}
export interface ImportPathCompletionContext {
pathValue: string;
position: Position;
range: Range;
}
export interface MixinReferenceCompletionContext {
mixinName: string;
range: Range;
}
export interface ICompletionParticipant {
onCssProperty?: (context: PropertyCompletionContext) => void;
onCssPropertyValue?: (context: PropertyValueCompletionContext) => void;
onCssURILiteralValue?: (context: URILiteralCompletionContext) => void;
onCssImportPath?: (context: ImportPathCompletionContext) => void;
onCssMixinReference?: (context: MixinReferenceCompletionContext) => void;
}
export interface DocumentContext {
resolveReference(ref: string, baseUrl: string): string | undefined;
}
/**
* Describes what LSP capabilities the client supports
*/
export interface ClientCapabilities {
/**
* The text document client capabilities
*/
textDocument?: {
/**
* Capabilities specific to completions.
*/
completion?: {
/**
* The client supports the following `CompletionItem` specific
* capabilities.
*/
completionItem?: {
/**
* Client supports the follow content formats for the documentation
* property. The order describes the preferred format of the client.
*/
documentationFormat?: MarkupKind[];
};
};
/**
* Capabilities specific to hovers.
*/
hover?: {
/**
* Client supports the follow content formats for the content
* property. The order describes the preferred format of the client.
*/
contentFormat?: MarkupKind[];
};
};
}
export declare namespace ClientCapabilities {
const LATEST: ClientCapabilities;
}
export interface LanguageServiceOptions {
/**
* Unless set to false, the default CSS data provider will be used
* along with the providers from customDataProviders.
* Defaults to true.
*/
useDefaultDataProvider?: boolean;
/**
* Provide data that could enhance the service's understanding of
* CSS property / at-rule / pseudo-class / pseudo-element
*/
customDataProviders?: ICSSDataProvider[];
/**
* Abstract file system access away from the service.
* Used for dynamic link resolving, path completion, etc.
*/
fileSystemProvider?: FileSystemProvider;
/**
* Describes the LSP capabilities the client supports.
*/
clientCapabilities?: ClientCapabilities;
}
export type EntryStatus = 'standard' | 'experimental' | 'nonstandard' | 'obsolete';
export interface IReference {
name: string;
url: string;
}
export interface IPropertyData {
name: string;
description?: string | MarkupContent;
browsers?: string[];
restrictions?: string[];
status?: EntryStatus;
syntax?: string;
values?: IValueData[];
references?: IReference[];
relevance?: number;
}
export interface IAtDirectiveData {
name: string;
description?: string | MarkupContent;
browsers?: string[];
status?: EntryStatus;
references?: IReference[];
}
export interface IPseudoClassData {
name: string;
description?: string | MarkupContent;
browsers?: string[];
status?: EntryStatus;
references?: IReference[];
}
export interface IPseudoElementData {
name: string;
description?: string | MarkupContent;
browsers?: string[];
status?: EntryStatus;
references?: IReference[];
}
export interface IValueData {
name: string;
description?: string | MarkupContent;
browsers?: string[];
status?: EntryStatus;
references?: IReference[];
}
export interface CSSDataV1 {
version: 1 | 1.1;
properties?: IPropertyData[];
atDirectives?: IAtDirectiveData[];
pseudoClasses?: IPseudoClassData[];
pseudoElements?: IPseudoElementData[];
}
export interface ICSSDataProvider {
provideProperties(): IPropertyData[];
provideAtDirectives(): IAtDirectiveData[];
providePseudoClasses(): IPseudoClassData[];
providePseudoElements(): IPseudoElementData[];
}
export declare enum FileType {
/**
* The file type is unknown.
*/
Unknown = 0,
/**
* A regular file.
*/
File = 1,
/**
* A directory.
*/
Directory = 2,
/**
* A symbolic link to a file.
*/
SymbolicLink = 64
}
export interface FileStat {
/**
* The type of the file, e.g. is a regular file, a directory, or symbolic link
* to a file.
*/
type: FileType;
/**
* The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
*/
ctime: number;
/**
* The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
*/
mtime: number;
/**
* The size in bytes.
*/
size: number;
}
export interface FileSystemProvider {
stat(uri: DocumentUri): Promise<FileStat>;
readDirectory?(uri: DocumentUri): Promise<[string, FileType][]>;
}
export interface CSSFormatConfiguration {
/** indentation size. Default: 4 */
tabSize?: number;
/** Whether to use spaces or tabs */
insertSpaces?: boolean;
/** end with a newline: Default: false */
insertFinalNewline?: boolean;
/** separate selectors with newline (e.g. "a,\nbr" or "a, br"): Default: true */
newlineBetweenSelectors?: boolean;
/** add a new line after every css rule: Default: true */
newlineBetweenRules?: boolean;
/** ensure space around selector separators: '>', '+', '~' (e.g. "a>b" -> "a > b"): Default: false */
spaceAroundSelectorSeparator?: boolean;
/** put braces on the same line as rules (`collapse`), or put braces on own line, Allman / ANSI style (`expand`). Default `collapse` */
braceStyle?: 'collapse' | 'expand';
/** whether existing line breaks before elements should be preserved. Default: true */
preserveNewLines?: boolean;
/** maximum number of line breaks to be preserved in one chunk. Default: unlimited */
maxPreserveNewLines?: number;
/** maximum amount of characters per line (0/undefined = disabled). Default: disabled. */
wrapLineLength?: number;
/** add indenting whitespace to empty lines. Default: false */
indentEmptyLines?: boolean;
/** @deprecated Use newlineBetweenSelectors instead*/
selectorSeparatorNewline?: boolean;
}

View file

@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* 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 { Range, Position, DocumentUri, MarkupContent, MarkupKind, Color, ColorInformation, ColorPresentation, FoldingRange, FoldingRangeKind, SelectionRange, Diagnostic, DiagnosticSeverity, CompletionItem, CompletionItemKind, CompletionList, CompletionItemTag, InsertTextFormat, SymbolInformation, SymbolKind, DocumentSymbol, Location, Hover, MarkedString, CodeActionContext, Command, CodeAction, DocumentHighlight, DocumentLink, WorkspaceEdit, TextEdit, CodeActionKind, TextDocumentEdit, VersionedTextDocumentIdentifier, DocumentHighlightKind } from 'vscode-languageserver-types';
import { TextDocument } from 'vscode-languageserver-textdocument';
export { TextDocument, Range, Position, DocumentUri, MarkupContent, MarkupKind, Color, ColorInformation, ColorPresentation, FoldingRange, FoldingRangeKind, SelectionRange, Diagnostic, DiagnosticSeverity, CompletionItem, CompletionItemKind, CompletionList, CompletionItemTag, InsertTextFormat, SymbolInformation, SymbolKind, DocumentSymbol, Location, Hover, MarkedString, CodeActionContext, Command, CodeAction, DocumentHighlight, DocumentLink, WorkspaceEdit, TextEdit, CodeActionKind, TextDocumentEdit, VersionedTextDocumentIdentifier, DocumentHighlightKind };
export var ClientCapabilities;
(function (ClientCapabilities) {
ClientCapabilities.LATEST = {
textDocument: {
completion: {
completionItem: {
documentationFormat: [MarkupKind.Markdown, MarkupKind.PlainText]
}
},
hover: {
contentFormat: [MarkupKind.Markdown, MarkupKind.PlainText]
}
}
};
})(ClientCapabilities || (ClientCapabilities = {}));
export var FileType;
(function (FileType) {
/**
* The file type is unknown.
*/
FileType[FileType["Unknown"] = 0] = "Unknown";
/**
* A regular file.
*/
FileType[FileType["File"] = 1] = "File";
/**
* A directory.
*/
FileType[FileType["Directory"] = 2] = "Directory";
/**
* A symbolic link to a file.
*/
FileType[FileType["SymbolicLink"] = 64] = "SymbolicLink";
})(FileType || (FileType = {}));

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,142 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export const positionKeywords = {
'bottom': 'Computes to 100% for the vertical position if one or two values are given, otherwise specifies the bottom edge as the origin for the next offset.',
'center': 'Computes to 50% (left 50%) for the horizontal position if the horizontal position is not otherwise specified, or 50% (top 50%) for the vertical position if it is.',
'left': 'Computes to 0% for the horizontal position if one or two values are given, otherwise specifies the left edge as the origin for the next offset.',
'right': 'Computes to 100% for the horizontal position if one or two values are given, otherwise specifies the right edge as the origin for the next offset.',
'top': 'Computes to 0% for the vertical position if one or two values are given, otherwise specifies the top edge as the origin for the next offset.'
};
export const repeatStyleKeywords = {
'no-repeat': 'Placed once and not repeated in this direction.',
'repeat': 'Repeated in this direction as often as needed to cover the background painting area.',
'repeat-x': 'Computes to repeat no-repeat.',
'repeat-y': 'Computes to no-repeat repeat.',
'round': 'Repeated as often as will fit within the background positioning area. If it doesnt fit a whole number of times, it is rescaled so that it does.',
'space': 'Repeated as often as will fit within the background positioning area without being clipped and then the images are spaced out to fill the area.'
};
export const lineStyleKeywords = {
'dashed': 'A series of square-ended dashes.',
'dotted': 'A series of round dots.',
'double': 'Two parallel solid lines with some space between them.',
'groove': 'Looks as if it were carved in the canvas.',
'hidden': 'Same as none, but has different behavior in the border conflict resolution rules for border-collapsed tables.',
'inset': 'Looks as if the content on the inside of the border is sunken into the canvas.',
'none': 'No border. Color and width are ignored.',
'outset': 'Looks as if the content on the inside of the border is coming out of the canvas.',
'ridge': 'Looks as if it were coming out of the canvas.',
'solid': 'A single line segment.'
};
export const lineWidthKeywords = ['medium', 'thick', 'thin'];
export const boxKeywords = {
'border-box': 'The background is painted within (clipped to) the border box.',
'content-box': 'The background is painted within (clipped to) the content box.',
'padding-box': 'The background is painted within (clipped to) the padding box.'
};
export const geometryBoxKeywords = {
'margin-box': 'Uses the margin box as reference box.',
'fill-box': 'Uses the object bounding box as reference box.',
'stroke-box': 'Uses the stroke bounding box as reference box.',
'view-box': 'Uses the nearest SVG viewport as reference box.'
};
export const cssWideKeywords = {
'initial': 'Represents the value specified as the propertys initial value.',
'inherit': 'Represents the computed value of the property on the elements parent.',
'unset': 'Acts as either `inherit` or `initial`, depending on whether the property is inherited or not.'
};
export const cssWideFunctions = {
'var()': 'Evaluates the value of a custom variable.',
'calc()': 'Evaluates an mathematical expression. The following operators can be used: + - * /.'
};
export const imageFunctions = {
'url()': 'Reference an image file by URL',
'image()': 'Provide image fallbacks and annotations.',
'-webkit-image-set()': 'Provide multiple resolutions. Remember to use unprefixed image-set() in addition.',
'image-set()': 'Provide multiple resolutions of an image and const the UA decide which is most appropriate in a given situation.',
'-moz-element()': 'Use an element in the document as an image. Remember to use unprefixed element() in addition.',
'element()': 'Use an element in the document as an image.',
'cross-fade()': 'Indicates the two images to be combined and how far along in the transition the combination is.',
'-webkit-gradient()': 'Deprecated. Use modern linear-gradient() or radial-gradient() instead.',
'-webkit-linear-gradient()': 'Linear gradient. Remember to use unprefixed version in addition.',
'-moz-linear-gradient()': 'Linear gradient. Remember to use unprefixed version in addition.',
'-o-linear-gradient()': 'Linear gradient. Remember to use unprefixed version in addition.',
'linear-gradient()': 'A linear gradient is created by specifying a straight gradient line, and then several colors placed along that line.',
'-webkit-repeating-linear-gradient()': 'Repeating Linear gradient. Remember to use unprefixed version in addition.',
'-moz-repeating-linear-gradient()': 'Repeating Linear gradient. Remember to use unprefixed version in addition.',
'-o-repeating-linear-gradient()': 'Repeating Linear gradient. Remember to use unprefixed version in addition.',
'repeating-linear-gradient()': 'Same as linear-gradient, except the color-stops are repeated infinitely in both directions, with their positions shifted by multiples of the difference between the last specified color-stops position and the first specified color-stops position.',
'-webkit-radial-gradient()': 'Radial gradient. Remember to use unprefixed version in addition.',
'-moz-radial-gradient()': 'Radial gradient. Remember to use unprefixed version in addition.',
'radial-gradient()': 'Colors emerge from a single point and smoothly spread outward in a circular or elliptical shape.',
'-webkit-repeating-radial-gradient()': 'Repeating radial gradient. Remember to use unprefixed version in addition.',
'-moz-repeating-radial-gradient()': 'Repeating radial gradient. Remember to use unprefixed version in addition.',
'repeating-radial-gradient()': 'Same as radial-gradient, except the color-stops are repeated infinitely in both directions, with their positions shifted by multiples of the difference between the last specified color-stops position and the first specified color-stops position.'
};
export const transitionTimingFunctions = {
'ease': 'Equivalent to cubic-bezier(0.25, 0.1, 0.25, 1.0).',
'ease-in': 'Equivalent to cubic-bezier(0.42, 0, 1.0, 1.0).',
'ease-in-out': 'Equivalent to cubic-bezier(0.42, 0, 0.58, 1.0).',
'ease-out': 'Equivalent to cubic-bezier(0, 0, 0.58, 1.0).',
'linear': 'Equivalent to cubic-bezier(0.0, 0.0, 1.0, 1.0).',
'step-end': 'Equivalent to steps(1, end).',
'step-start': 'Equivalent to steps(1, start).',
'steps()': 'The first parameter specifies the number of intervals in the function. The second parameter, which is optional, is either the value “start” or “end”.',
'cubic-bezier()': 'Specifies a cubic-bezier curve. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2).',
'cubic-bezier(0.6, -0.28, 0.735, 0.045)': 'Ease-in Back. Overshoots.',
'cubic-bezier(0.68, -0.55, 0.265, 1.55)': 'Ease-in-out Back. Overshoots.',
'cubic-bezier(0.175, 0.885, 0.32, 1.275)': 'Ease-out Back. Overshoots.',
'cubic-bezier(0.6, 0.04, 0.98, 0.335)': 'Ease-in Circular. Based on half circle.',
'cubic-bezier(0.785, 0.135, 0.15, 0.86)': 'Ease-in-out Circular. Based on half circle.',
'cubic-bezier(0.075, 0.82, 0.165, 1)': 'Ease-out Circular. Based on half circle.',
'cubic-bezier(0.55, 0.055, 0.675, 0.19)': 'Ease-in Cubic. Based on power of three.',
'cubic-bezier(0.645, 0.045, 0.355, 1)': 'Ease-in-out Cubic. Based on power of three.',
'cubic-bezier(0.215, 0.610, 0.355, 1)': 'Ease-out Cubic. Based on power of three.',
'cubic-bezier(0.95, 0.05, 0.795, 0.035)': 'Ease-in Exponential. Based on two to the power ten.',
'cubic-bezier(1, 0, 0, 1)': 'Ease-in-out Exponential. Based on two to the power ten.',
'cubic-bezier(0.19, 1, 0.22, 1)': 'Ease-out Exponential. Based on two to the power ten.',
'cubic-bezier(0.47, 0, 0.745, 0.715)': 'Ease-in Sine.',
'cubic-bezier(0.445, 0.05, 0.55, 0.95)': 'Ease-in-out Sine.',
'cubic-bezier(0.39, 0.575, 0.565, 1)': 'Ease-out Sine.',
'cubic-bezier(0.55, 0.085, 0.68, 0.53)': 'Ease-in Quadratic. Based on power of two.',
'cubic-bezier(0.455, 0.03, 0.515, 0.955)': 'Ease-in-out Quadratic. Based on power of two.',
'cubic-bezier(0.25, 0.46, 0.45, 0.94)': 'Ease-out Quadratic. Based on power of two.',
'cubic-bezier(0.895, 0.03, 0.685, 0.22)': 'Ease-in Quartic. Based on power of four.',
'cubic-bezier(0.77, 0, 0.175, 1)': 'Ease-in-out Quartic. Based on power of four.',
'cubic-bezier(0.165, 0.84, 0.44, 1)': 'Ease-out Quartic. Based on power of four.',
'cubic-bezier(0.755, 0.05, 0.855, 0.06)': 'Ease-in Quintic. Based on power of five.',
'cubic-bezier(0.86, 0, 0.07, 1)': 'Ease-in-out Quintic. Based on power of five.',
'cubic-bezier(0.23, 1, 0.320, 1)': 'Ease-out Quintic. Based on power of five.'
};
export const basicShapeFunctions = {
'circle()': 'Defines a circle.',
'ellipse()': 'Defines an ellipse.',
'inset()': 'Defines an inset rectangle.',
'polygon()': 'Defines a polygon.'
};
export const units = {
'length': ['cap', 'ch', 'cm', 'cqb', 'cqh', 'cqi', 'cqmax', 'cqmin', 'cqw', 'dvb', 'dvh', 'dvi', 'dvw', 'em', 'ex', 'ic', 'in', 'lh', 'lvb', 'lvh', 'lvi', 'lvw', 'mm', 'pc', 'pt', 'px', 'q', 'rcap', 'rch', 'rem', 'rex', 'ric', 'rlh', 'svb', 'svh', 'svi', 'svw', 'vb', 'vh', 'vi', 'vmax', 'vmin', 'vw'],
'angle': ['deg', 'rad', 'grad', 'turn'],
'time': ['ms', 's'],
'frequency': ['Hz', 'kHz'],
'resolution': ['dpi', 'dpcm', 'dppx'],
'percentage': ['%', 'fr']
};
export const 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', 'picture', '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', 'const', 'video', 'wbr'];
export const svgElements = ['circle', 'clipPath', 'cursor', 'defs', 'desc', 'ellipse', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting',
'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology',
'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence', 'filter', 'foreignObject', 'g', 'hatch', 'hatchpath', 'image', 'line', 'linearGradient',
'marker', 'mask', 'mesh', 'meshpatch', 'meshrow', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'set', 'solidcolor', 'stop', 'svg', 'switch',
'symbol', 'text', 'textPath', 'tspan', 'use', 'view'];
export const pageBoxDirectives = [
'@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'
];

View file

@ -0,0 +1,590 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nodes from '../parser/cssNodes';
import * as l10n from '@vscode/l10n';
const hexColorRegExp = /(^#([0-9A-F]{3}){1,2}$)|(^#([0-9A-F]{4}){1,2}$)/i;
export const colorFunctions = [
{
label: 'rgb',
func: 'rgb($red, $green, $blue)',
insertText: 'rgb(${1:red}, ${2:green}, ${3:blue})',
desc: l10n.t('Creates a Color from red, green, and blue values.')
},
{
label: 'rgba',
func: 'rgba($red, $green, $blue, $alpha)',
insertText: 'rgba(${1:red}, ${2:green}, ${3:blue}, ${4:alpha})',
desc: l10n.t('Creates a Color from red, green, blue, and alpha values.')
},
{
label: 'rgb relative',
func: 'rgb(from $color $red $green $blue)',
insertText: 'rgb(from ${1:color} ${2:r} ${3:g} ${4:b})',
desc: l10n.t('Creates a Color from the red, green, and blue values of another Color.')
},
{
label: 'hsl',
func: 'hsl($hue, $saturation, $lightness)',
insertText: 'hsl(${1:hue}, ${2:saturation}, ${3:lightness})',
desc: l10n.t('Creates a Color from hue, saturation, and lightness values.')
},
{
label: 'hsla',
func: 'hsla($hue, $saturation, $lightness, $alpha)',
insertText: 'hsla(${1:hue}, ${2:saturation}, ${3:lightness}, ${4:alpha})',
desc: l10n.t('Creates a Color from hue, saturation, lightness, and alpha values.')
},
{
label: 'hsl relative',
func: 'hsl(from $color $hue $saturation $lightness)',
insertText: 'hsl(from ${1:color} ${2:h} ${3:s} ${4:l})',
desc: l10n.t('Creates a Color from the hue, saturation, and lightness values of another Color.')
},
{
label: 'hwb',
func: 'hwb($hue $white $black)',
insertText: 'hwb(${1:hue} ${2:white} ${3:black})',
desc: l10n.t('Creates a Color from hue, white, and black values.')
},
{
label: 'hwb relative',
func: 'hwb(from $color $hue $white $black)',
insertText: 'hwb(from ${1:color} ${2:h} ${3:w} ${4:b})',
desc: l10n.t('Creates a Color from the hue, white, and black values of another Color.')
},
{
label: 'lab',
func: 'lab($lightness $a $b)',
insertText: 'lab(${1:lightness} ${2:a} ${3:b})',
desc: l10n.t('Creates a Color from lightness, a, and b values.')
},
{
label: 'lab relative',
func: 'lab(from $color $lightness $a $b)',
insertText: 'lab(from ${1:color} ${2:l} ${3:a} ${4:b})',
desc: l10n.t('Creates a Color from the lightness, a, and b values of another Color.')
},
{
label: 'oklab',
func: 'oklab($lightness $a $b)',
insertText: 'oklab(${1:lightness} ${2:a} ${3:b})',
desc: l10n.t('Creates a Color from lightness, a, and b values.')
},
{
label: 'oklab relative',
func: 'oklab(from $color $lightness $a $b)',
insertText: 'oklab(from ${1:color} ${2:l} ${3:a} ${4:b})',
desc: l10n.t('Creates a Color from the lightness, a, and b values of another Color.')
},
{
label: 'lch',
func: 'lch($lightness $chroma $hue)',
insertText: 'lch(${1:lightness} ${2:chroma} ${3:hue})',
desc: l10n.t('Creates a Color from lightness, chroma, and hue values.')
},
{
label: 'lch relative',
func: 'lch(from $color $lightness $chroma $hue)',
insertText: 'lch(from ${1:color} ${2:l} ${3:c} ${4:h})',
desc: l10n.t('Creates a Color from the lightness, chroma, and hue values of another Color.')
},
{
label: 'oklch',
func: 'oklch($lightness $chroma $hue)',
insertText: 'oklch(${1:lightness} ${2:chroma} ${3:hue})',
desc: l10n.t('Creates a Color from lightness, chroma, and hue values.')
},
{
label: 'oklch relative',
func: 'oklch(from $color $lightness $chroma $hue)',
insertText: 'oklch(from ${1:color} ${2:l} ${3:c} ${4:h})',
desc: l10n.t('Creates a Color from the lightness, chroma, and hue values of another Color.')
},
{
label: 'color',
func: 'color($color-space $red $green $blue)',
insertText: 'color(${1|srgb,srgb-linear,display-p3,a98-rgb,prophoto-rgb,rec2020,xyx,xyz-d50,xyz-d65|} ${2:red} ${3:green} ${4:blue})',
desc: l10n.t('Creates a Color in a specific color space from red, green, and blue values.')
},
{
label: 'color relative',
func: 'color(from $color $color-space $red $green $blue)',
insertText: 'color(from ${1:color} ${2|srgb,srgb-linear,display-p3,a98-rgb,prophoto-rgb,rec2020,xyx,xyz-d50,xyz-d65|} ${3:r} ${4:g} ${5:b})',
desc: l10n.t('Creates a Color in a specific color space from the red, green, and blue values of another Color.')
},
{
label: 'color-mix',
func: 'color-mix(in $color-space, $color $percentage, $color $percentage)',
insertText: 'color-mix(in ${1|srgb,srgb-linear,lab,oklab,xyz,xyz-d50,xyz-d65|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})',
desc: l10n.t('Mix two colors together in a rectangular color space.')
},
{
label: 'color-mix hue',
func: 'color-mix(in $color-space $interpolation-method hue, $color $percentage, $color $percentage)',
insertText: 'color-mix(in ${1|hsl,hwb,lch,oklch|} ${2|shorter hue,longer hue,increasing hue,decreasing hue|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})',
desc: l10n.t('Mix two colors together in a polar color space.')
},
];
const colorFunctionNameRegExp = /^(rgb|rgba|hsl|hsla|hwb)$/i;
export const colors = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgrey: '#a9a9a9',
darkgreen: '#006400',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkslategrey: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
grey: '#808080',
green: '#008000',
greenyellow: '#adff2f',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgrey: '#d3d3d3',
lightgreen: '#90ee90',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370d8',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#d87093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
red: '#ff0000',
rebeccapurple: '#663399',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
slategrey: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd32'
};
const colorsRegExp = new RegExp(`^(${Object.keys(colors).join('|')})$`, "i");
export const colorKeywords = {
'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.',
'transparent': 'Fully transparent. This keyword can be considered a shorthand for rgba(0,0,0,0) which is its computed value.',
};
const colorKeywordsRegExp = new RegExp(`^(${Object.keys(colorKeywords).join('|')})$`, "i");
function getNumericValue(node, factor) {
const val = node.getText();
const m = val.match(/^([-+]?[0-9]*\.?[0-9]+)(%?)$/);
if (m) {
if (m[2]) {
factor = 100.0;
}
const result = parseFloat(m[1]) / factor;
if (result >= 0 && result <= 1) {
return result;
}
}
throw new Error();
}
function getAngle(node) {
const val = node.getText();
const m = val.match(/^([-+]?[0-9]*\.?[0-9]+)(deg|rad|grad|turn)?$/);
if (m) {
switch (m[2]) {
case 'deg':
return parseFloat(val) % 360;
case 'rad':
return (parseFloat(val) * 180 / Math.PI) % 360;
case 'grad':
return (parseFloat(val) * 0.9) % 360;
case 'turn':
return (parseFloat(val) * 360) % 360;
default:
if ('undefined' === typeof m[2]) {
return parseFloat(val) % 360;
}
}
}
throw new Error();
}
export function isColorConstructor(node) {
const name = node.getName();
if (!name) {
return false;
}
return colorFunctionNameRegExp.test(name);
}
export function isColorString(s) {
return hexColorRegExp.test(s) || colorsRegExp.test(s) || colorKeywordsRegExp.test(s);
}
/**
* 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) {
if (node.type === nodes.NodeType.HexColorValue) {
return true;
}
else if (node.type === nodes.NodeType.Function) {
return isColorConstructor(node);
}
else if (node.type === nodes.NodeType.Identifier) {
if (node.parent && node.parent.type !== nodes.NodeType.Term) {
return false;
}
const candidateColor = node.getText().toLowerCase();
if (candidateColor === 'none') {
return false;
}
if (colors[candidateColor]) {
return true;
}
}
return false;
}
const Digit0 = 48;
const Digit9 = 57;
const A = 65;
const F = 70;
const a = 97;
const f = 102;
export function hexDigit(charCode) {
if (charCode < Digit0) {
return 0;
}
if (charCode <= Digit9) {
return charCode - Digit0;
}
if (charCode < a) {
charCode += (a - A);
}
if (charCode >= a && charCode <= f) {
return charCode - a + 10;
}
return 0;
}
export function colorFromHex(text) {
if (text[0] !== '#') {
return null;
}
switch (text.length) {
case 4:
return {
red: (hexDigit(text.charCodeAt(1)) * 0x11) / 255.0,
green: (hexDigit(text.charCodeAt(2)) * 0x11) / 255.0,
blue: (hexDigit(text.charCodeAt(3)) * 0x11) / 255.0,
alpha: 1
};
case 5:
return {
red: (hexDigit(text.charCodeAt(1)) * 0x11) / 255.0,
green: (hexDigit(text.charCodeAt(2)) * 0x11) / 255.0,
blue: (hexDigit(text.charCodeAt(3)) * 0x11) / 255.0,
alpha: (hexDigit(text.charCodeAt(4)) * 0x11) / 255.0,
};
case 7:
return {
red: (hexDigit(text.charCodeAt(1)) * 0x10 + hexDigit(text.charCodeAt(2))) / 255.0,
green: (hexDigit(text.charCodeAt(3)) * 0x10 + hexDigit(text.charCodeAt(4))) / 255.0,
blue: (hexDigit(text.charCodeAt(5)) * 0x10 + hexDigit(text.charCodeAt(6))) / 255.0,
alpha: 1
};
case 9:
return {
red: (hexDigit(text.charCodeAt(1)) * 0x10 + hexDigit(text.charCodeAt(2))) / 255.0,
green: (hexDigit(text.charCodeAt(3)) * 0x10 + hexDigit(text.charCodeAt(4))) / 255.0,
blue: (hexDigit(text.charCodeAt(5)) * 0x10 + hexDigit(text.charCodeAt(6))) / 255.0,
alpha: (hexDigit(text.charCodeAt(7)) * 0x10 + hexDigit(text.charCodeAt(8))) / 255.0
};
}
return null;
}
export function colorFrom256RGB(red, green, blue, alpha = 1.0) {
return {
red: red / 255.0,
green: green / 255.0,
blue: blue / 255.0,
alpha
};
}
export function colorFromHSL(hue, sat, light, alpha = 1.0) {
hue = hue / 60.0;
if (sat === 0) {
return { red: light, green: light, blue: light, alpha };
}
else {
const hueToRgb = (t1, t2, hue) => {
while (hue < 0) {
hue += 6;
}
while (hue >= 6) {
hue -= 6;
}
if (hue < 1) {
return (t2 - t1) * hue + t1;
}
if (hue < 3) {
return t2;
}
if (hue < 4) {
return (t2 - t1) * (4 - hue) + t1;
}
return t1;
};
const t2 = light <= 0.5 ? (light * (sat + 1)) : (light + sat - (light * sat));
const t1 = light * 2 - t2;
return { red: hueToRgb(t1, t2, hue + 2), green: hueToRgb(t1, t2, hue), blue: hueToRgb(t1, t2, hue - 2), alpha };
}
}
export function hslFromColor(rgba) {
const r = rgba.red;
const g = rgba.green;
const b = rgba.blue;
const a = rgba.alpha;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0;
let s = 0;
const l = (min + max) / 2;
const chroma = max - min;
if (chroma > 0) {
s = Math.min((l <= 0.5 ? chroma / (2 * l) : chroma / (2 - (2 * l))), 1);
switch (max) {
case r:
h = (g - b) / chroma + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / chroma + 2;
break;
case b:
h = (r - g) / chroma + 4;
break;
}
h *= 60;
h = Math.round(h);
}
return { h, s, l, a };
}
export function colorFromHWB(hue, white, black, alpha = 1.0) {
if (white + black >= 1) {
const gray = white / (white + black);
return { red: gray, green: gray, blue: gray, alpha };
}
const rgb = colorFromHSL(hue, 1, 0.5, alpha);
let red = rgb.red;
red *= (1 - white - black);
red += white;
let green = rgb.green;
green *= (1 - white - black);
green += white;
let blue = rgb.blue;
blue *= (1 - white - black);
blue += white;
return {
red: red,
green: green,
blue: blue,
alpha
};
}
export function hwbFromColor(rgba) {
const hsl = hslFromColor(rgba);
const white = Math.min(rgba.red, rgba.green, rgba.blue);
const black = 1 - Math.max(rgba.red, rgba.green, rgba.blue);
return {
h: hsl.h,
w: white,
b: black,
a: hsl.a
};
}
export function getColorValue(node) {
if (node.type === nodes.NodeType.HexColorValue) {
const text = node.getText();
return colorFromHex(text);
}
else if (node.type === nodes.NodeType.Function) {
const functionNode = node;
const name = functionNode.getName();
let colorValues = functionNode.getArguments().getChildren();
if (colorValues.length === 1) {
const functionArg = colorValues[0].getChildren();
if (functionArg.length === 1 && functionArg[0].type === nodes.NodeType.Expression) {
colorValues = functionArg[0].getChildren();
if (colorValues.length === 3) {
const lastValue = colorValues[2];
if (lastValue instanceof nodes.BinaryExpression) {
const left = lastValue.getLeft(), right = lastValue.getRight(), operator = lastValue.getOperator();
if (left && right && operator && operator.matches('/')) {
colorValues = [colorValues[0], colorValues[1], left, right];
}
}
}
}
}
if (!name || colorValues.length < 3 || colorValues.length > 4) {
return null;
}
try {
const alpha = colorValues.length === 4 ? getNumericValue(colorValues[3], 1) : 1;
if (name === 'rgb' || name === 'rgba') {
return {
red: getNumericValue(colorValues[0], 255.0),
green: getNumericValue(colorValues[1], 255.0),
blue: getNumericValue(colorValues[2], 255.0),
alpha
};
}
else if (name === 'hsl' || name === 'hsla') {
const h = getAngle(colorValues[0]);
const s = getNumericValue(colorValues[1], 100.0);
const l = getNumericValue(colorValues[2], 100.0);
return colorFromHSL(h, s, l, alpha);
}
else if (name === 'hwb') {
const h = getAngle(colorValues[0]);
const w = getNumericValue(colorValues[1], 100.0);
const b = getNumericValue(colorValues[2], 100.0);
return colorFromHWB(h, w, b, alpha);
}
}
catch (e) {
// parse error on numeric value
return null;
}
}
else if (node.type === nodes.NodeType.Identifier) {
if (node.parent && node.parent.type !== nodes.NodeType.Term) {
return null;
}
const term = node.parent;
if (term && term.parent && term.parent.type === nodes.NodeType.BinaryExpression) {
const expression = term.parent;
if (expression.parent && expression.parent.type === nodes.NodeType.ListEntry && expression.parent.key === expression) {
return null;
}
}
const candidateColor = node.getText().toLowerCase();
if (candidateColor === 'none') {
return null;
}
const colorHex = colors[candidateColor];
if (colorHex) {
return colorFromHex(colorHex);
}
}
return null;
}

View file

@ -0,0 +1,88 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as objects from '../utils/objects';
import { cssData } from '../data/webCustomData';
import { CSSDataProvider } from './dataProvider';
export class CSSDataManager {
constructor(options) {
this.dataProviders = [];
this._propertySet = {};
this._atDirectiveSet = {};
this._pseudoClassSet = {};
this._pseudoElementSet = {};
this._properties = [];
this._atDirectives = [];
this._pseudoClasses = [];
this._pseudoElements = [];
this.setDataProviders(options?.useDefaultDataProvider !== false, options?.customDataProviders || []);
}
setDataProviders(builtIn, providers) {
this.dataProviders = [];
if (builtIn) {
this.dataProviders.push(new CSSDataProvider(cssData));
}
this.dataProviders.push(...providers);
this.collectData();
}
/**
* Collect all data & handle duplicates
*/
collectData() {
this._propertySet = {};
this._atDirectiveSet = {};
this._pseudoClassSet = {};
this._pseudoElementSet = {};
this.dataProviders.forEach(provider => {
provider.provideProperties().forEach(p => {
if (!this._propertySet[p.name]) {
this._propertySet[p.name] = p;
}
});
provider.provideAtDirectives().forEach(p => {
if (!this._atDirectiveSet[p.name]) {
this._atDirectiveSet[p.name] = p;
}
});
provider.providePseudoClasses().forEach(p => {
if (!this._pseudoClassSet[p.name]) {
this._pseudoClassSet[p.name] = p;
}
});
provider.providePseudoElements().forEach(p => {
if (!this._pseudoElementSet[p.name]) {
this._pseudoElementSet[p.name] = p;
}
});
});
this._properties = objects.values(this._propertySet);
this._atDirectives = objects.values(this._atDirectiveSet);
this._pseudoClasses = objects.values(this._pseudoClassSet);
this._pseudoElements = objects.values(this._pseudoElementSet);
}
getProperty(name) { return this._propertySet[name]; }
getAtDirective(name) { return this._atDirectiveSet[name]; }
getPseudoClass(name) { return this._pseudoClassSet[name]; }
getPseudoElement(name) { return this._pseudoElementSet[name]; }
getProperties() {
return this._properties;
}
getAtDirectives() {
return this._atDirectives;
}
getPseudoClasses() {
return this._pseudoClasses;
}
getPseudoElements() {
return this._pseudoElements;
}
isKnownProperty(name) {
return name.toLowerCase() in this._propertySet;
}
isStandardProperty(name) {
return this.isKnownProperty(name) &&
(!this._propertySet[name.toLowerCase()].status || this._propertySet[name.toLowerCase()].status === 'standard');
}
}

View file

@ -0,0 +1,73 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export class CSSDataProvider {
/**
* Currently, unversioned data uses the V1 implementation
* In the future when the provider handles multiple versions of HTML custom data,
* use the latest implementation for unversioned data
*/
constructor(data) {
this._properties = [];
this._atDirectives = [];
this._pseudoClasses = [];
this._pseudoElements = [];
this.addData(data);
}
provideProperties() {
return this._properties;
}
provideAtDirectives() {
return this._atDirectives;
}
providePseudoClasses() {
return this._pseudoClasses;
}
providePseudoElements() {
return this._pseudoElements;
}
addData(data) {
if (Array.isArray(data.properties)) {
for (const prop of data.properties) {
if (isPropertyData(prop)) {
this._properties.push(prop);
}
}
}
if (Array.isArray(data.atDirectives)) {
for (const prop of data.atDirectives) {
if (isAtDirective(prop)) {
this._atDirectives.push(prop);
}
}
}
if (Array.isArray(data.pseudoClasses)) {
for (const prop of data.pseudoClasses) {
if (isPseudoClassData(prop)) {
this._pseudoClasses.push(prop);
}
}
}
if (Array.isArray(data.pseudoElements)) {
for (const prop of data.pseudoElements) {
if (isPseudoElementData(prop)) {
this._pseudoElements.push(prop);
}
}
}
}
}
function isPropertyData(d) {
return typeof d.name === 'string';
}
function isAtDirective(d) {
return typeof d.name === 'string';
}
function isPseudoClassData(d) {
return typeof d.name === 'string';
}
function isPseudoElementData(d) {
return typeof d.name === 'string';
}

View file

@ -0,0 +1,137 @@
/*---------------------------------------------------------------------------------------------
* 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 { MarkupKind } from '../cssLanguageTypes';
export const browserNames = {
E: 'Edge',
FF: 'Firefox',
S: 'Safari',
C: 'Chrome',
IE: 'IE',
O: 'Opera'
};
function getEntryStatus(status) {
switch (status) {
case 'experimental':
return '⚠️ Property is experimental. Be cautious when using it.\n\n';
case 'nonstandard':
return '🚨️ Property is nonstandard. Avoid using it.\n\n';
case 'obsolete':
return '🚨️️️ Property is obsolete. Avoid using it.\n\n';
default:
return '';
}
}
export function getEntryDescription(entry, doesSupportMarkdown, settings) {
let result;
if (doesSupportMarkdown) {
result = {
kind: 'markdown',
value: getEntryMarkdownDescription(entry, settings)
};
}
else {
result = {
kind: 'plaintext',
value: getEntryStringDescription(entry, settings)
};
}
if (result.value === '') {
return undefined;
}
return result;
}
export function textToMarkedString(text) {
text = text.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
function getEntryStringDescription(entry, settings) {
if (!entry.description || entry.description === '') {
return '';
}
if (typeof entry.description !== 'string') {
return entry.description.value;
}
let result = '';
if (settings?.documentation !== false) {
if (entry.status) {
result += getEntryStatus(entry.status);
}
result += entry.description;
const browserLabel = getBrowserLabel(entry.browsers);
if (browserLabel) {
result += '\n(' + browserLabel + ')';
}
if ('syntax' in entry) {
result += `\n\nSyntax: ${entry.syntax}`;
}
}
if (entry.references && entry.references.length > 0 && settings?.references !== false) {
if (result.length > 0) {
result += '\n\n';
}
result += entry.references.map(r => {
return `${r.name}: ${r.url}`;
}).join(' | ');
}
return result;
}
function getEntryMarkdownDescription(entry, settings) {
if (!entry.description || entry.description === '') {
return '';
}
let result = '';
if (settings?.documentation !== false) {
if (entry.status) {
result += getEntryStatus(entry.status);
}
if (typeof entry.description === 'string') {
result += textToMarkedString(entry.description);
}
else {
result += entry.description.kind === MarkupKind.Markdown ? entry.description.value : textToMarkedString(entry.description.value);
}
const browserLabel = getBrowserLabel(entry.browsers);
if (browserLabel) {
result += '\n\n(' + textToMarkedString(browserLabel) + ')';
}
if ('syntax' in entry && entry.syntax) {
result += `\n\nSyntax: ${textToMarkedString(entry.syntax)}`;
}
}
if (entry.references && entry.references.length > 0 && settings?.references !== false) {
if (result.length > 0) {
result += '\n\n';
}
result += entry.references.map(r => {
return `[${r.name}](${r.url})`;
}).join(' | ');
}
return result;
}
/**
* Input is like `["E12","FF49","C47","IE","O"]`
* Output is like `Edge 12, Firefox 49, Chrome 47, IE, Opera`
*/
export function getBrowserLabel(browsers = []) {
if (browsers.length === 0) {
return null;
}
return browsers
.map(b => {
let result = '';
const matches = b.match(/([A-Z]+)(\d+)?/);
const name = matches[1];
const version = matches[2];
if (name in browserNames) {
result += browserNames[name];
}
if (version) {
result += ' ' + version;
}
return result;
})
.join(', ');
}

View file

@ -0,0 +1,8 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export * from './entry';
export * from './colors';
export * from './builtinData';

View file

@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as l10n from '@vscode/l10n';
export class CSSIssueType {
constructor(id, message) {
this.id = id;
this.message = message;
}
}
export const ParseError = {
NumberExpected: new CSSIssueType('css-numberexpected', l10n.t("number expected")),
ConditionExpected: new CSSIssueType('css-conditionexpected', l10n.t("condition expected")),
RuleOrSelectorExpected: new CSSIssueType('css-ruleorselectorexpected', l10n.t("at-rule or selector expected")),
DotExpected: new CSSIssueType('css-dotexpected', l10n.t("dot expected")),
ColonExpected: new CSSIssueType('css-colonexpected', l10n.t("colon expected")),
SemiColonExpected: new CSSIssueType('css-semicolonexpected', l10n.t("semi-colon expected")),
TermExpected: new CSSIssueType('css-termexpected', l10n.t("term expected")),
ExpressionExpected: new CSSIssueType('css-expressionexpected', l10n.t("expression expected")),
OperatorExpected: new CSSIssueType('css-operatorexpected', l10n.t("operator expected")),
IdentifierExpected: new CSSIssueType('css-identifierexpected', l10n.t("identifier expected")),
PercentageExpected: new CSSIssueType('css-percentageexpected', l10n.t("percentage expected")),
URIOrStringExpected: new CSSIssueType('css-uriorstringexpected', l10n.t("uri or string expected")),
URIExpected: new CSSIssueType('css-uriexpected', l10n.t("URI expected")),
VariableNameExpected: new CSSIssueType('css-varnameexpected', l10n.t("variable name expected")),
VariableValueExpected: new CSSIssueType('css-varvalueexpected', l10n.t("variable value expected")),
PropertyValueExpected: new CSSIssueType('css-propertyvalueexpected', l10n.t("property value expected")),
LeftCurlyExpected: new CSSIssueType('css-lcurlyexpected', l10n.t("{ expected")),
RightCurlyExpected: new CSSIssueType('css-rcurlyexpected', l10n.t("} expected")),
LeftSquareBracketExpected: new CSSIssueType('css-rbracketexpected', l10n.t("[ expected")),
RightSquareBracketExpected: new CSSIssueType('css-lbracketexpected', l10n.t("] expected")),
LeftParenthesisExpected: new CSSIssueType('css-lparentexpected', l10n.t("( expected")),
RightParenthesisExpected: new CSSIssueType('css-rparentexpected', l10n.t(") expected")),
CommaExpected: new CSSIssueType('css-commaexpected', l10n.t("comma expected")),
PageDirectiveOrDeclarationExpected: new CSSIssueType('css-pagedirordeclexpected', l10n.t("page directive or declaraton expected")),
UnknownAtRule: new CSSIssueType('css-unknownatrule', l10n.t("at-rule unknown")),
UnknownKeyword: new CSSIssueType('css-unknownkeyword', l10n.t("unknown keyword")),
SelectorExpected: new CSSIssueType('css-selectorexpected', l10n.t("selector expected")),
StringLiteralExpected: new CSSIssueType('css-stringliteralexpected', l10n.t("string literal expected")),
WhitespaceExpected: new CSSIssueType('css-whitespaceexpected', l10n.t("whitespace expected")),
MediaQueryExpected: new CSSIssueType('css-mediaqueryexpected', l10n.t("media query expected")),
IdentifierOrWildcardExpected: new CSSIssueType('css-idorwildcardexpected', l10n.t("identifier or wildcard expected")),
WildcardExpected: new CSSIssueType('css-wildcardexpected', l10n.t("wildcard expected")),
IdentifierOrVariableExpected: new CSSIssueType('css-idorvarexpected', l10n.t("identifier or variable expected")),
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,592 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export var TokenType;
(function (TokenType) {
TokenType[TokenType["Ident"] = 0] = "Ident";
TokenType[TokenType["AtKeyword"] = 1] = "AtKeyword";
TokenType[TokenType["String"] = 2] = "String";
TokenType[TokenType["BadString"] = 3] = "BadString";
TokenType[TokenType["UnquotedString"] = 4] = "UnquotedString";
TokenType[TokenType["Hash"] = 5] = "Hash";
TokenType[TokenType["Num"] = 6] = "Num";
TokenType[TokenType["Percentage"] = 7] = "Percentage";
TokenType[TokenType["Dimension"] = 8] = "Dimension";
TokenType[TokenType["UnicodeRange"] = 9] = "UnicodeRange";
TokenType[TokenType["CDO"] = 10] = "CDO";
TokenType[TokenType["CDC"] = 11] = "CDC";
TokenType[TokenType["Colon"] = 12] = "Colon";
TokenType[TokenType["SemiColon"] = 13] = "SemiColon";
TokenType[TokenType["CurlyL"] = 14] = "CurlyL";
TokenType[TokenType["CurlyR"] = 15] = "CurlyR";
TokenType[TokenType["ParenthesisL"] = 16] = "ParenthesisL";
TokenType[TokenType["ParenthesisR"] = 17] = "ParenthesisR";
TokenType[TokenType["BracketL"] = 18] = "BracketL";
TokenType[TokenType["BracketR"] = 19] = "BracketR";
TokenType[TokenType["Whitespace"] = 20] = "Whitespace";
TokenType[TokenType["Includes"] = 21] = "Includes";
TokenType[TokenType["Dashmatch"] = 22] = "Dashmatch";
TokenType[TokenType["SubstringOperator"] = 23] = "SubstringOperator";
TokenType[TokenType["PrefixOperator"] = 24] = "PrefixOperator";
TokenType[TokenType["SuffixOperator"] = 25] = "SuffixOperator";
TokenType[TokenType["Delim"] = 26] = "Delim";
TokenType[TokenType["EMS"] = 27] = "EMS";
TokenType[TokenType["EXS"] = 28] = "EXS";
TokenType[TokenType["Length"] = 29] = "Length";
TokenType[TokenType["Angle"] = 30] = "Angle";
TokenType[TokenType["Time"] = 31] = "Time";
TokenType[TokenType["Freq"] = 32] = "Freq";
TokenType[TokenType["Exclamation"] = 33] = "Exclamation";
TokenType[TokenType["Resolution"] = 34] = "Resolution";
TokenType[TokenType["Comma"] = 35] = "Comma";
TokenType[TokenType["Charset"] = 36] = "Charset";
TokenType[TokenType["EscapedJavaScript"] = 37] = "EscapedJavaScript";
TokenType[TokenType["BadEscapedJavaScript"] = 38] = "BadEscapedJavaScript";
TokenType[TokenType["Comment"] = 39] = "Comment";
TokenType[TokenType["SingleLineComment"] = 40] = "SingleLineComment";
TokenType[TokenType["EOF"] = 41] = "EOF";
TokenType[TokenType["CustomToken"] = 42] = "CustomToken";
})(TokenType || (TokenType = {}));
export class MultiLineStream {
constructor(source) {
this.source = source;
this.len = source.length;
this.position = 0;
}
substring(from, to = this.position) {
return this.source.substring(from, to);
}
eos() {
return this.len <= this.position;
}
pos() {
return this.position;
}
goBackTo(pos) {
this.position = pos;
}
goBack(n) {
this.position -= n;
}
advance(n) {
this.position += n;
}
nextChar() {
return this.source.charCodeAt(this.position++) || 0;
}
peekChar(n = 0) {
return this.source.charCodeAt(this.position + n) || 0;
}
lookbackChar(n = 0) {
return this.source.charCodeAt(this.position - n) || 0;
}
advanceIfChar(ch) {
if (ch === this.source.charCodeAt(this.position)) {
this.position++;
return true;
}
return false;
}
advanceIfChars(ch) {
if (this.position + ch.length > this.source.length) {
return false;
}
let i = 0;
for (; i < ch.length; i++) {
if (this.source.charCodeAt(this.position + i) !== ch[i]) {
return false;
}
}
this.advance(i);
return true;
}
advanceWhileChar(condition) {
const posNow = this.position;
while (this.position < this.len && condition(this.source.charCodeAt(this.position))) {
this.position++;
}
return this.position - posNow;
}
}
const _a = 'a'.charCodeAt(0);
const _f = 'f'.charCodeAt(0);
const _z = 'z'.charCodeAt(0);
const _u = 'u'.charCodeAt(0);
const _A = 'A'.charCodeAt(0);
const _F = 'F'.charCodeAt(0);
const _Z = 'Z'.charCodeAt(0);
const _0 = '0'.charCodeAt(0);
const _9 = '9'.charCodeAt(0);
const _TLD = '~'.charCodeAt(0);
const _HAT = '^'.charCodeAt(0);
const _EQS = '='.charCodeAt(0);
const _PIP = '|'.charCodeAt(0);
const _MIN = '-'.charCodeAt(0);
const _USC = '_'.charCodeAt(0);
const _PRC = '%'.charCodeAt(0);
const _MUL = '*'.charCodeAt(0);
const _LPA = '('.charCodeAt(0);
const _RPA = ')'.charCodeAt(0);
const _LAN = '<'.charCodeAt(0);
const _RAN = '>'.charCodeAt(0);
const _ATS = '@'.charCodeAt(0);
const _HSH = '#'.charCodeAt(0);
const _DLR = '$'.charCodeAt(0);
const _BSL = '\\'.charCodeAt(0);
const _FSL = '/'.charCodeAt(0);
const _NWL = '\n'.charCodeAt(0);
const _CAR = '\r'.charCodeAt(0);
const _LFD = '\f'.charCodeAt(0);
const _DQO = '"'.charCodeAt(0);
const _SQO = '\''.charCodeAt(0);
const _WSP = ' '.charCodeAt(0);
const _TAB = '\t'.charCodeAt(0);
const _SEM = ';'.charCodeAt(0);
const _COL = ':'.charCodeAt(0);
const _CUL = '{'.charCodeAt(0);
const _CUR = '}'.charCodeAt(0);
const _BRL = '['.charCodeAt(0);
const _BRR = ']'.charCodeAt(0);
const _CMA = ','.charCodeAt(0);
const _DOT = '.'.charCodeAt(0);
const _BNG = '!'.charCodeAt(0);
const _QSM = '?'.charCodeAt(0);
const _PLS = '+'.charCodeAt(0);
const staticTokenTable = {};
staticTokenTable[_SEM] = TokenType.SemiColon;
staticTokenTable[_COL] = TokenType.Colon;
staticTokenTable[_CUL] = TokenType.CurlyL;
staticTokenTable[_CUR] = TokenType.CurlyR;
staticTokenTable[_BRR] = TokenType.BracketR;
staticTokenTable[_BRL] = TokenType.BracketL;
staticTokenTable[_LPA] = TokenType.ParenthesisL;
staticTokenTable[_RPA] = TokenType.ParenthesisR;
staticTokenTable[_CMA] = TokenType.Comma;
const staticUnitTable = {};
staticUnitTable['em'] = TokenType.EMS;
staticUnitTable['ex'] = TokenType.EXS;
staticUnitTable['px'] = TokenType.Length;
staticUnitTable['cm'] = TokenType.Length;
staticUnitTable['mm'] = TokenType.Length;
staticUnitTable['in'] = TokenType.Length;
staticUnitTable['pt'] = TokenType.Length;
staticUnitTable['pc'] = TokenType.Length;
staticUnitTable['deg'] = TokenType.Angle;
staticUnitTable['rad'] = TokenType.Angle;
staticUnitTable['grad'] = TokenType.Angle;
staticUnitTable['ms'] = TokenType.Time;
staticUnitTable['s'] = TokenType.Time;
staticUnitTable['hz'] = TokenType.Freq;
staticUnitTable['khz'] = TokenType.Freq;
staticUnitTable['%'] = TokenType.Percentage;
staticUnitTable['fr'] = TokenType.Percentage;
staticUnitTable['dpi'] = TokenType.Resolution;
staticUnitTable['dpcm'] = TokenType.Resolution;
export class Scanner {
constructor() {
this.stream = new MultiLineStream('');
this.ignoreComment = true;
this.ignoreWhitespace = true;
this.inURL = false;
}
setSource(input) {
this.stream = new MultiLineStream(input);
}
finishToken(offset, type, text) {
return {
offset: offset,
len: this.stream.pos() - offset,
type: type,
text: text || this.stream.substring(offset)
};
}
substring(offset, len) {
return this.stream.substring(offset, offset + len);
}
pos() {
return this.stream.pos();
}
goBackTo(pos) {
this.stream.goBackTo(pos);
}
scanUnquotedString() {
const offset = this.stream.pos();
const content = [];
if (this._unquotedString(content)) {
return this.finishToken(offset, TokenType.UnquotedString, content.join(''));
}
return null;
}
scan() {
// processes all whitespaces and comments
const triviaToken = this.trivia();
if (triviaToken !== null) {
return triviaToken;
}
const offset = this.stream.pos();
// End of file/input
if (this.stream.eos()) {
return this.finishToken(offset, TokenType.EOF);
}
return this.scanNext(offset);
}
/**
* Read the range as described in https://www.w3.org/TR/CSS21/syndata.html#tokenization
* Assume the `u` has aleady been consumed
* @returns if reading the unicode was successful
*/
tryScanUnicode() {
const offset = this.stream.pos();
if (!this.stream.eos() && this._unicodeRange()) {
return this.finishToken(offset, TokenType.UnicodeRange);
}
this.stream.goBackTo(offset);
return undefined;
}
scanNext(offset) {
// CDO <!--
if (this.stream.advanceIfChars([_LAN, _BNG, _MIN, _MIN])) {
return this.finishToken(offset, TokenType.CDO);
}
// CDC -->
if (this.stream.advanceIfChars([_MIN, _MIN, _RAN])) {
return this.finishToken(offset, TokenType.CDC);
}
let content = [];
if (this.ident(content)) {
return this.finishToken(offset, TokenType.Ident, content.join(''));
}
// at-keyword
if (this.stream.advanceIfChar(_ATS)) {
content = ['@'];
if (this._name(content)) {
const keywordText = content.join('');
if (keywordText === '@charset') {
return this.finishToken(offset, TokenType.Charset, keywordText);
}
return this.finishToken(offset, TokenType.AtKeyword, keywordText);
}
else {
return this.finishToken(offset, TokenType.Delim);
}
}
// hash
if (this.stream.advanceIfChar(_HSH)) {
content = ['#'];
if (this._name(content)) {
return this.finishToken(offset, TokenType.Hash, content.join(''));
}
else {
return this.finishToken(offset, TokenType.Delim);
}
}
// Important
if (this.stream.advanceIfChar(_BNG)) {
return this.finishToken(offset, TokenType.Exclamation);
}
// Numbers
if (this._number()) {
const pos = this.stream.pos();
content = [this.stream.substring(offset, pos)];
if (this.stream.advanceIfChar(_PRC)) {
// Percentage 43%
return this.finishToken(offset, TokenType.Percentage);
}
else if (this.ident(content)) {
const dim = this.stream.substring(pos).toLowerCase();
const tokenType = staticUnitTable[dim];
if (typeof tokenType !== 'undefined') {
// Known dimension 43px
return this.finishToken(offset, tokenType, content.join(''));
}
else {
// Unknown dimension 43ft
return this.finishToken(offset, TokenType.Dimension, content.join(''));
}
}
return this.finishToken(offset, TokenType.Num);
}
// String, BadString
content = [];
let tokenType = this._string(content);
if (tokenType !== null) {
return this.finishToken(offset, tokenType, content.join(''));
}
// single character tokens
tokenType = staticTokenTable[this.stream.peekChar()];
if (typeof tokenType !== 'undefined') {
this.stream.advance(1);
return this.finishToken(offset, tokenType);
}
// includes ~=
if (this.stream.peekChar(0) === _TLD && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(offset, TokenType.Includes);
}
// DashMatch |=
if (this.stream.peekChar(0) === _PIP && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(offset, TokenType.Dashmatch);
}
// Substring operator *=
if (this.stream.peekChar(0) === _MUL && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(offset, TokenType.SubstringOperator);
}
// Substring operator ^=
if (this.stream.peekChar(0) === _HAT && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(offset, TokenType.PrefixOperator);
}
// Substring operator $=
if (this.stream.peekChar(0) === _DLR && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(offset, TokenType.SuffixOperator);
}
// Delim
this.stream.nextChar();
return this.finishToken(offset, TokenType.Delim);
}
trivia() {
while (true) {
const offset = this.stream.pos();
if (this._whitespace()) {
if (!this.ignoreWhitespace) {
return this.finishToken(offset, TokenType.Whitespace);
}
}
else if (this.comment()) {
if (!this.ignoreComment) {
return this.finishToken(offset, TokenType.Comment);
}
}
else {
return null;
}
}
}
comment() {
if (this.stream.advanceIfChars([_FSL, _MUL])) {
let success = false, hot = false;
this.stream.advanceWhileChar((ch) => {
if (hot && ch === _FSL) {
success = true;
return false;
}
hot = ch === _MUL;
return true;
});
if (success) {
this.stream.advance(1);
}
return true;
}
return false;
}
_number() {
let npeek = 0, ch;
if (this.stream.peekChar() === _DOT) {
npeek = 1;
}
ch = this.stream.peekChar(npeek);
if (ch >= _0 && ch <= _9) {
this.stream.advance(npeek + 1);
this.stream.advanceWhileChar((ch) => {
return ch >= _0 && ch <= _9 || npeek === 0 && ch === _DOT;
});
return true;
}
return false;
}
_newline(result) {
const ch = this.stream.peekChar();
switch (ch) {
case _CAR:
case _LFD:
case _NWL:
this.stream.advance(1);
result.push(String.fromCharCode(ch));
if (ch === _CAR && this.stream.advanceIfChar(_NWL)) {
result.push('\n');
}
return true;
}
return false;
}
_escape(result, includeNewLines) {
let ch = this.stream.peekChar();
if (ch === _BSL) {
this.stream.advance(1);
ch = this.stream.peekChar();
let hexNumCount = 0;
while (hexNumCount < 6 && (ch >= _0 && ch <= _9 || ch >= _a && ch <= _f || ch >= _A && ch <= _F)) {
this.stream.advance(1);
ch = this.stream.peekChar();
hexNumCount++;
}
if (hexNumCount > 0) {
try {
const hexVal = parseInt(this.stream.substring(this.stream.pos() - hexNumCount), 16);
if (hexVal) {
result.push(String.fromCharCode(hexVal));
}
}
catch (e) {
// ignore
}
// optional whitespace or new line, not part of result text
if (ch === _WSP || ch === _TAB) {
this.stream.advance(1);
}
else {
this._newline([]);
}
return true;
}
if (ch !== _CAR && ch !== _LFD && ch !== _NWL) {
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
else if (includeNewLines) {
return this._newline(result);
}
}
return false;
}
_stringChar(closeQuote, result) {
// not closeQuote, not backslash, not newline
const ch = this.stream.peekChar();
if (ch !== 0 && ch !== closeQuote && ch !== _BSL && ch !== _CAR && ch !== _LFD && ch !== _NWL) {
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
_string(result) {
if (this.stream.peekChar() === _SQO || this.stream.peekChar() === _DQO) {
const closeQuote = this.stream.nextChar();
result.push(String.fromCharCode(closeQuote));
while (this._stringChar(closeQuote, result) || this._escape(result, true)) {
// loop
}
if (this.stream.peekChar() === closeQuote) {
this.stream.nextChar();
result.push(String.fromCharCode(closeQuote));
return TokenType.String;
}
else {
return TokenType.BadString;
}
}
return null;
}
_unquotedChar(result) {
// not closeQuote, not backslash, not newline
const ch = this.stream.peekChar();
if (ch !== 0 && ch !== _BSL && ch !== _SQO && ch !== _DQO && ch !== _LPA && ch !== _RPA && ch !== _WSP && ch !== _TAB && ch !== _NWL && ch !== _LFD && ch !== _CAR) {
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
_unquotedString(result) {
let hasContent = false;
while (this._unquotedChar(result) || this._escape(result)) {
hasContent = true;
}
return hasContent;
}
_whitespace() {
const n = this.stream.advanceWhileChar((ch) => {
return ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR;
});
return n > 0;
}
_name(result) {
let matched = false;
while (this._identChar(result) || this._escape(result)) {
matched = true;
}
return matched;
}
ident(result) {
const pos = this.stream.pos();
const hasMinus = this._minus(result);
if (hasMinus) {
if (this._minus(result) /* -- */ || this._identFirstChar(result) || this._escape(result)) {
while (this._identChar(result) || this._escape(result)) {
// loop
}
return true;
}
}
else if (this._identFirstChar(result) || this._escape(result)) {
while (this._identChar(result) || this._escape(result)) {
// loop
}
return true;
}
this.stream.goBackTo(pos);
return false;
}
_identFirstChar(result) {
const ch = this.stream.peekChar();
if (ch === _USC || // _
ch >= _a && ch <= _z || // a-z
ch >= _A && ch <= _Z || // A-Z
ch >= 0x80 && ch <= 0xFFFF) { // nonascii
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
_minus(result) {
const ch = this.stream.peekChar();
if (ch === _MIN) {
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
_identChar(result) {
const ch = this.stream.peekChar();
if (ch === _USC || // _
ch === _MIN || // -
ch >= _a && ch <= _z || // a-z
ch >= _A && ch <= _Z || // A-Z
ch >= _0 && ch <= _9 || // 0/9
ch >= 0x80 && ch <= 0xFFFF) { // nonascii
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
_unicodeRange() {
// follow https://www.w3.org/TR/CSS21/syndata.html#tokenization and https://www.w3.org/TR/css-syntax-3/#urange-syntax
// assume u has already been parsed
if (this.stream.advanceIfChar(_PLS)) {
const isHexDigit = (ch) => (ch >= _0 && ch <= _9 || ch >= _a && ch <= _f || ch >= _A && ch <= _F);
const codePoints = this.stream.advanceWhileChar(isHexDigit) + this.stream.advanceWhileChar(ch => ch === _QSM);
if (codePoints >= 1 && codePoints <= 6) {
if (this.stream.advanceIfChar(_MIN)) {
const digits = this.stream.advanceWhileChar(isHexDigit);
if (digits >= 1 && digits <= 6) {
return true;
}
}
else {
return true;
}
}
}
return false;
}
}

View file

@ -0,0 +1,311 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nodes from './cssNodes';
import { findFirst } from '../utils/arrays';
export class Scope {
constructor(offset, length) {
this.offset = offset;
this.length = length;
this.symbols = [];
this.parent = null;
this.children = [];
}
addChild(scope) {
this.children.push(scope);
scope.setParent(this);
}
setParent(scope) {
this.parent = scope;
}
findScope(offset, length = 0) {
if (this.offset <= offset && this.offset + this.length > offset + length || this.offset === offset && this.length === length) {
return this.findInScope(offset, length);
}
return null;
}
findInScope(offset, length = 0) {
// find the first scope child that has an offset larger than offset + length
const end = offset + length;
const idx = findFirst(this.children, s => s.offset > end);
if (idx === 0) {
// all scopes have offsets larger than our end
return this;
}
const res = this.children[idx - 1];
if (res.offset <= offset && res.offset + res.length >= offset + length) {
return res.findInScope(offset, length);
}
return this;
}
addSymbol(symbol) {
this.symbols.push(symbol);
}
getSymbol(name, type) {
for (let index = 0; index < this.symbols.length; index++) {
const symbol = this.symbols[index];
if (symbol.name === name && symbol.type === type) {
return symbol;
}
}
return null;
}
getSymbols() {
return this.symbols;
}
}
export class GlobalScope extends Scope {
constructor() {
super(0, Number.MAX_VALUE);
}
}
export class Symbol {
constructor(name, value, node, type) {
this.name = name;
this.value = value;
this.node = node;
this.type = type;
}
}
export class ScopeBuilder {
constructor(scope) {
this.scope = scope;
}
addSymbol(node, name, value, type) {
if (node.offset !== -1) {
const current = this.scope.findScope(node.offset, node.length);
if (current) {
current.addSymbol(new Symbol(name, value, node, type));
}
}
}
addScope(node) {
if (node.offset !== -1) {
const current = this.scope.findScope(node.offset, node.length);
if (current && (current.offset !== node.offset || current.length !== node.length)) { // scope already known?
const newScope = new Scope(node.offset, node.length);
current.addChild(newScope);
return newScope;
}
return current;
}
return null;
}
addSymbolToChildScope(scopeNode, node, name, value, type) {
if (scopeNode && scopeNode.offset !== -1) {
const current = this.addScope(scopeNode); // create the scope or gets the existing one
if (current) {
current.addSymbol(new Symbol(name, value, node, type));
}
}
}
visitNode(node) {
switch (node.type) {
case nodes.NodeType.Keyframe:
this.addSymbol(node, node.getName(), void 0, nodes.ReferenceType.Keyframe);
return true;
case nodes.NodeType.CustomPropertyDeclaration:
return this.visitCustomPropertyDeclarationNode(node);
case nodes.NodeType.VariableDeclaration:
return this.visitVariableDeclarationNode(node);
case nodes.NodeType.Ruleset:
return this.visitRuleSet(node);
case nodes.NodeType.MixinDeclaration:
this.addSymbol(node, node.getName(), void 0, nodes.ReferenceType.Mixin);
return true;
case nodes.NodeType.FunctionDeclaration:
this.addSymbol(node, node.getName(), void 0, nodes.ReferenceType.Function);
return true;
case nodes.NodeType.FunctionParameter: {
return this.visitFunctionParameterNode(node);
}
case nodes.NodeType.Declarations:
this.addScope(node);
return true;
case nodes.NodeType.For:
const forNode = node;
const scopeNode = forNode.getDeclarations();
if (scopeNode && forNode.variable) {
this.addSymbolToChildScope(scopeNode, forNode.variable, forNode.variable.getName(), void 0, nodes.ReferenceType.Variable);
}
return true;
case nodes.NodeType.Each: {
const eachNode = node;
const scopeNode = eachNode.getDeclarations();
if (scopeNode) {
const variables = eachNode.getVariables().getChildren();
for (const variable of variables) {
this.addSymbolToChildScope(scopeNode, variable, variable.getName(), void 0, nodes.ReferenceType.Variable);
}
}
return true;
}
}
return true;
}
visitRuleSet(node) {
const current = this.scope.findScope(node.offset, node.length);
if (current) {
for (const child of node.getSelectors().getChildren()) {
if (child instanceof nodes.Selector) {
if (child.getChildren().length === 1) { // only selectors with a single element can be extended
current.addSymbol(new Symbol(child.getChild(0).getText(), void 0, child, nodes.ReferenceType.Rule));
}
}
}
}
return true;
}
visitVariableDeclarationNode(node) {
const value = node.getValue() ? node.getValue().getText() : void 0;
this.addSymbol(node, node.getName(), value, nodes.ReferenceType.Variable);
return true;
}
visitFunctionParameterNode(node) {
// parameters are part of the body scope
const scopeNode = node.getParent().getDeclarations();
if (scopeNode) {
const valueNode = node.getDefaultValue();
const value = valueNode ? valueNode.getText() : void 0;
this.addSymbolToChildScope(scopeNode, node, node.getName(), value, nodes.ReferenceType.Variable);
}
return true;
}
visitCustomPropertyDeclarationNode(node) {
const value = node.getValue() ? node.getValue().getText() : '';
this.addCSSVariable(node.getProperty(), node.getProperty().getName(), value, nodes.ReferenceType.Variable);
return true;
}
addCSSVariable(node, name, value, type) {
if (node.offset !== -1) {
this.scope.addSymbol(new Symbol(name, value, node, type));
}
}
}
export class Symbols {
constructor(node) {
this.global = new GlobalScope();
node.acceptVisitor(new ScopeBuilder(this.global));
}
findSymbolsAtOffset(offset, referenceType) {
let scope = this.global.findScope(offset, 0);
const result = [];
const names = {};
while (scope) {
const symbols = scope.getSymbols();
for (let i = 0; i < symbols.length; i++) {
const symbol = symbols[i];
if (symbol.type === referenceType && !names[symbol.name]) {
result.push(symbol);
names[symbol.name] = true;
}
}
scope = scope.parent;
}
return result;
}
internalFindSymbol(node, referenceTypes) {
let scopeNode = node;
if (node.parent instanceof nodes.FunctionParameter && node.parent.getParent() instanceof nodes.BodyDeclaration) {
scopeNode = node.parent.getParent().getDeclarations();
}
if (node.parent instanceof nodes.FunctionArgument && node.parent.getParent() instanceof nodes.Function) {
const funcId = node.parent.getParent().getIdentifier();
if (funcId) {
const functionSymbol = this.internalFindSymbol(funcId, [nodes.ReferenceType.Function]);
if (functionSymbol) {
scopeNode = functionSymbol.node.getDeclarations();
}
}
}
if (!scopeNode) {
return null;
}
const name = node.getText();
let scope = this.global.findScope(scopeNode.offset, scopeNode.length);
while (scope) {
for (let index = 0; index < referenceTypes.length; index++) {
const type = referenceTypes[index];
const symbol = scope.getSymbol(name, type);
if (symbol) {
return symbol;
}
}
scope = scope.parent;
}
return null;
}
evaluateReferenceTypes(node) {
if (node instanceof nodes.Identifier) {
const referenceTypes = node.referenceTypes;
if (referenceTypes) {
return referenceTypes;
}
else {
if (node.isCustomProperty) {
return [nodes.ReferenceType.Variable];
}
// are a reference to a keyframe?
const decl = nodes.getParentDeclaration(node);
if (decl) {
const propertyName = decl.getNonPrefixedPropertyName();
if ((propertyName === 'animation' || propertyName === 'animation-name')
&& decl.getValue() && decl.getValue().offset === node.offset) {
return [nodes.ReferenceType.Keyframe];
}
}
}
}
else if (node instanceof nodes.Variable) {
return [nodes.ReferenceType.Variable];
}
const selector = node.findAParent(nodes.NodeType.Selector, nodes.NodeType.ExtendsReference);
if (selector) {
return [nodes.ReferenceType.Rule];
}
return null;
}
findSymbolFromNode(node) {
if (!node) {
return null;
}
while (node.type === nodes.NodeType.Interpolation) {
node = node.getParent();
}
const referenceTypes = this.evaluateReferenceTypes(node);
if (referenceTypes) {
return this.internalFindSymbol(node, referenceTypes);
}
return null;
}
matchesSymbol(node, symbol) {
if (!node) {
return false;
}
while (node.type === nodes.NodeType.Interpolation) {
node = node.getParent();
}
if (!node.matches(symbol.name)) {
return false;
}
const referenceTypes = this.evaluateReferenceTypes(node);
if (!referenceTypes || referenceTypes.indexOf(symbol.type) === -1) {
return false;
}
const nodeSymbol = this.internalFindSymbol(node, referenceTypes);
return nodeSymbol === symbol;
}
findSymbol(name, type, offset) {
let scope = this.global.findScope(offset);
while (scope) {
const symbol = scope.getSymbol(name, type);
if (symbol) {
return symbol;
}
scope = scope.parent;
}
return null;
}
}

View file

@ -0,0 +1,716 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as lessScanner from './lessScanner';
import { TokenType } from './cssScanner';
import * as cssParser from './cssParser';
import * as nodes from './cssNodes';
import { ParseError } from './cssErrors';
/// <summary>
/// A parser for LESS
/// http://lesscss.org/
/// </summary>
export class LESSParser extends cssParser.Parser {
constructor() {
super(new lessScanner.LESSScanner());
}
_parseStylesheetStatement(isNested = false) {
if (this.peek(TokenType.AtKeyword)) {
return this._parseVariableDeclaration()
|| this._parsePlugin()
|| super._parseStylesheetAtStatement(isNested);
}
return this._tryParseMixinDeclaration()
|| this._tryParseMixinReference()
|| this._parseFunction()
|| this._parseRuleset(true);
}
_parseImport() {
if (!this.peekKeyword('@import') && !this.peekKeyword('@import-once') /* deprecated in less 1.4.1 */) {
return null;
}
const node = this.create(nodes.Import);
this.consumeToken();
// less 1.4.1: @import (css) "lib"
if (this.accept(TokenType.ParenthesisL)) {
if (!this.accept(TokenType.Ident)) {
return this.finish(node, ParseError.IdentifierExpected, [TokenType.SemiColon]);
}
do {
if (!this.accept(TokenType.Comma)) {
break;
}
} while (this.accept(TokenType.Ident));
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.SemiColon]);
}
}
if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) {
return this.finish(node, ParseError.URIOrStringExpected, [TokenType.SemiColon]);
}
if (!this.peek(TokenType.SemiColon) && !this.peek(TokenType.EOF)) {
node.setMedialist(this._parseMediaQueryList());
}
return this.finish(node);
}
_parsePlugin() {
if (!this.peekKeyword('@plugin')) {
return null;
}
const node = this.createNode(nodes.NodeType.Plugin);
this.consumeToken(); // @import
if (!node.addChild(this._parseStringLiteral())) {
return this.finish(node, ParseError.StringLiteralExpected);
}
if (!this.accept(TokenType.SemiColon)) {
return this.finish(node, ParseError.SemiColonExpected);
}
return this.finish(node);
}
_parseMediaQuery() {
const node = super._parseMediaQuery();
if (!node) {
const node = this.create(nodes.MediaQuery);
if (node.addChild(this._parseVariable())) {
return this.finish(node);
}
return null;
}
return node;
}
_parseMediaDeclaration(isNested = false) {
return this._tryParseRuleset(isNested)
|| this._tryToParseDeclaration()
|| this._tryParseMixinDeclaration()
|| this._tryParseMixinReference()
|| this._parseDetachedRuleSetMixin()
|| this._parseStylesheetStatement(isNested);
}
_parseMediaFeatureName() {
return this._parseIdent() || this._parseVariable();
}
_parseVariableDeclaration(panic = []) {
const node = this.create(nodes.VariableDeclaration);
const mark = this.mark();
if (!node.setVariable(this._parseVariable(true))) {
return null;
}
if (this.accept(TokenType.Colon)) {
if (this.prevToken) {
node.colonPosition = this.prevToken.offset;
}
if (node.setValue(this._parseDetachedRuleSet())) {
node.needsSemicolon = false;
}
else if (!node.setValue(this._parseExpr())) {
return this.finish(node, ParseError.VariableValueExpected, [], panic);
}
node.addChild(this._parsePrio());
}
else {
this.restoreAtMark(mark);
return null; // at keyword, but no ':', not a variable declaration but some at keyword
}
if (this.peek(TokenType.SemiColon)) {
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
}
return this.finish(node);
}
_parseDetachedRuleSet() {
let mark = this.mark();
// "Anonymous mixin" used in each() and possibly a generic type in the future
if (this.peekDelim('#') || this.peekDelim('.')) {
this.consumeToken();
if (!this.hasWhitespace() && this.accept(TokenType.ParenthesisL)) {
let node = this.create(nodes.MixinDeclaration);
if (node.getParameters().addChild(this._parseMixinParameter())) {
while (this.accept(TokenType.Comma) || this.accept(TokenType.SemiColon)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseMixinParameter())) {
this.markError(node, ParseError.IdentifierExpected, [], [TokenType.ParenthesisR]);
}
}
}
if (!this.accept(TokenType.ParenthesisR)) {
this.restoreAtMark(mark);
return null;
}
}
else {
this.restoreAtMark(mark);
return null;
}
}
if (!this.peek(TokenType.CurlyL)) {
return null;
}
const content = this.create(nodes.BodyDeclaration);
this._parseBody(content, this._parseDetachedRuleSetBody.bind(this));
return this.finish(content);
}
_parseDetachedRuleSetBody() {
return this._tryParseKeyframeSelector() || this._parseRuleSetDeclaration();
}
_addLookupChildren(node) {
if (!node.addChild(this._parseLookupValue())) {
return false;
}
let expectsValue = false;
while (true) {
if (this.peek(TokenType.BracketL)) {
expectsValue = true;
}
if (!node.addChild(this._parseLookupValue())) {
break;
}
expectsValue = false;
}
return !expectsValue;
}
_parseLookupValue() {
const node = this.create(nodes.Node);
const mark = this.mark();
if (!this.accept(TokenType.BracketL)) {
this.restoreAtMark(mark);
return null;
}
if (((node.addChild(this._parseVariable(false, true)) ||
node.addChild(this._parsePropertyIdentifier())) &&
this.accept(TokenType.BracketR)) || this.accept(TokenType.BracketR)) {
return node;
}
this.restoreAtMark(mark);
return null;
}
_parseVariable(declaration = false, insideLookup = false) {
const isPropertyReference = !declaration && this.peekDelim('$');
if (!this.peekDelim('@') && !isPropertyReference && !this.peek(TokenType.AtKeyword)) {
return null;
}
const node = this.create(nodes.Variable);
const mark = this.mark();
while (this.acceptDelim('@') || (!declaration && this.acceptDelim('$'))) {
if (this.hasWhitespace()) {
this.restoreAtMark(mark);
return null;
}
}
if (!this.accept(TokenType.AtKeyword) && !this.accept(TokenType.Ident)) {
this.restoreAtMark(mark);
return null;
}
if (!insideLookup && this.peek(TokenType.BracketL)) {
if (!this._addLookupChildren(node)) {
this.restoreAtMark(mark);
return null;
}
}
return node;
}
_parseTermExpression() {
return this._parseVariable() ||
this._parseEscaped() ||
super._parseTermExpression() || // preference for colors before mixin references
this._tryParseMixinReference(false);
}
_parseEscaped() {
if (this.peek(TokenType.EscapedJavaScript) ||
this.peek(TokenType.BadEscapedJavaScript)) {
const node = this.createNode(nodes.NodeType.EscapedValue);
this.consumeToken();
return this.finish(node);
}
if (this.peekDelim('~')) {
const node = this.createNode(nodes.NodeType.EscapedValue);
this.consumeToken();
if (this.accept(TokenType.String) || this.accept(TokenType.EscapedJavaScript)) {
return this.finish(node);
}
else {
return this.finish(node, ParseError.TermExpected);
}
}
return null;
}
_parseOperator() {
const node = this._parseGuardOperator();
if (node) {
return node;
}
else {
return super._parseOperator();
}
}
_parseGuardOperator() {
if (this.peekDelim('>')) {
const node = this.createNode(nodes.NodeType.Operator);
this.consumeToken();
this.acceptDelim('=');
return node;
}
else if (this.peekDelim('=')) {
const node = this.createNode(nodes.NodeType.Operator);
this.consumeToken();
this.acceptDelim('<');
return node;
}
else if (this.peekDelim('<')) {
const node = this.createNode(nodes.NodeType.Operator);
this.consumeToken();
this.acceptDelim('=');
return node;
}
return null;
}
_parseRuleSetDeclaration() {
if (this.peek(TokenType.AtKeyword)) {
return this._parseKeyframe()
|| this._parseMedia(true)
|| this._parseImport()
|| this._parseSupports(true) // @supports
|| this._parseLayer() // @layer
|| this._parsePropertyAtRule() // @property
|| this._parseDetachedRuleSetMixin() // less detached ruleset mixin
|| this._parseVariableDeclaration() // Variable declarations
|| this._parseRuleSetDeclarationAtStatement();
}
return this._tryParseMixinDeclaration()
|| this._tryParseRuleset(true) // nested ruleset
|| this._tryParseMixinReference() // less mixin reference
|| this._parseFunction()
|| this._parseExtend() // less extend declaration
|| this._parseDeclaration(); // try css ruleset declaration as the last option
}
_parseKeyframeIdent() {
return this._parseIdent([nodes.ReferenceType.Keyframe]) || this._parseVariable();
}
_parseKeyframeSelector() {
return this._parseDetachedRuleSetMixin() // less detached ruleset mixin
|| super._parseKeyframeSelector();
}
// public _parseSimpleSelectorBody(): nodes.Node | null {
// return this._parseNestingSelector() || super._parseSimpleSelectorBody();
// }
_parseSelector(isNested) {
// CSS Guards
const node = this.create(nodes.Selector);
let hasContent = false;
if (isNested) {
// nested selectors can start with a combinator
hasContent = node.addChild(this._parseCombinator());
}
while (node.addChild(this._parseSimpleSelector())) {
hasContent = true;
const mark = this.mark();
if (node.addChild(this._parseGuard()) && this.peek(TokenType.CurlyL)) {
break;
}
this.restoreAtMark(mark);
node.addChild(this._parseCombinator()); // optional
}
return hasContent ? this.finish(node) : null;
}
_parseNestingSelector() {
if (this.peekDelim('&')) {
const node = this.createNode(nodes.NodeType.SelectorCombinator);
this.consumeToken();
while (!this.hasWhitespace() && (this.acceptDelim('-') || this.accept(TokenType.Num) || this.accept(TokenType.Dimension) || node.addChild(this._parseIdent()) || this.acceptDelim('&'))) {
// support &-foo
}
return this.finish(node);
}
return null;
}
_parseSelectorIdent() {
if (!this.peekInterpolatedIdent()) {
return null;
}
const node = this.createNode(nodes.NodeType.SelectorInterpolation);
const hasContent = this._acceptInterpolatedIdent(node);
return hasContent ? this.finish(node) : null;
}
_parsePropertyIdentifier(inLookup = false) {
const propertyRegex = /^[\w-]+/;
if (!this.peekInterpolatedIdent() && !this.peekRegExp(this.token.type, propertyRegex)) {
return null;
}
const mark = this.mark();
const node = this.create(nodes.Identifier);
node.isCustomProperty = this.acceptDelim('-') && this.acceptDelim('-');
let childAdded = false;
if (!inLookup) {
if (node.isCustomProperty) {
childAdded = this._acceptInterpolatedIdent(node);
}
else {
childAdded = this._acceptInterpolatedIdent(node, propertyRegex);
}
}
else {
if (node.isCustomProperty) {
childAdded = node.addChild(this._parseIdent());
}
else {
childAdded = node.addChild(this._parseRegexp(propertyRegex));
}
}
if (!childAdded) {
this.restoreAtMark(mark);
return null;
}
if (!inLookup && !this.hasWhitespace()) {
this.acceptDelim('+');
if (!this.hasWhitespace()) {
this.acceptIdent('_');
}
}
return this.finish(node);
}
peekInterpolatedIdent() {
return this.peek(TokenType.Ident) ||
this.peekDelim('@') ||
this.peekDelim('$') ||
this.peekDelim('-');
}
_acceptInterpolatedIdent(node, identRegex) {
let hasContent = false;
const indentInterpolation = () => {
const pos = this.mark();
if (this.acceptDelim('-')) {
if (!this.hasWhitespace()) {
this.acceptDelim('-');
}
if (this.hasWhitespace()) {
this.restoreAtMark(pos);
return null;
}
}
return this._parseInterpolation();
};
const accept = identRegex ?
() => this.acceptRegexp(identRegex) :
() => this.accept(TokenType.Ident);
while (accept() ||
node.addChild(this._parseInterpolation() ||
this.try(indentInterpolation))) {
hasContent = true;
if (this.hasWhitespace()) {
break;
}
}
return hasContent;
}
_parseInterpolation() {
// @{name} Variable or
// ${name} Property
const mark = this.mark();
if (this.peekDelim('@') || this.peekDelim('$')) {
const node = this.createNode(nodes.NodeType.Interpolation);
this.consumeToken();
if (this.hasWhitespace() || !this.accept(TokenType.CurlyL)) {
this.restoreAtMark(mark);
return null;
}
if (!node.addChild(this._parseIdent())) {
return this.finish(node, ParseError.IdentifierExpected);
}
if (!this.accept(TokenType.CurlyR)) {
return this.finish(node, ParseError.RightCurlyExpected);
}
return this.finish(node);
}
return null;
}
_tryParseMixinDeclaration() {
const mark = this.mark();
const node = this.create(nodes.MixinDeclaration);
if (!node.setIdentifier(this._parseMixinDeclarationIdentifier()) || !this.accept(TokenType.ParenthesisL)) {
this.restoreAtMark(mark);
return null;
}
if (node.getParameters().addChild(this._parseMixinParameter())) {
while (this.accept(TokenType.Comma) || this.accept(TokenType.SemiColon)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseMixinParameter())) {
this.markError(node, ParseError.IdentifierExpected, [], [TokenType.ParenthesisR]);
}
}
}
if (!this.accept(TokenType.ParenthesisR)) {
this.restoreAtMark(mark);
return null;
}
node.setGuard(this._parseGuard());
if (!this.peek(TokenType.CurlyL)) {
this.restoreAtMark(mark);
return null;
}
return this._parseBody(node, this._parseMixInBodyDeclaration.bind(this));
}
_parseMixInBodyDeclaration() {
return this._parseFontFace() || this._parseRuleSetDeclaration();
}
_parseMixinDeclarationIdentifier() {
let identifier;
if (this.peekDelim('#') || this.peekDelim('.')) {
identifier = this.create(nodes.Identifier);
this.consumeToken(); // # or .
if (this.hasWhitespace() || !identifier.addChild(this._parseIdent())) {
return null;
}
}
else if (this.peek(TokenType.Hash)) {
identifier = this.create(nodes.Identifier);
this.consumeToken(); // TokenType.Hash
}
else {
return null;
}
identifier.referenceTypes = [nodes.ReferenceType.Mixin];
return this.finish(identifier);
}
_parsePseudo() {
if (!this.peek(TokenType.Colon)) {
return null;
}
const mark = this.mark();
const node = this.create(nodes.ExtendsReference);
this.consumeToken(); // :
if (this.acceptIdent('extend')) {
return this._completeExtends(node);
}
this.restoreAtMark(mark);
return super._parsePseudo();
}
_parseExtend() {
if (!this.peekDelim('&')) {
return null;
}
const mark = this.mark();
const node = this.create(nodes.ExtendsReference);
this.consumeToken(); // &
if (this.hasWhitespace() || !this.accept(TokenType.Colon) || !this.acceptIdent('extend')) {
this.restoreAtMark(mark);
return null;
}
return this._completeExtends(node);
}
_completeExtends(node) {
if (!this.accept(TokenType.ParenthesisL)) {
return this.finish(node, ParseError.LeftParenthesisExpected);
}
const selectors = node.getSelectors();
if (!selectors.addChild(this._parseSelector(true))) {
return this.finish(node, ParseError.SelectorExpected);
}
while (this.accept(TokenType.Comma)) {
if (!selectors.addChild(this._parseSelector(true))) {
return this.finish(node, ParseError.SelectorExpected);
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
return this.finish(node);
}
_parseDetachedRuleSetMixin() {
if (!this.peek(TokenType.AtKeyword)) {
return null;
}
const mark = this.mark();
const node = this.create(nodes.MixinReference);
if (node.addChild(this._parseVariable(true)) && (this.hasWhitespace() || !this.accept(TokenType.ParenthesisL))) {
this.restoreAtMark(mark);
return null;
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
return this.finish(node);
}
_tryParseMixinReference(atRoot = true) {
const mark = this.mark();
const node = this.create(nodes.MixinReference);
let identifier = this._parseMixinDeclarationIdentifier();
while (identifier) {
this.acceptDelim('>');
const nextId = this._parseMixinDeclarationIdentifier();
if (nextId) {
node.getNamespaces().addChild(identifier);
identifier = nextId;
}
else {
break;
}
}
if (!node.setIdentifier(identifier)) {
this.restoreAtMark(mark);
return null;
}
let hasArguments = false;
if (this.accept(TokenType.ParenthesisL)) {
hasArguments = true;
if (node.getArguments().addChild(this._parseMixinArgument())) {
while (this.accept(TokenType.Comma) || this.accept(TokenType.SemiColon)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getArguments().addChild(this._parseMixinArgument())) {
return this.finish(node, ParseError.ExpressionExpected);
}
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
identifier.referenceTypes = [nodes.ReferenceType.Mixin];
}
else {
identifier.referenceTypes = [nodes.ReferenceType.Mixin, nodes.ReferenceType.Rule];
}
if (this.peek(TokenType.BracketL)) {
if (!atRoot) {
this._addLookupChildren(node);
}
}
else {
node.addChild(this._parsePrio());
}
if (!hasArguments && !this.peek(TokenType.SemiColon) && !this.peek(TokenType.CurlyR) && !this.peek(TokenType.EOF)) {
this.restoreAtMark(mark);
return null;
}
return this.finish(node);
}
_parseMixinArgument() {
// [variableName ':'] expression | variableName '...'
const node = this.create(nodes.FunctionArgument);
const pos = this.mark();
const argument = this._parseVariable();
if (argument) {
if (!this.accept(TokenType.Colon)) {
this.restoreAtMark(pos);
}
else {
node.setIdentifier(argument);
}
}
if (node.setValue(this._parseDetachedRuleSet() || this._parseExpr(true))) {
return this.finish(node);
}
this.restoreAtMark(pos);
return null;
}
_parseMixinParameter() {
const node = this.create(nodes.FunctionParameter);
// special rest variable: @rest...
if (this.peekKeyword('@rest')) {
const restNode = this.create(nodes.Node);
this.consumeToken();
if (!this.accept(lessScanner.Ellipsis)) {
return this.finish(node, ParseError.DotExpected, [], [TokenType.Comma, TokenType.ParenthesisR]);
}
node.setIdentifier(this.finish(restNode));
return this.finish(node);
}
// special const args: ...
if (this.peek(lessScanner.Ellipsis)) {
const varargsNode = this.create(nodes.Node);
this.consumeToken();
node.setIdentifier(this.finish(varargsNode));
return this.finish(node);
}
let hasContent = false;
// default variable declaration: @param: 12 or @name
if (node.setIdentifier(this._parseVariable())) {
this.accept(TokenType.Colon);
hasContent = true;
}
if (!node.setDefaultValue(this._parseDetachedRuleSet() || this._parseExpr(true)) && !hasContent) {
return null;
}
return this.finish(node);
}
_parseGuard() {
if (!this.peekIdent('when')) {
return null;
}
const node = this.create(nodes.LessGuard);
this.consumeToken(); // when
node.isNegated = this.acceptIdent('not');
if (!node.getConditions().addChild(this._parseGuardCondition())) {
return this.finish(node, ParseError.ConditionExpected);
}
while (this.acceptIdent('and') || this.accept(TokenType.Comma)) {
if (!node.getConditions().addChild(this._parseGuardCondition())) {
return this.finish(node, ParseError.ConditionExpected);
}
}
return this.finish(node);
}
_parseGuardCondition() {
if (!this.peek(TokenType.ParenthesisL)) {
return null;
}
const node = this.create(nodes.GuardCondition);
this.consumeToken(); // ParenthesisL
if (!node.addChild(this._parseExpr())) {
// empty (?)
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
return this.finish(node);
}
_parseFunction() {
const pos = this.mark();
const node = this.create(nodes.Function);
if (!node.setIdentifier(this._parseFunctionIdentifier())) {
return null;
}
if (this.hasWhitespace() || !this.accept(TokenType.ParenthesisL)) {
this.restoreAtMark(pos);
return null;
}
if (node.getArguments().addChild(this._parseMixinArgument())) {
while (this.accept(TokenType.Comma) || this.accept(TokenType.SemiColon)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getArguments().addChild(this._parseMixinArgument())) {
return this.finish(node, ParseError.ExpressionExpected);
}
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
return this.finish(node);
}
_parseFunctionIdentifier() {
if (this.peekDelim('%')) {
const node = this.create(nodes.Identifier);
node.referenceTypes = [nodes.ReferenceType.Function];
this.consumeToken();
return this.finish(node);
}
return super._parseFunctionIdentifier();
}
_parseURLArgument() {
const pos = this.mark();
const node = super._parseURLArgument();
if (!node || !this.peek(TokenType.ParenthesisR)) {
this.restoreAtMark(pos);
const node = this.create(nodes.Node);
node.addChild(this._parseBinaryExpr());
return this.finish(node);
}
return node;
}
}

View file

@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as scanner from './cssScanner';
const _FSL = '/'.charCodeAt(0);
const _NWL = '\n'.charCodeAt(0);
const _CAR = '\r'.charCodeAt(0);
const _LFD = '\f'.charCodeAt(0);
const _TIC = '`'.charCodeAt(0);
const _DOT = '.'.charCodeAt(0);
let customTokenValue = scanner.TokenType.CustomToken;
export const Ellipsis = customTokenValue++;
export class LESSScanner extends scanner.Scanner {
scanNext(offset) {
// LESS: escaped JavaScript code `const a = "dddd"`
const tokenType = this.escapedJavaScript();
if (tokenType !== null) {
return this.finishToken(offset, tokenType);
}
if (this.stream.advanceIfChars([_DOT, _DOT, _DOT])) {
return this.finishToken(offset, Ellipsis);
}
return super.scanNext(offset);
}
comment() {
if (super.comment()) {
return true;
}
if (!this.inURL && this.stream.advanceIfChars([_FSL, _FSL])) {
this.stream.advanceWhileChar((ch) => {
switch (ch) {
case _NWL:
case _CAR:
case _LFD:
return false;
default:
return true;
}
});
return true;
}
else {
return false;
}
}
escapedJavaScript() {
const ch = this.stream.peekChar();
if (ch === _TIC) {
this.stream.advance(1);
this.stream.advanceWhileChar((ch) => { return ch !== _TIC; });
return this.stream.advanceIfChar(_TIC) ? scanner.TokenType.EscapedJavaScript : scanner.TokenType.BadEscapedJavaScript;
}
return null;
}
}

View file

@ -0,0 +1,17 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as l10n from '@vscode/l10n';
export class SCSSIssueType {
constructor(id, message) {
this.id = id;
this.message = message;
}
}
export const SCSSParseError = {
FromExpected: new SCSSIssueType('scss-fromexpected', l10n.t("'from' expected")),
ThroughOrToExpected: new SCSSIssueType('scss-throughexpected', l10n.t("'through' or 'to' expected")),
InExpected: new SCSSIssueType('scss-fromexpected', l10n.t("'in' expected")),
};

View file

@ -0,0 +1,810 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as scssScanner from './scssScanner';
import { TokenType } from './cssScanner';
import * as cssParser from './cssParser';
import * as nodes from './cssNodes';
import { SCSSParseError } from './scssErrors';
import { ParseError } from './cssErrors';
/// <summary>
/// A parser for scss
/// http://sass-lang.com/documentation/file.SASS_REFERENCE.html
/// </summary>
export class SCSSParser extends cssParser.Parser {
constructor() {
super(new scssScanner.SCSSScanner());
}
_parseStylesheetStatement(isNested = false) {
if (this.peek(TokenType.AtKeyword)) {
return this._parseWarnAndDebug() // @warn, @debug and @error statements
|| this._parseControlStatement() // @if, @while, @for, @each
|| this._parseMixinDeclaration() // @mixin
|| this._parseMixinContent() // @content
|| this._parseMixinReference() // @include
|| this._parseFunctionDeclaration() // @function
|| this._parseForward() // @forward
|| this._parseUse() // @use
|| this._parseRuleset(isNested) // @at-rule
|| super._parseStylesheetAtStatement(isNested);
}
return this._parseRuleset(true) || this._parseVariableDeclaration();
}
_parseImport() {
if (!this.peekKeyword('@import')) {
return null;
}
const node = this.create(nodes.Import);
this.consumeToken();
if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) {
return this.finish(node, ParseError.URIOrStringExpected);
}
while (this.accept(TokenType.Comma)) {
if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) {
return this.finish(node, ParseError.URIOrStringExpected);
}
}
if (!this.peek(TokenType.SemiColon) && !this.peek(TokenType.EOF)) {
node.setMedialist(this._parseMediaQueryList());
}
return this.finish(node);
}
// scss variables: $font-size: 12px;
_parseVariableDeclaration(panic = []) {
if (!this.peek(scssScanner.VariableName)) {
return null;
}
const node = this.create(nodes.VariableDeclaration);
if (!node.setVariable(this._parseVariable())) {
return null;
}
if (!this.accept(TokenType.Colon)) {
return this.finish(node, ParseError.ColonExpected);
}
if (this.prevToken) {
node.colonPosition = this.prevToken.offset;
}
if (!node.setValue(this._parseExpr())) {
return this.finish(node, ParseError.VariableValueExpected, [], panic);
}
while (this.peek(TokenType.Exclamation)) {
if (node.addChild(this._tryParsePrio())) {
// !important
}
else {
this.consumeToken();
if (!this.peekRegExp(TokenType.Ident, /^(default|global)$/)) {
return this.finish(node, ParseError.UnknownKeyword);
}
this.consumeToken();
}
}
if (this.peek(TokenType.SemiColon)) {
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
}
return this.finish(node);
}
_parseMediaCondition() {
return this._parseInterpolation() || super._parseMediaCondition();
}
_parseMediaFeatureRangeOperator() {
return this.accept(scssScanner.SmallerEqualsOperator) || this.accept(scssScanner.GreaterEqualsOperator) || super._parseMediaFeatureRangeOperator();
}
_parseMediaFeatureName() {
return this._parseModuleMember()
|| this._parseFunction() // function before ident
|| this._parseIdent()
|| this._parseVariable();
}
_parseKeyframeSelector() {
return this._tryParseKeyframeSelector()
|| this._parseControlStatement(this._parseKeyframeSelector.bind(this))
|| this._parseVariableDeclaration()
|| this._parseMixinContent();
}
_parseVariable() {
if (!this.peek(scssScanner.VariableName)) {
return null;
}
const node = this.create(nodes.Variable);
this.consumeToken();
return node;
}
_parseModuleMember() {
const pos = this.mark();
const node = this.create(nodes.Module);
if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Module]))) {
return null;
}
if (this.hasWhitespace()
|| !this.acceptDelim('.')
|| this.hasWhitespace()) {
this.restoreAtMark(pos);
return null;
}
if (!node.addChild(this._parseVariable() || this._parseFunction())) {
return this.finish(node, ParseError.IdentifierOrVariableExpected);
}
return node;
}
_parseIdent(referenceTypes) {
if (!this.peek(TokenType.Ident) && !this.peek(scssScanner.InterpolationFunction) && !this.peekDelim('-')) {
return null;
}
const node = this.create(nodes.Identifier);
node.referenceTypes = referenceTypes;
node.isCustomProperty = this.peekRegExp(TokenType.Ident, /^--/);
let hasContent = false;
const indentInterpolation = () => {
const pos = this.mark();
if (this.acceptDelim('-')) {
if (!this.hasWhitespace()) {
this.acceptDelim('-');
}
if (this.hasWhitespace()) {
this.restoreAtMark(pos);
return null;
}
}
return this._parseInterpolation();
};
while (this.accept(TokenType.Ident) || node.addChild(indentInterpolation()) || (hasContent && this.acceptRegexp(/^[\w-]/))) {
hasContent = true;
if (this.hasWhitespace()) {
break;
}
}
return hasContent ? this.finish(node) : null;
}
_parseTermExpression() {
return this._parseModuleMember() ||
this._parseVariable() ||
this._parseNestingSelector() ||
//this._tryParsePrio() ||
super._parseTermExpression();
}
_parseInterpolation() {
if (this.peek(scssScanner.InterpolationFunction)) {
const node = this.create(nodes.Interpolation);
this.consumeToken();
if (!node.addChild(this._parseExpr()) && !this._parseNestingSelector()) {
if (this.accept(TokenType.CurlyR)) {
return this.finish(node);
}
return this.finish(node, ParseError.ExpressionExpected);
}
if (!this.accept(TokenType.CurlyR)) {
return this.finish(node, ParseError.RightCurlyExpected);
}
return this.finish(node);
}
return null;
}
_parseOperator() {
if (this.peek(scssScanner.EqualsOperator) || this.peek(scssScanner.NotEqualsOperator)
|| this.peek(scssScanner.GreaterEqualsOperator) || this.peek(scssScanner.SmallerEqualsOperator)
|| this.peekDelim('>') || this.peekDelim('<')
|| this.peekIdent('and') || this.peekIdent('or')
|| this.peekDelim('%')) {
const node = this.createNode(nodes.NodeType.Operator);
this.consumeToken();
return this.finish(node);
}
return super._parseOperator();
}
_parseUnaryOperator() {
if (this.peekIdent('not')) {
const node = this.create(nodes.Node);
this.consumeToken();
return this.finish(node);
}
return super._parseUnaryOperator();
}
_parseRuleSetDeclaration() {
if (this.peek(TokenType.AtKeyword)) {
return this._parseKeyframe() // nested @keyframe
|| this._parseImport() // nested @import
|| this._parseMedia(true) // nested @media
|| this._parseFontFace() // nested @font-face
|| this._parseWarnAndDebug() // @warn, @debug and @error statements
|| this._parseControlStatement() // @if, @while, @for, @each
|| this._parseFunctionDeclaration() // @function
|| this._parseExtends() // @extends
|| this._parseMixinReference() // @include
|| this._parseMixinContent() // @content
|| this._parseMixinDeclaration() // nested @mixin
|| this._parseRuleset(true) // @at-rule
|| this._parseSupports(true) // @supports
|| this._parseLayer() // @layer
|| this._parsePropertyAtRule() // @property
|| this._parseRuleSetDeclarationAtStatement();
}
return this._parseVariableDeclaration() // variable declaration
|| this._tryParseRuleset(true) // nested ruleset
|| this._parseDeclaration(); // try css ruleset declaration as last so in the error case, the ast will contain a declaration
}
_parseDeclaration(stopTokens) {
const custonProperty = this._tryParseCustomPropertyDeclaration(stopTokens);
if (custonProperty) {
return custonProperty;
}
const node = this.create(nodes.Declaration);
if (!node.setProperty(this._parseProperty())) {
return null;
}
if (!this.accept(TokenType.Colon)) {
return this.finish(node, ParseError.ColonExpected, [TokenType.Colon], stopTokens || [TokenType.SemiColon]);
}
if (this.prevToken) {
node.colonPosition = this.prevToken.offset;
}
let hasContent = false;
if (node.setValue(this._parseExpr())) {
hasContent = true;
node.addChild(this._parsePrio());
}
if (this.peek(TokenType.CurlyL)) {
node.setNestedProperties(this._parseNestedProperties());
}
else {
if (!hasContent) {
return this.finish(node, ParseError.PropertyValueExpected);
}
}
if (this.peek(TokenType.SemiColon)) {
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
}
return this.finish(node);
}
_parseNestedProperties() {
const node = this.create(nodes.NestedProperties);
return this._parseBody(node, this._parseDeclaration.bind(this));
}
_parseExtends() {
if (this.peekKeyword('@extend')) {
const node = this.create(nodes.ExtendsReference);
this.consumeToken();
if (!node.getSelectors().addChild(this._parseSimpleSelector())) {
return this.finish(node, ParseError.SelectorExpected);
}
while (this.accept(TokenType.Comma)) {
node.getSelectors().addChild(this._parseSimpleSelector());
}
if (this.accept(TokenType.Exclamation)) {
if (!this.acceptIdent('optional')) {
return this.finish(node, ParseError.UnknownKeyword);
}
}
return this.finish(node);
}
return null;
}
_parseSimpleSelectorBody() {
return this._parseSelectorPlaceholder() || super._parseSimpleSelectorBody();
}
_parseNestingSelector() {
if (this.peekDelim('&')) {
const node = this.createNode(nodes.NodeType.SelectorCombinator);
this.consumeToken();
while (!this.hasWhitespace() && (this.acceptDelim('-') || this.accept(TokenType.Num) || this.accept(TokenType.Dimension) || node.addChild(this._parseIdent()) || this.acceptDelim('&'))) {
// support &-foo-1
}
return this.finish(node);
}
return null;
}
_parseSelectorPlaceholder() {
if (this.peekDelim('%')) {
const node = this.createNode(nodes.NodeType.SelectorPlaceholder);
this.consumeToken();
this._parseIdent();
return this.finish(node);
}
else if (this.peekKeyword('@at-root')) {
const node = this.createNode(nodes.NodeType.SelectorPlaceholder);
this.consumeToken();
if (this.accept(TokenType.ParenthesisL)) {
if (!this.acceptIdent('with') && !this.acceptIdent('without')) {
return this.finish(node, ParseError.IdentifierExpected);
}
if (!this.accept(TokenType.Colon)) {
return this.finish(node, ParseError.ColonExpected);
}
if (!node.addChild(this._parseIdent())) {
return this.finish(node, ParseError.IdentifierExpected);
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]);
}
}
return this.finish(node);
}
return null;
}
_parseElementName() {
const pos = this.mark();
const node = super._parseElementName();
if (node && !this.hasWhitespace() && this.peek(TokenType.ParenthesisL)) { // for #49589
this.restoreAtMark(pos);
return null;
}
return node;
}
_tryParsePseudoIdentifier() {
return this._parseInterpolation() || super._tryParsePseudoIdentifier(); // for #49589
}
_parseWarnAndDebug() {
if (!this.peekKeyword('@debug')
&& !this.peekKeyword('@warn')
&& !this.peekKeyword('@error')) {
return null;
}
const node = this.createNode(nodes.NodeType.Debug);
this.consumeToken(); // @debug, @warn or @error
node.addChild(this._parseExpr()); // optional
return this.finish(node);
}
_parseControlStatement(parseStatement = this._parseRuleSetDeclaration.bind(this)) {
if (!this.peek(TokenType.AtKeyword)) {
return null;
}
return this._parseIfStatement(parseStatement) || this._parseForStatement(parseStatement)
|| this._parseEachStatement(parseStatement) || this._parseWhileStatement(parseStatement);
}
_parseIfStatement(parseStatement) {
if (!this.peekKeyword('@if')) {
return null;
}
return this._internalParseIfStatement(parseStatement);
}
_internalParseIfStatement(parseStatement) {
const node = this.create(nodes.IfStatement);
this.consumeToken(); // @if or if
if (!node.setExpression(this._parseExpr(true))) {
return this.finish(node, ParseError.ExpressionExpected);
}
this._parseBody(node, parseStatement);
if (this.acceptKeyword('@else')) {
if (this.peekIdent('if')) {
node.setElseClause(this._internalParseIfStatement(parseStatement));
}
else if (this.peek(TokenType.CurlyL)) {
const elseNode = this.create(nodes.ElseStatement);
this._parseBody(elseNode, parseStatement);
node.setElseClause(elseNode);
}
}
return this.finish(node);
}
_parseForStatement(parseStatement) {
if (!this.peekKeyword('@for')) {
return null;
}
const node = this.create(nodes.ForStatement);
this.consumeToken(); // @for
if (!node.setVariable(this._parseVariable())) {
return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]);
}
if (!this.acceptIdent('from')) {
return this.finish(node, SCSSParseError.FromExpected, [TokenType.CurlyR]);
}
if (!node.addChild(this._parseBinaryExpr())) {
return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]);
}
if (!this.acceptIdent('to') && !this.acceptIdent('through')) {
return this.finish(node, SCSSParseError.ThroughOrToExpected, [TokenType.CurlyR]);
}
if (!node.addChild(this._parseBinaryExpr())) {
return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]);
}
return this._parseBody(node, parseStatement);
}
_parseEachStatement(parseStatement) {
if (!this.peekKeyword('@each')) {
return null;
}
const node = this.create(nodes.EachStatement);
this.consumeToken(); // @each
const variables = node.getVariables();
if (!variables.addChild(this._parseVariable())) {
return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]);
}
while (this.accept(TokenType.Comma)) {
if (!variables.addChild(this._parseVariable())) {
return this.finish(node, ParseError.VariableNameExpected, [TokenType.CurlyR]);
}
}
this.finish(variables);
if (!this.acceptIdent('in')) {
return this.finish(node, SCSSParseError.InExpected, [TokenType.CurlyR]);
}
if (!node.addChild(this._parseExpr())) {
return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]);
}
return this._parseBody(node, parseStatement);
}
_parseWhileStatement(parseStatement) {
if (!this.peekKeyword('@while')) {
return null;
}
const node = this.create(nodes.WhileStatement);
this.consumeToken(); // @while
if (!node.addChild(this._parseBinaryExpr())) {
return this.finish(node, ParseError.ExpressionExpected, [TokenType.CurlyR]);
}
return this._parseBody(node, parseStatement);
}
_parseFunctionBodyDeclaration() {
return this._parseVariableDeclaration() || this._parseReturnStatement() || this._parseWarnAndDebug()
|| this._parseControlStatement(this._parseFunctionBodyDeclaration.bind(this));
}
_parseFunctionDeclaration() {
if (!this.peekKeyword('@function')) {
return null;
}
const node = this.create(nodes.FunctionDeclaration);
this.consumeToken(); // @function
if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Function]))) {
return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]);
}
if (!this.accept(TokenType.ParenthesisL)) {
return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.CurlyR]);
}
if (node.getParameters().addChild(this._parseParameterDeclaration())) {
while (this.accept(TokenType.Comma)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseParameterDeclaration())) {
return this.finish(node, ParseError.VariableNameExpected);
}
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]);
}
return this._parseBody(node, this._parseFunctionBodyDeclaration.bind(this));
}
_parseReturnStatement() {
if (!this.peekKeyword('@return')) {
return null;
}
const node = this.createNode(nodes.NodeType.ReturnStatement);
this.consumeToken(); // @function
if (!node.addChild(this._parseExpr())) {
return this.finish(node, ParseError.ExpressionExpected);
}
return this.finish(node);
}
_parseMixinDeclaration() {
if (!this.peekKeyword('@mixin')) {
return null;
}
const node = this.create(nodes.MixinDeclaration);
this.consumeToken();
if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Mixin]))) {
return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]);
}
if (this.accept(TokenType.ParenthesisL)) {
if (node.getParameters().addChild(this._parseParameterDeclaration())) {
while (this.accept(TokenType.Comma)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseParameterDeclaration())) {
return this.finish(node, ParseError.VariableNameExpected);
}
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyR]);
}
}
return this._parseBody(node, this._parseRuleSetDeclaration.bind(this));
}
_parseParameterDeclaration() {
const node = this.create(nodes.FunctionParameter);
if (!node.setIdentifier(this._parseVariable())) {
return null;
}
if (this.accept(scssScanner.Ellipsis)) {
// ok
}
if (this.accept(TokenType.Colon)) {
if (!node.setDefaultValue(this._parseExpr(true))) {
return this.finish(node, ParseError.VariableValueExpected, [], [TokenType.Comma, TokenType.ParenthesisR]);
}
}
return this.finish(node);
}
_parseMixinContent() {
if (!this.peekKeyword('@content')) {
return null;
}
const node = this.create(nodes.MixinContentReference);
this.consumeToken();
if (this.accept(TokenType.ParenthesisL)) {
if (node.getArguments().addChild(this._parseFunctionArgument())) {
while (this.accept(TokenType.Comma)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getArguments().addChild(this._parseFunctionArgument())) {
return this.finish(node, ParseError.ExpressionExpected);
}
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
}
return this.finish(node);
}
_parseMixinReference() {
if (!this.peekKeyword('@include')) {
return null;
}
const node = this.create(nodes.MixinReference);
this.consumeToken();
// Could be module or mixin identifier, set as mixin as default.
const firstIdent = this._parseIdent([nodes.ReferenceType.Mixin]);
if (!node.setIdentifier(firstIdent)) {
return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]);
}
// Is a module accessor.
if (!this.hasWhitespace() && this.acceptDelim('.') && !this.hasWhitespace()) {
const secondIdent = this._parseIdent([nodes.ReferenceType.Mixin]);
if (!secondIdent) {
return this.finish(node, ParseError.IdentifierExpected, [TokenType.CurlyR]);
}
const moduleToken = this.create(nodes.Module);
// Re-purpose first matched ident as identifier for module token.
firstIdent.referenceTypes = [nodes.ReferenceType.Module];
moduleToken.setIdentifier(firstIdent);
// Override identifier with second ident.
node.setIdentifier(secondIdent);
node.addChild(moduleToken);
}
if (this.accept(TokenType.ParenthesisL)) {
if (node.getArguments().addChild(this._parseFunctionArgument())) {
while (this.accept(TokenType.Comma)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getArguments().addChild(this._parseFunctionArgument())) {
return this.finish(node, ParseError.ExpressionExpected);
}
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
}
if (this.peekIdent('using') || this.peek(TokenType.CurlyL)) {
node.setContent(this._parseMixinContentDeclaration());
}
return this.finish(node);
}
_parseMixinContentDeclaration() {
const node = this.create(nodes.MixinContentDeclaration);
if (this.acceptIdent('using')) {
if (!this.accept(TokenType.ParenthesisL)) {
return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.CurlyL]);
}
if (node.getParameters().addChild(this._parseParameterDeclaration())) {
while (this.accept(TokenType.Comma)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseParameterDeclaration())) {
return this.finish(node, ParseError.VariableNameExpected);
}
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected, [TokenType.CurlyL]);
}
}
if (this.peek(TokenType.CurlyL)) {
this._parseBody(node, this._parseMixinReferenceBodyStatement.bind(this));
}
return this.finish(node);
}
_parseMixinReferenceBodyStatement() {
return this._tryParseKeyframeSelector() || this._parseRuleSetDeclaration();
}
_parseFunctionArgument() {
// [variableName ':'] expression | variableName '...'
const node = this.create(nodes.FunctionArgument);
const pos = this.mark();
const argument = this._parseVariable();
if (argument) {
if (!this.accept(TokenType.Colon)) {
if (this.accept(scssScanner.Ellipsis)) { // optional
node.setValue(argument);
return this.finish(node);
}
else {
this.restoreAtMark(pos);
}
}
else {
node.setIdentifier(argument);
}
}
if (node.setValue(this._parseExpr(true))) {
this.accept(scssScanner.Ellipsis); // #43746
node.addChild(this._parsePrio()); // #9859
return this.finish(node);
}
else if (node.setValue(this._tryParsePrio())) {
return this.finish(node);
}
return null;
}
_parseURLArgument() {
const pos = this.mark();
const node = super._parseURLArgument();
if (!node || !this.peek(TokenType.ParenthesisR)) {
this.restoreAtMark(pos);
const node = this.create(nodes.Node);
node.addChild(this._parseBinaryExpr());
return this.finish(node);
}
return node;
}
_parseOperation() {
if (!this.peek(TokenType.ParenthesisL)) {
return null;
}
const node = this.create(nodes.Node);
this.consumeToken();
while (node.addChild(this._parseListElement())) {
this.accept(TokenType.Comma); // optional
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
return this.finish(node);
}
_parseListElement() {
const node = this.create(nodes.ListEntry);
const child = this._parseBinaryExpr();
if (!child) {
return null;
}
if (this.accept(TokenType.Colon)) {
node.setKey(child);
if (!node.setValue(this._parseBinaryExpr())) {
return this.finish(node, ParseError.ExpressionExpected);
}
}
else {
node.setValue(child);
}
return this.finish(node);
}
_parseUse() {
if (!this.peekKeyword('@use')) {
return null;
}
const node = this.create(nodes.Use);
this.consumeToken(); // @use
if (!node.addChild(this._parseStringLiteral())) {
return this.finish(node, ParseError.StringLiteralExpected);
}
if (!this.peek(TokenType.SemiColon) && !this.peek(TokenType.EOF)) {
if (!this.peekRegExp(TokenType.Ident, /as|with/)) {
return this.finish(node, ParseError.UnknownKeyword);
}
if (this.acceptIdent('as') &&
(!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Module])) && !this.acceptDelim('*'))) {
return this.finish(node, ParseError.IdentifierOrWildcardExpected);
}
if (this.acceptIdent('with')) {
if (!this.accept(TokenType.ParenthesisL)) {
return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.ParenthesisR]);
}
// First variable statement, no comma.
if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) {
return this.finish(node, ParseError.VariableNameExpected);
}
while (this.accept(TokenType.Comma)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) {
return this.finish(node, ParseError.VariableNameExpected);
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
}
}
if (!this.accept(TokenType.SemiColon) && !this.accept(TokenType.EOF)) {
return this.finish(node, ParseError.SemiColonExpected);
}
return this.finish(node);
}
_parseModuleConfigDeclaration() {
const node = this.create(nodes.ModuleConfiguration);
if (!node.setIdentifier(this._parseVariable())) {
return null;
}
if (!this.accept(TokenType.Colon) || !node.setValue(this._parseExpr(true))) {
return this.finish(node, ParseError.VariableValueExpected, [], [TokenType.Comma, TokenType.ParenthesisR]);
}
if (this.accept(TokenType.Exclamation)) {
if (this.hasWhitespace() || !this.acceptIdent('default')) {
return this.finish(node, ParseError.UnknownKeyword);
}
}
return this.finish(node);
}
_parseForward() {
if (!this.peekKeyword('@forward')) {
return null;
}
const node = this.create(nodes.Forward);
this.consumeToken();
if (!node.addChild(this._parseStringLiteral())) {
return this.finish(node, ParseError.StringLiteralExpected);
}
if (this.acceptIdent('as')) {
const identifier = this._parseIdent([nodes.ReferenceType.Forward]);
if (!node.setIdentifier(identifier)) {
return this.finish(node, ParseError.IdentifierExpected);
}
// Wildcard must be the next character after the identifier string.
if (this.hasWhitespace() || !this.acceptDelim('*')) {
return this.finish(node, ParseError.WildcardExpected);
}
}
if (this.acceptIdent('with')) {
if (!this.accept(TokenType.ParenthesisL)) {
return this.finish(node, ParseError.LeftParenthesisExpected, [TokenType.ParenthesisR]);
}
// First variable statement, no comma.
if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) {
return this.finish(node, ParseError.VariableNameExpected);
}
while (this.accept(TokenType.Comma)) {
if (this.peek(TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) {
return this.finish(node, ParseError.VariableNameExpected);
}
}
if (!this.accept(TokenType.ParenthesisR)) {
return this.finish(node, ParseError.RightParenthesisExpected);
}
}
else if (this.peekIdent('hide') || this.peekIdent('show')) {
if (!node.addChild(this._parseForwardVisibility())) {
return this.finish(node, ParseError.IdentifierOrVariableExpected);
}
}
if (!this.accept(TokenType.SemiColon) && !this.accept(TokenType.EOF)) {
return this.finish(node, ParseError.SemiColonExpected);
}
return this.finish(node);
}
_parseForwardVisibility() {
const node = this.create(nodes.ForwardVisibility);
// Assume to be "hide" or "show".
node.setIdentifier(this._parseIdent());
while (node.addChild(this._parseVariable() || this._parseIdent())) {
// Consume all variables and idents ahead.
this.accept(TokenType.Comma);
}
// More than just identifier
return node.getChildren().length > 1 ? node : null;
}
_parseSupportsCondition() {
return this._parseInterpolation() || super._parseSupportsCondition();
}
}

View file

@ -0,0 +1,95 @@
/*---------------------------------------------------------------------------------------------
* 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 { TokenType, Scanner } from './cssScanner';
const _FSL = '/'.charCodeAt(0);
const _NWL = '\n'.charCodeAt(0);
const _CAR = '\r'.charCodeAt(0);
const _LFD = '\f'.charCodeAt(0);
const _DLR = '$'.charCodeAt(0);
const _HSH = '#'.charCodeAt(0);
const _CUL = '{'.charCodeAt(0);
const _EQS = '='.charCodeAt(0);
const _BNG = '!'.charCodeAt(0);
const _LAN = '<'.charCodeAt(0);
const _RAN = '>'.charCodeAt(0);
const _DOT = '.'.charCodeAt(0);
const _ATS = '@'.charCodeAt(0);
let customTokenValue = TokenType.CustomToken;
export const VariableName = customTokenValue++;
export const InterpolationFunction = customTokenValue++;
export const Default = customTokenValue++;
export const EqualsOperator = customTokenValue++;
export const NotEqualsOperator = customTokenValue++;
export const GreaterEqualsOperator = customTokenValue++;
export const SmallerEqualsOperator = customTokenValue++;
export const Ellipsis = customTokenValue++;
export const Module = customTokenValue++;
export class SCSSScanner extends Scanner {
scanNext(offset) {
// scss variable
if (this.stream.advanceIfChar(_DLR)) {
const content = ['$'];
if (this.ident(content)) {
return this.finishToken(offset, VariableName, content.join(''));
}
else {
this.stream.goBackTo(offset);
}
}
// scss: interpolation function #{..})
if (this.stream.advanceIfChars([_HSH, _CUL])) {
return this.finishToken(offset, InterpolationFunction);
}
// operator ==
if (this.stream.advanceIfChars([_EQS, _EQS])) {
return this.finishToken(offset, EqualsOperator);
}
// operator !=
if (this.stream.advanceIfChars([_BNG, _EQS])) {
return this.finishToken(offset, NotEqualsOperator);
}
// operators <, <=
if (this.stream.advanceIfChar(_LAN)) {
if (this.stream.advanceIfChar(_EQS)) {
return this.finishToken(offset, SmallerEqualsOperator);
}
return this.finishToken(offset, TokenType.Delim);
}
// ooperators >, >=
if (this.stream.advanceIfChar(_RAN)) {
if (this.stream.advanceIfChar(_EQS)) {
return this.finishToken(offset, GreaterEqualsOperator);
}
return this.finishToken(offset, TokenType.Delim);
}
// ellipis
if (this.stream.advanceIfChars([_DOT, _DOT, _DOT])) {
return this.finishToken(offset, Ellipsis);
}
return super.scanNext(offset);
}
comment() {
if (super.comment()) {
return true;
}
if (!this.inURL && this.stream.advanceIfChars([_FSL, _FSL])) {
this.stream.advanceWhileChar((ch) => {
switch (ch) {
case _NWL:
case _CAR:
case _LFD:
return false;
default:
return true;
}
});
return true;
}
else {
return false;
}
}
}

View file

@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nodes from '../parser/cssNodes';
import { difference } from '../utils/strings';
import { Rules } from '../services/lintRules';
import { Command, TextEdit, CodeAction, CodeActionKind, TextDocumentEdit, VersionedTextDocumentIdentifier } from '../cssLanguageTypes';
import * as l10n from '@vscode/l10n';
export class CSSCodeActions {
constructor(cssDataManager) {
this.cssDataManager = cssDataManager;
}
doCodeActions(document, range, context, stylesheet) {
return this.doCodeActions2(document, range, context, stylesheet).map(ca => {
const textDocumentEdit = ca.edit && ca.edit.documentChanges && ca.edit.documentChanges[0];
return Command.create(ca.title, '_css.applyCodeAction', document.uri, document.version, textDocumentEdit && textDocumentEdit.edits);
});
}
doCodeActions2(document, range, context, stylesheet) {
const result = [];
if (context.diagnostics) {
for (const diagnostic of context.diagnostics) {
this.appendFixesForMarker(document, stylesheet, diagnostic, result);
}
}
return result;
}
getFixesForUnknownProperty(document, property, marker, result) {
const propertyName = property.getName();
const candidates = [];
this.cssDataManager.getProperties().forEach(p => {
const score = difference(propertyName, p.name);
if (score >= propertyName.length / 2 /*score_lim*/) {
candidates.push({ property: p.name, score });
}
});
// Sort in descending order.
candidates.sort((a, b) => {
return b.score - a.score || a.property.localeCompare(b.property);
});
let maxActions = 3;
for (const candidate of candidates) {
const propertyName = candidate.property;
const title = l10n.t("Rename to '{0}'", propertyName);
const edit = TextEdit.replace(marker.range, propertyName);
const documentIdentifier = VersionedTextDocumentIdentifier.create(document.uri, document.version);
const workspaceEdit = { documentChanges: [TextDocumentEdit.create(documentIdentifier, [edit])] };
const codeAction = CodeAction.create(title, workspaceEdit, CodeActionKind.QuickFix);
codeAction.diagnostics = [marker];
result.push(codeAction);
if (--maxActions <= 0) {
return;
}
}
}
appendFixesForMarker(document, stylesheet, marker, result) {
if (marker.code !== Rules.UnknownProperty.id) {
return;
}
const offset = document.offsetAt(marker.range.start);
const end = document.offsetAt(marker.range.end);
const nodepath = nodes.getNodePath(stylesheet, offset);
for (let i = nodepath.length - 1; i >= 0; i--) {
const node = nodepath[i];
if (node instanceof nodes.Declaration) {
const property = node.getProperty();
if (property && property.offset === offset && property.end === end) {
this.getFixesForUnknownProperty(document, property, marker, result);
return;
}
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,190 @@
/*---------------------------------------------------------------------------------------------
* 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 { TokenType, Scanner } from '../parser/cssScanner';
import { SCSSScanner, InterpolationFunction } from '../parser/scssScanner';
import { LESSScanner } from '../parser/lessScanner';
export function getFoldingRanges(document, context) {
const ranges = computeFoldingRanges(document);
return limitFoldingRanges(ranges, context);
}
function computeFoldingRanges(document) {
function getStartLine(t) {
return document.positionAt(t.offset).line;
}
function getEndLine(t) {
return document.positionAt(t.offset + t.len).line;
}
function getScanner() {
switch (document.languageId) {
case 'scss':
return new SCSSScanner();
case 'less':
return new LESSScanner();
default:
return new Scanner();
}
}
function tokenToRange(t, kind) {
const startLine = getStartLine(t);
const endLine = getEndLine(t);
if (startLine !== endLine) {
return {
startLine,
endLine,
kind
};
}
else {
return null;
}
}
const ranges = [];
const delimiterStack = [];
const scanner = getScanner();
scanner.ignoreComment = false;
scanner.setSource(document.getText());
let token = scanner.scan();
let prevToken = null;
while (token.type !== TokenType.EOF) {
switch (token.type) {
case TokenType.CurlyL:
case InterpolationFunction:
{
delimiterStack.push({ line: getStartLine(token), type: 'brace', isStart: true });
break;
}
case TokenType.CurlyR: {
if (delimiterStack.length !== 0) {
const prevDelimiter = popPrevStartDelimiterOfType(delimiterStack, 'brace');
if (!prevDelimiter) {
break;
}
let endLine = getEndLine(token);
if (prevDelimiter.type === 'brace') {
/**
* Other than the case when curly brace is not on a new line by itself, for example
* .foo {
* color: red; }
* Use endLine minus one to show ending curly brace
*/
if (prevToken && getEndLine(prevToken) !== endLine) {
endLine--;
}
if (prevDelimiter.line !== endLine) {
ranges.push({
startLine: prevDelimiter.line,
endLine,
kind: undefined
});
}
}
}
break;
}
/**
* In CSS, there is no single line comment prefixed with //
* All comments are marked as `Comment`
*/
case TokenType.Comment: {
const commentRegionMarkerToDelimiter = (marker) => {
if (marker === '#region') {
return { line: getStartLine(token), type: 'comment', isStart: true };
}
else {
return { line: getEndLine(token), type: 'comment', isStart: false };
}
};
const getCurrDelimiter = (token) => {
const matches = token.text.match(/^\s*\/\*\s*(#region|#endregion)\b\s*(.*?)\s*\*\//);
if (matches) {
return commentRegionMarkerToDelimiter(matches[1]);
}
else if (document.languageId === 'scss' || document.languageId === 'less') {
const matches = token.text.match(/^\s*\/\/\s*(#region|#endregion)\b\s*(.*?)\s*/);
if (matches) {
return commentRegionMarkerToDelimiter(matches[1]);
}
}
return null;
};
const currDelimiter = getCurrDelimiter(token);
// /* */ comment region folding
// All #region and #endregion cases
if (currDelimiter) {
if (currDelimiter.isStart) {
delimiterStack.push(currDelimiter);
}
else {
const prevDelimiter = popPrevStartDelimiterOfType(delimiterStack, 'comment');
if (!prevDelimiter) {
break;
}
if (prevDelimiter.type === 'comment') {
if (prevDelimiter.line !== currDelimiter.line) {
ranges.push({
startLine: prevDelimiter.line,
endLine: currDelimiter.line,
kind: 'region'
});
}
}
}
}
// Multiline comment case
else {
const range = tokenToRange(token, 'comment');
if (range) {
ranges.push(range);
}
}
break;
}
}
prevToken = token;
token = scanner.scan();
}
return ranges;
}
function popPrevStartDelimiterOfType(stack, type) {
if (stack.length === 0) {
return null;
}
for (let i = stack.length - 1; i >= 0; i--) {
if (stack[i].type === type && stack[i].isStart) {
return stack.splice(i, 1)[0];
}
}
return null;
}
/**
* - Sort regions
* - Remove invalid regions (intersections)
* - If limit exceeds, only return `rangeLimit` amount of ranges
*/
function limitFoldingRanges(ranges, context) {
const maxRanges = context && context.rangeLimit || Number.MAX_VALUE;
const sortedRanges = ranges.sort((r1, r2) => {
let diff = r1.startLine - r2.startLine;
if (diff === 0) {
diff = r1.endLine - r2.endLine;
}
return diff;
});
const validRanges = [];
let prevEndLine = -1;
sortedRanges.forEach(r => {
if (!(r.startLine < prevEndLine && prevEndLine < r.endLine)) {
validRanges.push(r);
prevEndLine = r.endLine;
}
});
if (validRanges.length < maxRanges) {
return validRanges;
}
else {
return validRanges.slice(0, maxRanges);
}
}

View file

@ -0,0 +1,136 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Range, Position } from '../cssLanguageTypes';
import { css_beautify } from '../beautify/beautify-css';
import { repeat } from '../utils/strings';
export function format(document, range, options) {
let value = document.getText();
let includesEnd = true;
let initialIndentLevel = 0;
let inRule = false;
const tabSize = options.tabSize || 4;
if (range) {
let startOffset = document.offsetAt(range.start);
// include all leading whitespace iff at the beginning of the line
let extendedStart = startOffset;
while (extendedStart > 0 && isWhitespace(value, extendedStart - 1)) {
extendedStart--;
}
if (extendedStart === 0 || isEOL(value, extendedStart - 1)) {
startOffset = extendedStart;
}
else {
// else keep at least one whitespace
if (extendedStart < startOffset) {
startOffset = extendedStart + 1;
}
}
// include all following whitespace until the end of the line
let endOffset = document.offsetAt(range.end);
let extendedEnd = endOffset;
while (extendedEnd < value.length && isWhitespace(value, extendedEnd)) {
extendedEnd++;
}
if (extendedEnd === value.length || isEOL(value, extendedEnd)) {
endOffset = extendedEnd;
}
range = Range.create(document.positionAt(startOffset), document.positionAt(endOffset));
// Test if inside a rule
inRule = isInRule(value, startOffset);
includesEnd = endOffset === value.length;
value = value.substring(startOffset, endOffset);
if (startOffset !== 0) {
const startOfLineOffset = document.offsetAt(Position.create(range.start.line, 0));
initialIndentLevel = computeIndentLevel(document.getText(), startOfLineOffset, options);
}
if (inRule) {
value = `{\n${trimLeft(value)}`;
}
}
else {
range = Range.create(Position.create(0, 0), document.positionAt(value.length));
}
const cssOptions = {
indent_size: tabSize,
indent_char: options.insertSpaces ? ' ' : '\t',
end_with_newline: includesEnd && getFormatOption(options, 'insertFinalNewline', false),
selector_separator_newline: getFormatOption(options, 'newlineBetweenSelectors', true),
newline_between_rules: getFormatOption(options, 'newlineBetweenRules', true),
space_around_selector_separator: getFormatOption(options, 'spaceAroundSelectorSeparator', false),
brace_style: getFormatOption(options, 'braceStyle', 'collapse'),
indent_empty_lines: getFormatOption(options, 'indentEmptyLines', false),
max_preserve_newlines: getFormatOption(options, 'maxPreserveNewLines', undefined),
preserve_newlines: getFormatOption(options, 'preserveNewLines', true),
wrap_line_length: getFormatOption(options, 'wrapLineLength', undefined),
eol: '\n'
};
let result = css_beautify(value, cssOptions);
if (inRule) {
result = trimLeft(result.substring(2));
}
if (initialIndentLevel > 0) {
const indent = options.insertSpaces ? repeat(' ', tabSize * initialIndentLevel) : repeat('\t', initialIndentLevel);
result = result.split('\n').join('\n' + indent);
if (range.start.character === 0) {
result = indent + result; // keep the indent
}
}
return [{
range: range,
newText: result
}];
}
function trimLeft(str) {
return str.replace(/^\s+/, '');
}
const _CUL = '{'.charCodeAt(0);
const _CUR = '}'.charCodeAt(0);
function isInRule(str, offset) {
while (offset >= 0) {
const ch = str.charCodeAt(offset);
if (ch === _CUL) {
return true;
}
else if (ch === _CUR) {
return false;
}
offset--;
}
return false;
}
function getFormatOption(options, key, dflt) {
if (options && options.hasOwnProperty(key)) {
const value = options[key];
if (value !== null) {
return value;
}
}
return dflt;
}
function computeIndentLevel(content, offset, options) {
let i = offset;
let nChars = 0;
const tabSize = options.tabSize || 4;
while (i < content.length) {
const ch = content.charAt(i);
if (ch === ' ') {
nChars++;
}
else if (ch === '\t') {
nChars += tabSize;
}
else {
break;
}
i++;
}
return Math.floor(nChars / tabSize);
}
function isEOL(text, offset) {
return '\r\n'.indexOf(text.charAt(offset)) !== -1;
}
function isWhitespace(text, offset) {
return ' \t'.indexOf(text.charAt(offset)) !== -1;
}

View file

@ -0,0 +1,148 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nodes from '../parser/cssNodes';
import * as languageFacts from '../languageFacts/facts';
import { SelectorPrinting } from './selectorPrinting';
import { startsWith } from '../utils/strings';
import { Range, MarkupKind } from '../cssLanguageTypes';
import { isDefined } from '../utils/objects';
export class CSSHover {
constructor(clientCapabilities, cssDataManager) {
this.clientCapabilities = clientCapabilities;
this.cssDataManager = cssDataManager;
this.selectorPrinting = new SelectorPrinting(cssDataManager);
}
configure(settings) {
this.defaultSettings = settings;
}
doHover(document, position, stylesheet, settings = this.defaultSettings) {
function getRange(node) {
return Range.create(document.positionAt(node.offset), document.positionAt(node.end));
}
const offset = document.offsetAt(position);
const nodepath = nodes.getNodePath(stylesheet, offset);
/**
* nodepath is top-down
* Build up the hover by appending inner node's information
*/
let hover = null;
for (let i = 0; i < nodepath.length; i++) {
const node = nodepath[i];
if (node instanceof nodes.Selector) {
hover = {
contents: this.selectorPrinting.selectorToMarkedString(node),
range: getRange(node)
};
break;
}
if (node instanceof nodes.SimpleSelector) {
/**
* Some sass specific at rules such as `@at-root` are parsed as `SimpleSelector`
*/
if (!startsWith(node.getText(), '@')) {
hover = {
contents: this.selectorPrinting.simpleSelectorToMarkedString(node),
range: getRange(node)
};
}
break;
}
if (node instanceof nodes.Declaration) {
const propertyName = node.getFullPropertyName();
const entry = this.cssDataManager.getProperty(propertyName);
if (entry) {
const contents = languageFacts.getEntryDescription(entry, this.doesSupportMarkdown(), settings);
if (contents) {
hover = {
contents,
range: getRange(node)
};
}
else {
hover = null;
}
}
continue;
}
if (node instanceof nodes.UnknownAtRule) {
const atRuleName = node.getText();
const entry = this.cssDataManager.getAtDirective(atRuleName);
if (entry) {
const contents = languageFacts.getEntryDescription(entry, this.doesSupportMarkdown(), settings);
if (contents) {
hover = {
contents,
range: getRange(node)
};
}
else {
hover = null;
}
}
continue;
}
if (node instanceof nodes.Node && node.type === nodes.NodeType.PseudoSelector) {
const selectorName = node.getText();
const entry = selectorName.slice(0, 2) === '::'
? this.cssDataManager.getPseudoElement(selectorName)
: this.cssDataManager.getPseudoClass(selectorName);
if (entry) {
const contents = languageFacts.getEntryDescription(entry, this.doesSupportMarkdown(), settings);
if (contents) {
hover = {
contents,
range: getRange(node)
};
}
else {
hover = null;
}
}
continue;
}
}
if (hover) {
hover.contents = this.convertContents(hover.contents);
}
return hover;
}
convertContents(contents) {
if (!this.doesSupportMarkdown()) {
if (typeof contents === 'string') {
return contents;
}
// MarkupContent
else if ('kind' in contents) {
return {
kind: 'plaintext',
value: contents.value
};
}
// MarkedString[]
else if (Array.isArray(contents)) {
return contents.map(c => {
return typeof c === 'string' ? c : c.value;
});
}
// MarkedString
else {
return contents.value;
}
}
return contents;
}
doesSupportMarkdown() {
if (!isDefined(this.supportsMarkdown)) {
if (!isDefined(this.clientCapabilities)) {
this.supportsMarkdown = true;
return this.supportsMarkdown;
}
const hover = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.hover;
this.supportsMarkdown = hover && hover.contentFormat && Array.isArray(hover.contentFormat) && hover.contentFormat.indexOf(MarkupKind.Markdown) !== -1;
}
return this.supportsMarkdown;
}
}

View file

@ -0,0 +1,463 @@
/*---------------------------------------------------------------------------------------------
* 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 { DocumentHighlightKind, Location, Range, SymbolKind, TextEdit, FileType } from '../cssLanguageTypes';
import * as l10n from '@vscode/l10n';
import * as nodes from '../parser/cssNodes';
import { Symbols } from '../parser/cssSymbolScope';
import { getColorValue, hslFromColor, hwbFromColor } from '../languageFacts/facts';
import { startsWith } from '../utils/strings';
import { dirname, joinPath } from '../utils/resources';
const startsWithSchemeRegex = /^\w+:\/\//;
const startsWithData = /^data:/;
export class CSSNavigation {
constructor(fileSystemProvider, resolveModuleReferences) {
this.fileSystemProvider = fileSystemProvider;
this.resolveModuleReferences = resolveModuleReferences;
}
findDefinition(document, position, stylesheet) {
const symbols = new Symbols(stylesheet);
const offset = document.offsetAt(position);
const node = nodes.getNodeAtOffset(stylesheet, offset);
if (!node) {
return null;
}
const symbol = symbols.findSymbolFromNode(node);
if (!symbol) {
return null;
}
return {
uri: document.uri,
range: getRange(symbol.node, document)
};
}
findReferences(document, position, stylesheet) {
const highlights = this.findDocumentHighlights(document, position, stylesheet);
return highlights.map(h => {
return {
uri: document.uri,
range: h.range
};
});
}
getHighlightNode(document, position, stylesheet) {
const offset = document.offsetAt(position);
let node = nodes.getNodeAtOffset(stylesheet, offset);
if (!node || node.type === nodes.NodeType.Stylesheet || node.type === nodes.NodeType.Declarations) {
return;
}
if (node.type === nodes.NodeType.Identifier && node.parent && node.parent.type === nodes.NodeType.ClassSelector) {
node = node.parent;
}
return node;
}
findDocumentHighlights(document, position, stylesheet) {
const result = [];
const node = this.getHighlightNode(document, position, stylesheet);
if (!node) {
return result;
}
const symbols = new Symbols(stylesheet);
const symbol = symbols.findSymbolFromNode(node);
const name = node.getText();
stylesheet.accept(candidate => {
if (symbol) {
if (symbols.matchesSymbol(candidate, symbol)) {
result.push({
kind: getHighlightKind(candidate),
range: getRange(candidate, document)
});
return false;
}
}
else if (node && node.type === candidate.type && candidate.matches(name)) {
// Same node type and data
result.push({
kind: getHighlightKind(candidate),
range: getRange(candidate, document)
});
}
return true;
});
return result;
}
isRawStringDocumentLinkNode(node) {
return node.type === nodes.NodeType.Import;
}
findDocumentLinks(document, stylesheet, documentContext) {
const linkData = this.findUnresolvedLinks(document, stylesheet);
const resolvedLinks = [];
for (let data of linkData) {
const link = data.link;
const target = link.target;
if (!target || startsWithData.test(target)) {
// no links for data:
}
else if (startsWithSchemeRegex.test(target)) {
resolvedLinks.push(link);
}
else {
const resolved = documentContext.resolveReference(target, document.uri);
if (resolved) {
link.target = resolved;
}
resolvedLinks.push(link);
}
}
return resolvedLinks;
}
async findDocumentLinks2(document, stylesheet, documentContext) {
const linkData = this.findUnresolvedLinks(document, stylesheet);
const resolvedLinks = [];
for (let data of linkData) {
const link = data.link;
const target = link.target;
if (!target || startsWithData.test(target)) {
// no links for data:
}
else if (startsWithSchemeRegex.test(target)) {
resolvedLinks.push(link);
}
else {
const resolvedTarget = await this.resolveReference(target, document.uri, documentContext, data.isRawLink);
if (resolvedTarget !== undefined) {
link.target = resolvedTarget;
resolvedLinks.push(link);
}
}
}
return resolvedLinks;
}
findUnresolvedLinks(document, stylesheet) {
const result = [];
const collect = (uriStringNode) => {
let rawUri = uriStringNode.getText();
const range = getRange(uriStringNode, document);
// Make sure the range is not empty
if (range.start.line === range.end.line && range.start.character === range.end.character) {
return;
}
if (startsWith(rawUri, `'`) || startsWith(rawUri, `"`)) {
rawUri = rawUri.slice(1, -1);
}
const isRawLink = uriStringNode.parent ? this.isRawStringDocumentLinkNode(uriStringNode.parent) : false;
result.push({ link: { target: rawUri, range }, isRawLink });
};
stylesheet.accept(candidate => {
if (candidate.type === nodes.NodeType.URILiteral) {
const first = candidate.getChild(0);
if (first) {
collect(first);
}
return false;
}
/**
* In @import, it is possible to include links that do not use `url()`
* For example, `@import 'foo.css';`
*/
if (candidate.parent && this.isRawStringDocumentLinkNode(candidate.parent)) {
const rawText = candidate.getText();
if (startsWith(rawText, `'`) || startsWith(rawText, `"`)) {
collect(candidate);
}
return false;
}
return true;
});
return result;
}
findSymbolInformations(document, stylesheet) {
const result = [];
const addSymbolInformation = (name, kind, symbolNodeOrRange) => {
const range = symbolNodeOrRange instanceof nodes.Node ? getRange(symbolNodeOrRange, document) : symbolNodeOrRange;
const entry = {
name: name || l10n.t('<undefined>'),
kind,
location: Location.create(document.uri, range)
};
result.push(entry);
};
this.collectDocumentSymbols(document, stylesheet, addSymbolInformation);
return result;
}
findDocumentSymbols(document, stylesheet) {
const result = [];
const parents = [];
const addDocumentSymbol = (name, kind, symbolNodeOrRange, nameNodeOrRange, bodyNode) => {
const range = symbolNodeOrRange instanceof nodes.Node ? getRange(symbolNodeOrRange, document) : symbolNodeOrRange;
let selectionRange = nameNodeOrRange instanceof nodes.Node ? getRange(nameNodeOrRange, document) : nameNodeOrRange;
if (!selectionRange || !containsRange(range, selectionRange)) {
selectionRange = Range.create(range.start, range.start);
}
const entry = {
name: name || l10n.t('<undefined>'),
kind,
range,
selectionRange
};
let top = parents.pop();
while (top && !containsRange(top[1], range)) {
top = parents.pop();
}
if (top) {
const topSymbol = top[0];
if (!topSymbol.children) {
topSymbol.children = [];
}
topSymbol.children.push(entry);
parents.push(top); // put back top
}
else {
result.push(entry);
}
if (bodyNode) {
parents.push([entry, getRange(bodyNode, document)]);
}
};
this.collectDocumentSymbols(document, stylesheet, addDocumentSymbol);
return result;
}
collectDocumentSymbols(document, stylesheet, collect) {
stylesheet.accept(node => {
if (node instanceof nodes.RuleSet) {
for (const selector of node.getSelectors().getChildren()) {
if (selector instanceof nodes.Selector) {
const range = Range.create(document.positionAt(selector.offset), document.positionAt(node.end));
collect(selector.getText(), SymbolKind.Class, range, selector, node.getDeclarations());
}
}
}
else if (node instanceof nodes.VariableDeclaration) {
collect(node.getName(), SymbolKind.Variable, node, node.getVariable(), undefined);
}
else if (node instanceof nodes.MixinDeclaration) {
collect(node.getName(), SymbolKind.Method, node, node.getIdentifier(), node.getDeclarations());
}
else if (node instanceof nodes.FunctionDeclaration) {
collect(node.getName(), SymbolKind.Function, node, node.getIdentifier(), node.getDeclarations());
}
else if (node instanceof nodes.Keyframe) {
const name = l10n.t("@keyframes {0}", node.getName());
collect(name, SymbolKind.Class, node, node.getIdentifier(), node.getDeclarations());
}
else if (node instanceof nodes.FontFace) {
const name = l10n.t("@font-face");
collect(name, SymbolKind.Class, node, undefined, node.getDeclarations());
}
else if (node instanceof nodes.Media) {
const mediaList = node.getChild(0);
if (mediaList instanceof nodes.Medialist) {
const name = '@media ' + mediaList.getText();
collect(name, SymbolKind.Module, node, mediaList, node.getDeclarations());
}
}
return true;
});
}
findDocumentColors(document, stylesheet) {
const result = [];
stylesheet.accept((node) => {
const colorInfo = getColorInformation(node, document);
if (colorInfo) {
result.push(colorInfo);
}
return true;
});
return result;
}
getColorPresentations(document, stylesheet, color, range) {
const result = [];
const red256 = Math.round(color.red * 255), green256 = Math.round(color.green * 255), blue256 = Math.round(color.blue * 255);
let label;
if (color.alpha === 1) {
label = `rgb(${red256}, ${green256}, ${blue256})`;
}
else {
label = `rgba(${red256}, ${green256}, ${blue256}, ${color.alpha})`;
}
result.push({ label: label, textEdit: TextEdit.replace(range, label) });
if (color.alpha === 1) {
label = `#${toTwoDigitHex(red256)}${toTwoDigitHex(green256)}${toTwoDigitHex(blue256)}`;
}
else {
label = `#${toTwoDigitHex(red256)}${toTwoDigitHex(green256)}${toTwoDigitHex(blue256)}${toTwoDigitHex(Math.round(color.alpha * 255))}`;
}
result.push({ label: label, textEdit: TextEdit.replace(range, label) });
const hsl = hslFromColor(color);
if (hsl.a === 1) {
label = `hsl(${hsl.h}, ${Math.round(hsl.s * 100)}%, ${Math.round(hsl.l * 100)}%)`;
}
else {
label = `hsla(${hsl.h}, ${Math.round(hsl.s * 100)}%, ${Math.round(hsl.l * 100)}%, ${hsl.a})`;
}
result.push({ label: label, textEdit: TextEdit.replace(range, label) });
const hwb = hwbFromColor(color);
if (hwb.a === 1) {
label = `hwb(${hwb.h} ${Math.round(hwb.w * 100)}% ${Math.round(hwb.b * 100)}%)`;
}
else {
label = `hwb(${hwb.h} ${Math.round(hwb.w * 100)}% ${Math.round(hwb.b * 100)}% / ${hwb.a})`;
}
result.push({ label: label, textEdit: TextEdit.replace(range, label) });
return result;
}
prepareRename(document, position, stylesheet) {
const node = this.getHighlightNode(document, position, stylesheet);
if (node) {
return Range.create(document.positionAt(node.offset), document.positionAt(node.end));
}
}
doRename(document, position, newName, stylesheet) {
const highlights = this.findDocumentHighlights(document, position, stylesheet);
const edits = highlights.map(h => TextEdit.replace(h.range, newName));
return {
changes: { [document.uri]: edits }
};
}
async resolveModuleReference(ref, documentUri, documentContext) {
if (startsWith(documentUri, 'file://')) {
const moduleName = getModuleNameFromPath(ref);
if (moduleName && moduleName !== '.' && moduleName !== '..') {
const rootFolderUri = documentContext.resolveReference('/', documentUri);
const documentFolderUri = dirname(documentUri);
const modulePath = await this.resolvePathToModule(moduleName, documentFolderUri, rootFolderUri);
if (modulePath) {
const pathWithinModule = ref.substring(moduleName.length + 1);
return joinPath(modulePath, pathWithinModule);
}
}
}
return undefined;
}
async mapReference(target, isRawLink) {
return target;
}
async resolveReference(target, documentUri, documentContext, isRawLink = false) {
// Following [css-loader](https://github.com/webpack-contrib/css-loader#url)
// and [sass-loader's](https://github.com/webpack-contrib/sass-loader#imports)
// convention, if an import path starts with ~ then use node module resolution
// *unless* it starts with "~/" as this refers to the user's home directory.
if (target[0] === '~' && target[1] !== '/' && this.fileSystemProvider) {
target = target.substring(1);
return this.mapReference(await this.resolveModuleReference(target, documentUri, documentContext), isRawLink);
}
const ref = await this.mapReference(documentContext.resolveReference(target, documentUri), isRawLink);
// Following [less-loader](https://github.com/webpack-contrib/less-loader#imports)
// and [sass-loader's](https://github.com/webpack-contrib/sass-loader#resolving-import-at-rules)
// new resolving import at-rules (~ is deprecated). The loader will first try to resolve @import as a relative path. If it cannot be resolved,
// then the loader will try to resolve @import inside node_modules.
if (this.resolveModuleReferences) {
if (ref && await this.fileExists(ref)) {
return ref;
}
const moduleReference = await this.mapReference(await this.resolveModuleReference(target, documentUri, documentContext), isRawLink);
if (moduleReference) {
return moduleReference;
}
}
// fall back. it might not exists
return ref;
}
async resolvePathToModule(_moduleName, documentFolderUri, rootFolderUri) {
// resolve the module relative to the document. We can't use `require` here as the code is webpacked.
const packPath = joinPath(documentFolderUri, 'node_modules', _moduleName, 'package.json');
if (await this.fileExists(packPath)) {
return dirname(packPath);
}
else if (rootFolderUri && documentFolderUri.startsWith(rootFolderUri) && (documentFolderUri.length !== rootFolderUri.length)) {
return this.resolvePathToModule(_moduleName, dirname(documentFolderUri), rootFolderUri);
}
return undefined;
}
async fileExists(uri) {
if (!this.fileSystemProvider) {
return false;
}
try {
const stat = await this.fileSystemProvider.stat(uri);
if (stat.type === FileType.Unknown && stat.size === -1) {
return false;
}
return true;
}
catch (err) {
return false;
}
}
}
function getColorInformation(node, document) {
const color = getColorValue(node);
if (color) {
const range = getRange(node, document);
return { color, range };
}
return null;
}
function getRange(node, document) {
return Range.create(document.positionAt(node.offset), document.positionAt(node.end));
}
/**
* Test if `otherRange` is in `range`. If the ranges are equal, will return true.
*/
function containsRange(range, otherRange) {
const otherStartLine = otherRange.start.line, otherEndLine = otherRange.end.line;
const rangeStartLine = range.start.line, rangeEndLine = range.end.line;
if (otherStartLine < rangeStartLine || otherEndLine < rangeStartLine) {
return false;
}
if (otherStartLine > rangeEndLine || otherEndLine > rangeEndLine) {
return false;
}
if (otherStartLine === rangeStartLine && otherRange.start.character < range.start.character) {
return false;
}
if (otherEndLine === rangeEndLine && otherRange.end.character > range.end.character) {
return false;
}
return true;
}
function getHighlightKind(node) {
if (node.type === nodes.NodeType.Selector) {
return DocumentHighlightKind.Write;
}
if (node instanceof nodes.Identifier) {
if (node.parent && node.parent instanceof nodes.Property) {
if (node.isCustomProperty) {
return DocumentHighlightKind.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 DocumentHighlightKind.Write;
}
}
return DocumentHighlightKind.Read;
}
function toTwoDigitHex(n) {
const r = n.toString(16);
return r.length !== 2 ? '0' + r : r;
}
function getModuleNameFromPath(path) {
const firstSlash = path.indexOf('/');
if (firstSlash === -1) {
return '';
}
// If a scoped module (starts with @) then get up until second instance of '/', or to the end of the string for root-level imports.
if (path[0] === '@') {
const secondSlash = path.indexOf('/', firstSlash + 1);
if (secondSlash === -1) {
return path;
}
return path.substring(0, secondSlash);
}
// Otherwise get until first instance of '/'
return path.substring(0, firstSlash);
}

View file

@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* 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 { Range, SelectionRange } from '../cssLanguageTypes';
import { NodeType } from '../parser/cssNodes';
export function getSelectionRanges(document, positions, stylesheet) {
function getSelectionRange(position) {
const applicableRanges = getApplicableRanges(position);
let current = undefined;
for (let index = applicableRanges.length - 1; index >= 0; index--) {
current = SelectionRange.create(Range.create(document.positionAt(applicableRanges[index][0]), document.positionAt(applicableRanges[index][1])), current);
}
if (!current) {
current = SelectionRange.create(Range.create(position, position));
}
return current;
}
return positions.map(getSelectionRange);
function getApplicableRanges(position) {
const offset = document.offsetAt(position);
let currNode = stylesheet.findChildAtOffset(offset, true);
if (!currNode) {
return [];
}
const result = [];
while (currNode) {
if (currNode.parent &&
currNode.offset === currNode.parent.offset &&
currNode.end === currNode.parent.end) {
currNode = currNode.parent;
continue;
}
// The `{ }` part of `.a { }`
if (currNode.type === NodeType.Declarations) {
if (offset > currNode.offset && offset < currNode.end) {
// Return `{ }` and the range inside `{` and `}`
result.push([currNode.offset + 1, currNode.end - 1]);
}
}
result.push([currNode.offset, currNode.end]);
currNode = currNode.parent;
}
return result;
}
}

View file

@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nodes from '../parser/cssNodes';
import { LintConfigurationSettings, Rules } from './lintRules';
import { LintVisitor } from './lint';
import { Range, DiagnosticSeverity } from '../cssLanguageTypes';
export class CSSValidation {
constructor(cssDataManager) {
this.cssDataManager = cssDataManager;
}
configure(settings) {
this.settings = settings;
}
doValidation(document, stylesheet, settings = this.settings) {
if (settings && settings.validate === false) {
return [];
}
const entries = [];
entries.push.apply(entries, nodes.ParseErrorCollector.entries(stylesheet));
entries.push.apply(entries, LintVisitor.entries(stylesheet, document, new LintConfigurationSettings(settings && settings.lint), this.cssDataManager));
const ruleIds = [];
for (const r in Rules) {
ruleIds.push(Rules[r].id);
}
function toDiagnostic(marker) {
const range = Range.create(document.positionAt(marker.getOffset()), document.positionAt(marker.getOffset() + marker.getLength()));
const source = document.languageId;
return {
code: marker.getRule().id,
source: source,
message: marker.getMessage(),
severity: marker.getLevel() === nodes.Level.Warning ? DiagnosticSeverity.Warning : DiagnosticSeverity.Error,
range: range
};
}
return entries.filter(entry => entry.getLevel() !== nodes.Level.Ignore).map(toDiagnostic);
}
}

View file

@ -0,0 +1,378 @@
/*---------------------------------------------------------------------------------------------
* 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 { CSSCompletion } from './cssCompletion';
import { CompletionItemKind, InsertTextFormat, TextEdit } from '../cssLanguageTypes';
import * as l10n from '@vscode/l10n';
class LESSCompletion extends CSSCompletion {
constructor(lsOptions, cssDataManager) {
super('@', lsOptions, cssDataManager);
}
createFunctionProposals(proposals, existingNode, sortToEnd, result) {
for (const p of proposals) {
const item = {
label: p.name,
detail: p.example,
documentation: p.description,
textEdit: TextEdit.replace(this.getCompletionRange(existingNode), p.name + '($0)'),
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Function
};
if (sortToEnd) {
item.sortText = 'z';
}
result.items.push(item);
}
return result;
}
getTermProposals(entry, existingNode, result) {
let functions = LESSCompletion.builtInProposals;
if (entry) {
functions = functions.filter(f => !f.type || !entry.restrictions || entry.restrictions.indexOf(f.type) !== -1);
}
this.createFunctionProposals(functions, existingNode, true, result);
return super.getTermProposals(entry, existingNode, result);
}
getColorProposals(entry, existingNode, result) {
this.createFunctionProposals(LESSCompletion.colorProposals, existingNode, false, result);
return super.getColorProposals(entry, existingNode, result);
}
getCompletionsForDeclarationProperty(declaration, result) {
this.getCompletionsForSelector(null, true, result);
return super.getCompletionsForDeclarationProperty(declaration, result);
}
}
LESSCompletion.builtInProposals = [
// Boolean functions
{
'name': 'if',
'example': 'if(condition, trueValue [, falseValue]);',
'description': l10n.t('returns one of two values depending on a condition.')
},
{
'name': 'boolean',
'example': 'boolean(condition);',
'description': l10n.t('"store" a boolean test for later evaluation in a guard or if().')
},
// List functions
{
'name': 'length',
'example': 'length(@list);',
'description': l10n.t('returns the number of elements in a value list')
},
{
'name': 'extract',
'example': 'extract(@list, index);',
'description': l10n.t('returns a value at the specified position in the list')
},
{
'name': 'range',
'example': 'range([start, ] end [, step]);',
'description': l10n.t('generate a list spanning a range of values')
},
{
'name': 'each',
'example': 'each(@list, ruleset);',
'description': l10n.t('bind the evaluation of a ruleset to each member of a list.')
},
// Other built-ins
{
'name': 'escape',
'example': 'escape(@string);',
'description': l10n.t('URL encodes a string')
},
{
'name': 'e',
'example': 'e(@string);',
'description': l10n.t('escape string content')
},
{
'name': 'replace',
'example': 'replace(@string, @pattern, @replacement[, @flags]);',
'description': l10n.t('string replace')
},
{
'name': 'unit',
'example': 'unit(@dimension, [@unit: \'\']);',
'description': l10n.t('remove or change the unit of a dimension')
},
{
'name': 'color',
'example': 'color(@string);',
'description': l10n.t('parses a string to a color'),
'type': 'color'
},
{
'name': 'convert',
'example': 'convert(@value, unit);',
'description': l10n.t('converts numbers from one type into another')
},
{
'name': 'data-uri',
'example': 'data-uri([mimetype,] url);',
'description': l10n.t('inlines a resource and falls back to `url()`'),
'type': 'url'
},
{
'name': 'abs',
'description': l10n.t('absolute value of a number'),
'example': 'abs(number);'
},
{
'name': 'acos',
'description': l10n.t('arccosine - inverse of cosine function'),
'example': 'acos(number);'
},
{
'name': 'asin',
'description': l10n.t('arcsine - inverse of sine function'),
'example': 'asin(number);'
},
{
'name': 'ceil',
'example': 'ceil(@number);',
'description': l10n.t('rounds up to an integer')
},
{
'name': 'cos',
'description': l10n.t('cosine function'),
'example': 'cos(number);'
},
{
'name': 'floor',
'description': l10n.t('rounds down to an integer'),
'example': 'floor(@number);'
},
{
'name': 'percentage',
'description': l10n.t('converts to a %, e.g. 0.5 > 50%'),
'example': 'percentage(@number);',
'type': 'percentage'
},
{
'name': 'round',
'description': l10n.t('rounds a number to a number of places'),
'example': 'round(number, [places: 0]);'
},
{
'name': 'sqrt',
'description': l10n.t('calculates square root of a number'),
'example': 'sqrt(number);'
},
{
'name': 'sin',
'description': l10n.t('sine function'),
'example': 'sin(number);'
},
{
'name': 'tan',
'description': l10n.t('tangent function'),
'example': 'tan(number);'
},
{
'name': 'atan',
'description': l10n.t('arctangent - inverse of tangent function'),
'example': 'atan(number);'
},
{
'name': 'pi',
'description': l10n.t('returns pi'),
'example': 'pi();'
},
{
'name': 'pow',
'description': l10n.t('first argument raised to the power of the second argument'),
'example': 'pow(@base, @exponent);'
},
{
'name': 'mod',
'description': l10n.t('first argument modulus second argument'),
'example': 'mod(number, number);'
},
{
'name': 'min',
'description': l10n.t('returns the lowest of one or more values'),
'example': 'min(@x, @y);'
},
{
'name': 'max',
'description': l10n.t('returns the lowest of one or more values'),
'example': 'max(@x, @y);'
}
];
LESSCompletion.colorProposals = [
{
'name': 'argb',
'example': 'argb(@color);',
'description': l10n.t('creates a #AARRGGBB')
},
{
'name': 'hsl',
'example': 'hsl(@hue, @saturation, @lightness);',
'description': l10n.t('creates a color')
},
{
'name': 'hsla',
'example': 'hsla(@hue, @saturation, @lightness, @alpha);',
'description': l10n.t('creates a color')
},
{
'name': 'hsv',
'example': 'hsv(@hue, @saturation, @value);',
'description': l10n.t('creates a color')
},
{
'name': 'hsva',
'example': 'hsva(@hue, @saturation, @value, @alpha);',
'description': l10n.t('creates a color')
},
{
'name': 'hue',
'example': 'hue(@color);',
'description': l10n.t('returns the `hue` channel of `@color` in the HSL space')
},
{
'name': 'saturation',
'example': 'saturation(@color);',
'description': l10n.t('returns the `saturation` channel of `@color` in the HSL space')
},
{
'name': 'lightness',
'example': 'lightness(@color);',
'description': l10n.t('returns the `lightness` channel of `@color` in the HSL space')
},
{
'name': 'hsvhue',
'example': 'hsvhue(@color);',
'description': l10n.t('returns the `hue` channel of `@color` in the HSV space')
},
{
'name': 'hsvsaturation',
'example': 'hsvsaturation(@color);',
'description': l10n.t('returns the `saturation` channel of `@color` in the HSV space')
},
{
'name': 'hsvvalue',
'example': 'hsvvalue(@color);',
'description': l10n.t('returns the `value` channel of `@color` in the HSV space')
},
{
'name': 'red',
'example': 'red(@color);',
'description': l10n.t('returns the `red` channel of `@color`')
},
{
'name': 'green',
'example': 'green(@color);',
'description': l10n.t('returns the `green` channel of `@color`')
},
{
'name': 'blue',
'example': 'blue(@color);',
'description': l10n.t('returns the `blue` channel of `@color`')
},
{
'name': 'alpha',
'example': 'alpha(@color);',
'description': l10n.t('returns the `alpha` channel of `@color`')
},
{
'name': 'luma',
'example': 'luma(@color);',
'description': l10n.t('returns the `luma` value (perceptual brightness) of `@color`')
},
{
'name': 'saturate',
'example': 'saturate(@color, 10%);',
'description': l10n.t('return `@color` 10% points more saturated')
},
{
'name': 'desaturate',
'example': 'desaturate(@color, 10%);',
'description': l10n.t('return `@color` 10% points less saturated')
},
{
'name': 'lighten',
'example': 'lighten(@color, 10%);',
'description': l10n.t('return `@color` 10% points lighter')
},
{
'name': 'darken',
'example': 'darken(@color, 10%);',
'description': l10n.t('return `@color` 10% points darker')
},
{
'name': 'fadein',
'example': 'fadein(@color, 10%);',
'description': l10n.t('return `@color` 10% points less transparent')
},
{
'name': 'fadeout',
'example': 'fadeout(@color, 10%);',
'description': l10n.t('return `@color` 10% points more transparent')
},
{
'name': 'fade',
'example': 'fade(@color, 50%);',
'description': l10n.t('return `@color` with 50% transparency')
},
{
'name': 'spin',
'example': 'spin(@color, 10);',
'description': l10n.t('return `@color` with a 10 degree larger in hue')
},
{
'name': 'mix',
'example': 'mix(@color1, @color2, [@weight: 50%]);',
'description': l10n.t('return a mix of `@color1` and `@color2`')
},
{
'name': 'greyscale',
'example': 'greyscale(@color);',
'description': l10n.t('returns a grey, 100% desaturated color'),
},
{
'name': 'contrast',
'example': 'contrast(@color1, [@darkcolor: black], [@lightcolor: white], [@threshold: 43%]);',
'description': l10n.t('return `@darkcolor` if `@color1 is> 43% luma` otherwise return `@lightcolor`, see notes')
},
{
'name': 'multiply',
'example': 'multiply(@color1, @color2);'
},
{
'name': 'screen',
'example': 'screen(@color1, @color2);'
},
{
'name': 'overlay',
'example': 'overlay(@color1, @color2);'
},
{
'name': 'softlight',
'example': 'softlight(@color1, @color2);'
},
{
'name': 'hardlight',
'example': 'hardlight(@color1, @color2);'
},
{
'name': 'difference',
'example': 'difference(@color1, @color2);'
},
{
'name': 'exclusion',
'example': 'exclusion(@color1, @color2);'
},
{
'name': 'average',
'example': 'average(@color1, @color2);'
},
{
'name': 'negation',
'example': 'negation(@color1, @color2);'
}
];
export { LESSCompletion };

View file

@ -0,0 +1,565 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as l10n from '@vscode/l10n';
import * as languageFacts from '../languageFacts/facts';
import * as nodes from '../parser/cssNodes';
import { union } from '../utils/arrays';
import { Rules, Settings } from './lintRules';
import calculateBoxModel, { Element } from './lintUtil';
class NodesByRootMap {
constructor() {
this.data = {};
}
add(root, name, node) {
let entry = this.data[root];
if (!entry) {
entry = { nodes: [], names: [] };
this.data[root] = entry;
}
entry.names.push(name);
if (node) {
entry.nodes.push(node);
}
}
}
class LintVisitor {
static entries(node, document, settings, cssDataManager, entryFilter) {
const visitor = new LintVisitor(document, settings, cssDataManager);
node.acceptVisitor(visitor);
visitor.completeValidations();
return visitor.getEntries(entryFilter);
}
constructor(document, settings, cssDataManager) {
this.cssDataManager = cssDataManager;
this.warnings = [];
this.settings = settings;
this.documentText = document.getText();
this.keyframes = new NodesByRootMap();
this.validProperties = {};
const properties = settings.getSetting(Settings.ValidProperties);
if (Array.isArray(properties)) {
properties.forEach((p) => {
if (typeof p === 'string') {
const name = p.trim().toLowerCase();
if (name.length) {
this.validProperties[name] = true;
}
}
});
}
}
isValidPropertyDeclaration(element) {
const propertyName = element.fullPropertyName;
return this.validProperties[propertyName];
}
fetch(input, s) {
const elements = [];
for (const curr of input) {
if (curr.fullPropertyName === s) {
elements.push(curr);
}
}
return elements;
}
fetchWithValue(input, s, v) {
const elements = [];
for (const inputElement of input) {
if (inputElement.fullPropertyName === s) {
const expression = inputElement.node.getValue();
if (expression && this.findValueInExpression(expression, v)) {
elements.push(inputElement);
}
}
}
return elements;
}
findValueInExpression(expression, v) {
let found = false;
expression.accept(node => {
if (node.type === nodes.NodeType.Identifier && node.matches(v)) {
found = true;
}
return !found;
});
return found;
}
getEntries(filter = (nodes.Level.Warning | nodes.Level.Error)) {
return this.warnings.filter(entry => {
return (entry.getLevel() & filter) !== 0;
});
}
addEntry(node, rule, details) {
const entry = new nodes.Marker(node, rule, this.settings.getRule(rule), details);
this.warnings.push(entry);
}
getMissingNames(expected, actual) {
const expectedClone = expected.slice(0); // clone
for (let i = 0; i < actual.length; i++) {
const k = expectedClone.indexOf(actual[i]);
if (k !== -1) {
expectedClone[k] = null;
}
}
let result = null;
for (let i = 0; i < expectedClone.length; i++) {
const curr = expectedClone[i];
if (curr) {
if (result === null) {
result = l10n.t("'{0}'", curr);
}
else {
result = l10n.t("{0}, '{1}'", result, curr);
}
}
}
return result;
}
visitNode(node) {
switch (node.type) {
case nodes.NodeType.UnknownAtRule:
return this.visitUnknownAtRule(node);
case nodes.NodeType.Keyframe:
return this.visitKeyframe(node);
case nodes.NodeType.FontFace:
return this.visitFontFace(node);
case nodes.NodeType.Ruleset:
return this.visitRuleSet(node);
case nodes.NodeType.SimpleSelector:
return this.visitSimpleSelector(node);
case nodes.NodeType.Function:
return this.visitFunction(node);
case nodes.NodeType.NumericValue:
return this.visitNumericValue(node);
case nodes.NodeType.Import:
return this.visitImport(node);
case nodes.NodeType.HexColorValue:
return this.visitHexColorValue(node);
case nodes.NodeType.Prio:
return this.visitPrio(node);
case nodes.NodeType.IdentifierSelector:
return this.visitIdentifierSelector(node);
}
return true;
}
completeValidations() {
this.validateKeyframes();
}
visitUnknownAtRule(node) {
const atRuleName = node.getChild(0);
if (!atRuleName) {
return false;
}
const atDirective = this.cssDataManager.getAtDirective(atRuleName.getText());
if (atDirective) {
return false;
}
this.addEntry(atRuleName, Rules.UnknownAtRules, `Unknown at rule ${atRuleName.getText()}`);
return true;
}
visitKeyframe(node) {
const keyword = node.getKeyword();
if (!keyword) {
return false;
}
const text = keyword.getText();
this.keyframes.add(node.getName(), text, (text !== '@keyframes') ? keyword : null);
return true;
}
validateKeyframes() {
// @keyframe and it's vendor specific alternatives
// @keyframe should be included
const expected = ['@-webkit-keyframes', '@-moz-keyframes', '@-o-keyframes'];
for (const name in this.keyframes.data) {
const actual = this.keyframes.data[name].names;
const needsStandard = (actual.indexOf('@keyframes') === -1);
if (!needsStandard && actual.length === 1) {
continue; // only the non-vendor specific keyword is used, that's fine, no warning
}
const missingVendorSpecific = this.getMissingNames(expected, actual);
if (missingVendorSpecific || needsStandard) {
for (const node of this.keyframes.data[name].nodes) {
if (needsStandard) {
const message = l10n.t("Always define standard rule '@keyframes' when defining keyframes.");
this.addEntry(node, Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
}
if (missingVendorSpecific) {
const message = l10n.t("Always include all vendor specific rules: Missing: {0}", missingVendorSpecific);
this.addEntry(node, Rules.AllVendorPrefixes, message);
}
}
}
}
return true;
}
visitSimpleSelector(node) {
/////////////////////////////////////////////////////////////
// Lint - The universal selector (*) is known to be slow.
/////////////////////////////////////////////////////////////
const firstChar = this.documentText.charAt(node.offset);
if (node.length === 1 && firstChar === '*') {
this.addEntry(node, Rules.UniversalSelector);
}
return true;
}
visitIdentifierSelector(node) {
/////////////////////////////////////////////////////////////
// Lint - Avoid id selectors
/////////////////////////////////////////////////////////////
this.addEntry(node, Rules.AvoidIdSelector);
return true;
}
visitImport(node) {
/////////////////////////////////////////////////////////////
// Lint - Import statements shouldn't be used, because they aren't offering parallel downloads.
/////////////////////////////////////////////////////////////
this.addEntry(node, Rules.ImportStatemement);
return true;
}
visitRuleSet(node) {
/////////////////////////////////////////////////////////////
// Lint - Don't use empty rulesets.
/////////////////////////////////////////////////////////////
const declarations = node.getDeclarations();
if (!declarations) {
// syntax error
return false;
}
if (!declarations.hasChildren()) {
this.addEntry(node.getSelectors(), Rules.EmptyRuleSet);
}
const propertyTable = [];
for (const element of declarations.getChildren()) {
if (element instanceof nodes.Declaration) {
propertyTable.push(new Element(element));
}
}
/////////////////////////////////////////////////////////////
// the rule warns when it finds:
// width being used with border, border-left, border-right, padding, padding-left, or padding-right
// height being used with border, border-top, border-bottom, padding, padding-top, or padding-bottom
// No error when box-sizing property is specified, as it assumes the user knows what he's doing.
// see https://github.com/CSSLint/csslint/wiki/Beware-of-box-model-size
/////////////////////////////////////////////////////////////
const boxModel = calculateBoxModel(propertyTable);
if (boxModel.width) {
let properties = [];
if (boxModel.right.value) {
properties = union(properties, boxModel.right.properties);
}
if (boxModel.left.value) {
properties = union(properties, boxModel.left.properties);
}
if (properties.length !== 0) {
for (const item of properties) {
this.addEntry(item.node, Rules.BewareOfBoxModelSize);
}
this.addEntry(boxModel.width.node, Rules.BewareOfBoxModelSize);
}
}
if (boxModel.height) {
let properties = [];
if (boxModel.top.value) {
properties = union(properties, boxModel.top.properties);
}
if (boxModel.bottom.value) {
properties = union(properties, boxModel.bottom.properties);
}
if (properties.length !== 0) {
for (const item of properties) {
this.addEntry(item.node, Rules.BewareOfBoxModelSize);
}
this.addEntry(boxModel.height.node, Rules.BewareOfBoxModelSize);
}
}
/////////////////////////////////////////////////////////////
// Properties ignored due to display
/////////////////////////////////////////////////////////////
// With 'display: inline-block', 'float' has no effect
let displayElems = this.fetchWithValue(propertyTable, 'display', 'inline-block');
if (displayElems.length > 0) {
const elem = this.fetch(propertyTable, 'float');
for (let index = 0; index < elem.length; index++) {
const node = elem[index].node;
const value = node.getValue();
if (value && !value.matches('none')) {
this.addEntry(node, Rules.PropertyIgnoredDueToDisplay, l10n.t("inline-block is ignored due to the float. If 'float' has a value other than 'none', the box is floated and 'display' is treated as 'block'"));
}
}
}
// With 'display: block', 'vertical-align' has no effect
displayElems = this.fetchWithValue(propertyTable, 'display', 'block');
if (displayElems.length > 0) {
const elem = this.fetch(propertyTable, 'vertical-align');
for (let index = 0; index < elem.length; index++) {
this.addEntry(elem[index].node, Rules.PropertyIgnoredDueToDisplay, l10n.t("Property is ignored due to the display. With 'display: block', vertical-align should not be used."));
}
}
/////////////////////////////////////////////////////////////
// Avoid 'float'
/////////////////////////////////////////////////////////////
const elements = this.fetch(propertyTable, 'float');
for (let index = 0; index < elements.length; index++) {
const element = elements[index];
if (!this.isValidPropertyDeclaration(element)) {
this.addEntry(element.node, Rules.AvoidFloat);
}
}
/////////////////////////////////////////////////////////////
// Don't use duplicate declarations.
/////////////////////////////////////////////////////////////
for (let i = 0; i < propertyTable.length; i++) {
const element = propertyTable[i];
if (element.fullPropertyName !== 'background' && !this.validProperties[element.fullPropertyName]) {
const value = element.node.getValue();
if (value && this.documentText.charAt(value.offset) !== '-') {
const elements = this.fetch(propertyTable, element.fullPropertyName);
if (elements.length > 1) {
for (let k = 0; k < elements.length; k++) {
const value = elements[k].node.getValue();
if (value && this.documentText.charAt(value.offset) !== '-' && elements[k] !== element) {
this.addEntry(element.node, Rules.DuplicateDeclarations);
}
}
}
}
}
}
/////////////////////////////////////////////////////////////
// Unknown propery & When using a vendor-prefixed gradient, make sure to use them all.
/////////////////////////////////////////////////////////////
const isExportBlock = node.getSelectors().matches(":export");
if (!isExportBlock) {
const propertiesBySuffix = new NodesByRootMap();
let containsUnknowns = false;
for (const element of propertyTable) {
const decl = element.node;
if (this.isCSSDeclaration(decl)) {
let name = element.fullPropertyName;
const firstChar = name.charAt(0);
if (firstChar === '-') {
if (name.charAt(1) !== '-') { // avoid css variables
if (!this.cssDataManager.isKnownProperty(name) && !this.validProperties[name]) {
this.addEntry(decl.getProperty(), Rules.UnknownVendorSpecificProperty);
}
const nonPrefixedName = decl.getNonPrefixedPropertyName();
propertiesBySuffix.add(nonPrefixedName, name, decl.getProperty());
}
}
else {
const fullName = name;
if (firstChar === '*' || firstChar === '_') {
this.addEntry(decl.getProperty(), Rules.IEStarHack);
name = name.substr(1);
}
// _property and *property might be contributed via custom data
if (!this.cssDataManager.isKnownProperty(fullName) && !this.cssDataManager.isKnownProperty(name)) {
if (!this.validProperties[name]) {
this.addEntry(decl.getProperty(), Rules.UnknownProperty, l10n.t("Unknown property: '{0}'", decl.getFullPropertyName()));
}
}
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
for (const suffix in propertiesBySuffix.data) {
const entry = propertiesBySuffix.data[suffix];
const actual = entry.names;
const needsStandard = this.cssDataManager.isStandardProperty(suffix) && (actual.indexOf(suffix) === -1);
if (!needsStandard && actual.length === 1) {
continue; // only the non-vendor specific rule is used, that's fine, no warning
}
/**
* We should ignore missing standard properties, if there's an explicit contextual reference to a
* vendor specific pseudo-element selector with the same vendor (prefix)
*
* (See https://github.com/microsoft/vscode/issues/164350)
*/
const entriesThatNeedStandard = new Set(needsStandard ? entry.nodes : []);
if (needsStandard) {
const pseudoElements = this.getContextualVendorSpecificPseudoElements(node);
for (const node of entry.nodes) {
const propertyName = node.getName();
const prefix = propertyName.substring(0, propertyName.length - suffix.length);
if (pseudoElements.some(x => x.startsWith(prefix))) {
entriesThatNeedStandard.delete(node);
}
}
}
const expected = [];
for (let i = 0, len = LintVisitor.prefixes.length; i < len; i++) {
const prefix = LintVisitor.prefixes[i];
if (this.cssDataManager.isStandardProperty(prefix + suffix)) {
expected.push(prefix + suffix);
}
}
const missingVendorSpecific = this.getMissingNames(expected, actual);
if (missingVendorSpecific || needsStandard) {
for (const node of entry.nodes) {
if (needsStandard && entriesThatNeedStandard.has(node)) {
const message = l10n.t("Also define the standard property '{0}' for compatibility", suffix);
this.addEntry(node, Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
}
if (missingVendorSpecific) {
const message = l10n.t("Always include all vendor specific properties: Missing: {0}", missingVendorSpecific);
this.addEntry(node, Rules.AllVendorPrefixes, message);
}
}
}
}
}
}
return true;
}
/**
* Walks up the syntax tree (starting from given `node`) and captures vendor
* specific pseudo-element selectors.
* @returns An array of vendor specific pseudo-elements; or empty if none
* was found.
*/
getContextualVendorSpecificPseudoElements(node) {
function walkDown(s, n) {
for (const child of n.getChildren()) {
if (child.type === nodes.NodeType.PseudoSelector) {
const pseudoElement = child.getChildren()[0]?.getText();
if (pseudoElement) {
s.add(pseudoElement);
}
}
walkDown(s, child);
}
}
function walkUp(s, n) {
if (n.type === nodes.NodeType.Ruleset) {
for (const selector of n.getSelectors().getChildren()) {
walkDown(s, selector);
}
}
return n.parent ? walkUp(s, n.parent) : undefined;
}
const result = new Set();
walkUp(result, node);
return Array.from(result);
}
visitPrio(node) {
/////////////////////////////////////////////////////////////
// Don't use !important
/////////////////////////////////////////////////////////////
this.addEntry(node, Rules.AvoidImportant);
return true;
}
visitNumericValue(node) {
/////////////////////////////////////////////////////////////
// 0 has no following unit
/////////////////////////////////////////////////////////////
const funcDecl = node.findParent(nodes.NodeType.Function);
if (funcDecl && funcDecl.getName() === 'calc') {
return true;
}
const decl = node.findParent(nodes.NodeType.Declaration);
if (decl) {
const declValue = decl.getValue();
if (declValue) {
const value = node.getValue();
if (!value.unit || languageFacts.units.length.indexOf(value.unit.toLowerCase()) === -1) {
return true;
}
if (parseFloat(value.value) === 0.0 && !!value.unit && !this.validProperties[decl.getFullPropertyName()]) {
this.addEntry(node, Rules.ZeroWithUnit);
}
}
}
return true;
}
visitFontFace(node) {
const declarations = node.getDeclarations();
if (!declarations) {
// syntax error
return false;
}
let definesSrc = false, definesFontFamily = false;
let containsUnknowns = false;
for (const node of declarations.getChildren()) {
if (this.isCSSDeclaration(node)) {
const name = node.getProperty().getName().toLowerCase();
if (name === 'src') {
definesSrc = true;
}
if (name === 'font-family') {
definesFontFamily = true;
}
}
else {
containsUnknowns = true;
}
}
if (!containsUnknowns && (!definesSrc || !definesFontFamily)) {
this.addEntry(node, Rules.RequiredPropertiesForFontFace);
}
return true;
}
isCSSDeclaration(node) {
if (node instanceof nodes.Declaration) {
if (!node.getValue()) {
return false;
}
const property = node.getProperty();
if (!property) {
return false;
}
const identifier = property.getIdentifier();
if (!identifier || identifier.containsInterpolation()) {
return false;
}
return true;
}
return false;
}
visitHexColorValue(node) {
// Rule: #eeff0011 or #eeff00 or #ef01 or #ef0
const length = node.length;
if (length !== 9 && length !== 7 && length !== 5 && length !== 4) {
this.addEntry(node, Rules.HexColorLength);
}
return false;
}
visitFunction(node) {
const fnName = node.getName().toLowerCase();
let expectedAttrCount = -1;
let 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, Rules.ArgsInColorFunction);
}
}
return true;
}
}
LintVisitor.prefixes = [
'-ms-', '-moz-', '-o-', '-webkit-', // Quite common
// '-xv-', '-atsc-', '-wap-', '-khtml-', 'mso-', 'prince-', '-ah-', '-hp-', '-ro-', '-rim-', '-tc-' // Quite un-common
];
export { LintVisitor };

View file

@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nodes from '../parser/cssNodes';
import * as l10n from '@vscode/l10n';
const Warning = nodes.Level.Warning;
const Error = nodes.Level.Error;
const Ignore = nodes.Level.Ignore;
export class Rule {
constructor(id, message, defaultValue) {
this.id = id;
this.message = message;
this.defaultValue = defaultValue;
// nothing to do
}
}
export class Setting {
constructor(id, message, defaultValue) {
this.id = id;
this.message = message;
this.defaultValue = defaultValue;
// nothing to do
}
}
export const Rules = {
AllVendorPrefixes: new Rule('compatibleVendorPrefixes', l10n.t("When using a vendor-specific prefix make sure to also include all other vendor-specific properties"), Ignore),
IncludeStandardPropertyWhenUsingVendorPrefix: new Rule('vendorPrefix', l10n.t("When using a vendor-specific prefix also include the standard property"), Warning),
DuplicateDeclarations: new Rule('duplicateProperties', l10n.t("Do not use duplicate style definitions"), Ignore),
EmptyRuleSet: new Rule('emptyRules', l10n.t("Do not use empty rulesets"), Warning),
ImportStatemement: new Rule('importStatement', l10n.t("Import statements do not load in parallel"), Ignore),
BewareOfBoxModelSize: new Rule('boxModel', l10n.t("Do not use width or height when using padding or border"), Ignore),
UniversalSelector: new Rule('universalSelector', l10n.t("The universal selector (*) is known to be slow"), Ignore),
ZeroWithUnit: new Rule('zeroUnits', l10n.t("No unit for zero needed"), Ignore),
RequiredPropertiesForFontFace: new Rule('fontFaceProperties', l10n.t("@font-face rule must define 'src' and 'font-family' properties"), Warning),
HexColorLength: new Rule('hexColorLength', l10n.t("Hex colors must consist of three, four, six or eight hex numbers"), Error),
ArgsInColorFunction: new Rule('argumentsInColorFunction', l10n.t("Invalid number of parameters"), Error),
UnknownProperty: new Rule('unknownProperties', l10n.t("Unknown property."), Warning),
UnknownAtRules: new Rule('unknownAtRules', l10n.t("Unknown at-rule."), Warning),
IEStarHack: new Rule('ieHack', l10n.t("IE hacks are only necessary when supporting IE7 and older"), Ignore),
UnknownVendorSpecificProperty: new Rule('unknownVendorSpecificProperties', l10n.t("Unknown vendor specific property."), Ignore),
PropertyIgnoredDueToDisplay: new Rule('propertyIgnoredDueToDisplay', l10n.t("Property is ignored due to the display."), Warning),
AvoidImportant: new Rule('important', l10n.t("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', l10n.t("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', l10n.t("Selectors should not contain IDs because these rules are too tightly coupled with the HTML."), Ignore),
};
export const Settings = {
ValidProperties: new Setting('validProperties', l10n.t("A list of properties that are not validated against the `unknownProperties` rule."), [])
};
export class LintConfigurationSettings {
constructor(conf = {}) {
this.conf = conf;
}
getRule(rule) {
if (this.conf.hasOwnProperty(rule.id)) {
const level = toLevel(this.conf[rule.id]);
if (level) {
return level;
}
}
return rule.defaultValue;
}
getSetting(setting) {
return this.conf[setting.id];
}
}
function toLevel(level) {
switch (level) {
case 'ignore': return nodes.Level.Ignore;
case 'warning': return nodes.Level.Warning;
case 'error': return nodes.Level.Error;
}
return null;
}

View file

@ -0,0 +1,196 @@
/*---------------------------------------------------------------------------------------------
* 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 { includes } from '../utils/arrays';
export class Element {
constructor(decl) {
this.fullPropertyName = decl.getFullPropertyName().toLowerCase();
this.node = decl;
}
}
function setSide(model, side, value, property) {
const state = model[side];
state.value = value;
if (value) {
if (!includes(state.properties, property)) {
state.properties.push(property);
}
}
}
function setAllSides(model, value, property) {
setSide(model, 'top', value, property);
setSide(model, 'right', value, property);
setSide(model, 'bottom', value, property);
setSide(model, 'left', value, property);
}
function updateModelWithValue(model, side, value, property) {
if (side === 'top' || side === 'right' ||
side === 'bottom' || side === 'left') {
setSide(model, side, value, property);
}
else {
setAllSides(model, value, property);
}
}
function updateModelWithList(model, values, property) {
switch (values.length) {
case 1:
updateModelWithValue(model, undefined, values[0], property);
break;
case 2:
updateModelWithValue(model, 'top', values[0], property);
updateModelWithValue(model, 'bottom', values[0], property);
updateModelWithValue(model, 'right', values[1], property);
updateModelWithValue(model, 'left', values[1], property);
break;
case 3:
updateModelWithValue(model, 'top', values[0], property);
updateModelWithValue(model, 'right', values[1], property);
updateModelWithValue(model, 'left', values[1], property);
updateModelWithValue(model, 'bottom', values[2], property);
break;
case 4:
updateModelWithValue(model, 'top', values[0], property);
updateModelWithValue(model, 'right', values[1], property);
updateModelWithValue(model, 'bottom', values[2], property);
updateModelWithValue(model, 'left', values[3], property);
break;
}
}
function matches(value, candidates) {
for (let candidate of candidates) {
if (value.matches(candidate)) {
return true;
}
}
return false;
}
/**
* @param allowsKeywords whether the initial value of property is zero, so keywords `initial` and `unset` count as zero
* @return `true` if this node represents a non-zero border; otherwise, `false`
*/
function checkLineWidth(value, allowsKeywords = true) {
if (allowsKeywords && matches(value, ['initial', 'unset'])) {
return false;
}
// a <length> is a value and a unit
// so use `parseFloat` to strip the unit
return parseFloat(value.getText()) !== 0;
}
function checkLineWidthList(nodes, allowsKeywords = true) {
return nodes.map(node => checkLineWidth(node, allowsKeywords));
}
/**
* @param allowsKeywords whether keywords `initial` and `unset` count as zero
* @return `true` if this node represents a non-zero border; otherwise, `false`
*/
function checkLineStyle(valueNode, allowsKeywords = true) {
if (matches(valueNode, ['none', 'hidden'])) {
return false;
}
if (allowsKeywords && matches(valueNode, ['initial', 'unset'])) {
return false;
}
return true;
}
function checkLineStyleList(nodes, allowsKeywords = true) {
return nodes.map(node => checkLineStyle(node, allowsKeywords));
}
function checkBorderShorthand(node) {
const children = node.getChildren();
// the only child can be a keyword, a <line-width>, or a <line-style>
// if either check returns false, the result is no border
if (children.length === 1) {
const value = children[0];
return checkLineWidth(value) && checkLineStyle(value);
}
// multiple children can't contain keywords
// if any child means no border, the result is no border
for (const child of children) {
const value = child;
if (!checkLineWidth(value, /* allowsKeywords: */ false) ||
!checkLineStyle(value, /* allowsKeywords: */ false)) {
return false;
}
}
return true;
}
export default function calculateBoxModel(propertyTable) {
const model = {
top: { value: false, properties: [] },
right: { value: false, properties: [] },
bottom: { value: false, properties: [] },
left: { value: false, properties: [] },
};
for (const property of propertyTable) {
const value = property.node.value;
if (typeof value === 'undefined') {
continue;
}
switch (property.fullPropertyName) {
case 'box-sizing':
// has `box-sizing`, bail out
return {
top: { value: false, properties: [] },
right: { value: false, properties: [] },
bottom: { value: false, properties: [] },
left: { value: false, properties: [] },
};
case 'width':
model.width = property;
break;
case 'height':
model.height = property;
break;
default:
const segments = property.fullPropertyName.split('-');
switch (segments[0]) {
case 'border':
switch (segments[1]) {
case undefined:
case 'top':
case 'right':
case 'bottom':
case 'left':
switch (segments[2]) {
case undefined:
updateModelWithValue(model, segments[1], checkBorderShorthand(value), property);
break;
case 'width':
// the initial value of `border-width` is `medium`, not zero
updateModelWithValue(model, segments[1], checkLineWidth(value, false), property);
break;
case 'style':
// the initial value of `border-style` is `none`
updateModelWithValue(model, segments[1], checkLineStyle(value, true), property);
break;
}
break;
case 'width':
// the initial value of `border-width` is `medium`, not zero
updateModelWithList(model, checkLineWidthList(value.getChildren(), false), property);
break;
case 'style':
// the initial value of `border-style` is `none`
updateModelWithList(model, checkLineStyleList(value.getChildren(), true), property);
break;
}
break;
case 'padding':
if (segments.length === 1) {
// the initial value of `padding` is zero
updateModelWithList(model, checkLineWidthList(value.getChildren(), true), property);
}
else {
// the initial value of `padding` is zero
updateModelWithValue(model, segments[1], checkLineWidth(value, true), property);
}
break;
}
break;
}
}
return model;
}

View file

@ -0,0 +1,157 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { FileType, CompletionItemKind, TextEdit, Range, Position } from '../cssLanguageTypes';
import { startsWith, endsWith } from '../utils/strings';
import { joinPath } from '../utils/resources';
export class PathCompletionParticipant {
constructor(readDirectory) {
this.readDirectory = readDirectory;
this.literalCompletions = [];
this.importCompletions = [];
}
onCssURILiteralValue(context) {
this.literalCompletions.push(context);
}
onCssImportPath(context) {
this.importCompletions.push(context);
}
async computeCompletions(document, documentContext) {
const result = { items: [], isIncomplete: false };
for (const literalCompletion of this.literalCompletions) {
const uriValue = literalCompletion.uriValue;
const fullValue = stripQuotes(uriValue);
if (fullValue === '.' || fullValue === '..') {
result.isIncomplete = true;
}
else {
const items = await this.providePathSuggestions(uriValue, literalCompletion.position, literalCompletion.range, document, documentContext);
for (let item of items) {
result.items.push(item);
}
}
}
for (const importCompletion of this.importCompletions) {
const pathValue = importCompletion.pathValue;
const fullValue = stripQuotes(pathValue);
if (fullValue === '.' || fullValue === '..') {
result.isIncomplete = true;
}
else {
let suggestions = await this.providePathSuggestions(pathValue, importCompletion.position, importCompletion.range, document, documentContext);
if (document.languageId === 'scss') {
suggestions.forEach(s => {
if (startsWith(s.label, '_') && endsWith(s.label, '.scss')) {
if (s.textEdit) {
s.textEdit.newText = s.label.slice(1, -5);
}
else {
s.label = s.label.slice(1, -5);
}
}
});
}
for (let item of suggestions) {
result.items.push(item);
}
}
}
return result;
}
async providePathSuggestions(pathValue, position, range, document, documentContext) {
const fullValue = stripQuotes(pathValue);
const isValueQuoted = startsWith(pathValue, `'`) || startsWith(pathValue, `"`);
const valueBeforeCursor = isValueQuoted
? fullValue.slice(0, position.character - (range.start.character + 1))
: fullValue.slice(0, position.character - range.start.character);
const currentDocUri = document.uri;
const fullValueRange = isValueQuoted ? shiftRange(range, 1, -1) : range;
const replaceRange = pathToReplaceRange(valueBeforeCursor, fullValue, fullValueRange);
const valueBeforeLastSlash = valueBeforeCursor.substring(0, valueBeforeCursor.lastIndexOf('/') + 1); // keep the last slash
let parentDir = documentContext.resolveReference(valueBeforeLastSlash || '.', currentDocUri);
if (parentDir) {
try {
const result = [];
const infos = await this.readDirectory(parentDir);
for (const [name, type] of infos) {
// Exclude paths that start with `.`
if (name.charCodeAt(0) !== CharCode_dot && (type === FileType.Directory || joinPath(parentDir, name) !== currentDocUri)) {
result.push(createCompletionItem(name, type === FileType.Directory, replaceRange));
}
}
return result;
}
catch (e) {
// ignore
}
}
return [];
}
}
const CharCode_dot = '.'.charCodeAt(0);
function stripQuotes(fullValue) {
if (startsWith(fullValue, `'`) || startsWith(fullValue, `"`)) {
return fullValue.slice(1, -1);
}
else {
return fullValue;
}
}
function pathToReplaceRange(valueBeforeCursor, fullValue, fullValueRange) {
let replaceRange;
const lastIndexOfSlash = valueBeforeCursor.lastIndexOf('/');
if (lastIndexOfSlash === -1) {
replaceRange = fullValueRange;
}
else {
// For cases where cursor is in the middle of attribute value, like <script src="./s|rc/test.js">
// Find the last slash before cursor, and calculate the start of replace range from there
const valueAfterLastSlash = fullValue.slice(lastIndexOfSlash + 1);
const startPos = shiftPosition(fullValueRange.end, -valueAfterLastSlash.length);
// If whitespace exists, replace until it
const whitespaceIndex = valueAfterLastSlash.indexOf(' ');
let endPos;
if (whitespaceIndex !== -1) {
endPos = shiftPosition(startPos, whitespaceIndex);
}
else {
endPos = fullValueRange.end;
}
replaceRange = Range.create(startPos, endPos);
}
return replaceRange;
}
function createCompletionItem(name, isDir, replaceRange) {
if (isDir) {
name = name + '/';
return {
label: escapePath(name),
kind: CompletionItemKind.Folder,
textEdit: TextEdit.replace(replaceRange, escapePath(name)),
command: {
title: 'Suggest',
command: 'editor.action.triggerSuggest'
}
};
}
else {
return {
label: escapePath(name),
kind: CompletionItemKind.File,
textEdit: TextEdit.replace(replaceRange, escapePath(name))
};
}
}
// Escape https://www.w3.org/TR/CSS1/#url
function escapePath(p) {
return p.replace(/(\s|\(|\)|,|"|')/g, '\\$1');
}
function shiftPosition(pos, offset) {
return Position.create(pos.line, pos.character + offset);
}
function shiftRange(range, startOffset, endOffset) {
const start = shiftPosition(range.start, startOffset);
const end = shiftPosition(range.end, endOffset);
return Range.create(start, end);
}

View file

@ -0,0 +1,355 @@
/*---------------------------------------------------------------------------------------------
* 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 { CSSCompletion } from './cssCompletion';
import * as nodes from '../parser/cssNodes';
import { CompletionItemKind, TextEdit, InsertTextFormat } from '../cssLanguageTypes';
import * as l10n from '@vscode/l10n';
const sassDocumentationName = l10n.t('Sass documentation');
class SCSSCompletion extends CSSCompletion {
constructor(lsServiceOptions, cssDataManager) {
super('$', lsServiceOptions, cssDataManager);
addReferencesToDocumentation(SCSSCompletion.scssModuleLoaders);
addReferencesToDocumentation(SCSSCompletion.scssModuleBuiltIns);
}
isImportPathParent(type) {
return type === nodes.NodeType.Forward
|| type === nodes.NodeType.Use
|| super.isImportPathParent(type);
}
getCompletionForImportPath(importPathNode, result) {
const parentType = importPathNode.getParent().type;
if (parentType === nodes.NodeType.Forward || parentType === nodes.NodeType.Use) {
for (let p of SCSSCompletion.scssModuleBuiltIns) {
const item = {
label: p.label,
documentation: p.documentation,
textEdit: TextEdit.replace(this.getCompletionRange(importPathNode), `'${p.label}'`),
kind: CompletionItemKind.Module
};
result.items.push(item);
}
}
return super.getCompletionForImportPath(importPathNode, result);
}
createReplaceFunction() {
let tabStopCounter = 1;
return (_match, p1) => {
return '\\' + p1 + ': ${' + tabStopCounter++ + ':' + (SCSSCompletion.variableDefaults[p1] || '') + '}';
};
}
createFunctionProposals(proposals, existingNode, sortToEnd, result) {
for (const p of proposals) {
const insertText = p.func.replace(/\[?(\$\w+)\]?/g, this.createReplaceFunction());
const label = p.func.substr(0, p.func.indexOf('('));
const item = {
label: label,
detail: p.func,
documentation: p.desc,
textEdit: TextEdit.replace(this.getCompletionRange(existingNode), insertText),
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Function
};
if (sortToEnd) {
item.sortText = 'z';
}
result.items.push(item);
}
return result;
}
getCompletionsForSelector(ruleSet, isNested, result) {
this.createFunctionProposals(SCSSCompletion.selectorFuncs, null, true, result);
return super.getCompletionsForSelector(ruleSet, isNested, result);
}
getTermProposals(entry, existingNode, result) {
let functions = SCSSCompletion.builtInFuncs;
if (entry) {
functions = functions.filter(f => !f.type || !entry.restrictions || entry.restrictions.indexOf(f.type) !== -1);
}
this.createFunctionProposals(functions, existingNode, true, result);
return super.getTermProposals(entry, existingNode, result);
}
getColorProposals(entry, existingNode, result) {
this.createFunctionProposals(SCSSCompletion.colorProposals, existingNode, false, result);
return super.getColorProposals(entry, existingNode, result);
}
getCompletionsForDeclarationProperty(declaration, result) {
this.getCompletionForAtDirectives(result);
this.getCompletionsForSelector(null, true, result);
return super.getCompletionsForDeclarationProperty(declaration, result);
}
getCompletionsForExtendsReference(_extendsRef, existingNode, result) {
const symbols = this.getSymbolContext().findSymbolsAtOffset(this.offset, nodes.ReferenceType.Rule);
for (const symbol of symbols) {
const suggest = {
label: symbol.name,
textEdit: TextEdit.replace(this.getCompletionRange(existingNode), symbol.name),
kind: CompletionItemKind.Function,
};
result.items.push(suggest);
}
return result;
}
getCompletionForAtDirectives(result) {
result.items.push(...SCSSCompletion.scssAtDirectives);
return result;
}
getCompletionForTopLevel(result) {
this.getCompletionForAtDirectives(result);
this.getCompletionForModuleLoaders(result);
super.getCompletionForTopLevel(result);
return result;
}
getCompletionForModuleLoaders(result) {
result.items.push(...SCSSCompletion.scssModuleLoaders);
return result;
}
}
SCSSCompletion.variableDefaults = {
'$red': '1',
'$green': '2',
'$blue': '3',
'$alpha': '1.0',
'$color': '#000000',
'$weight': '0.5',
'$hue': '0',
'$saturation': '0%',
'$lightness': '0%',
'$degrees': '0',
'$amount': '0',
'$string': '""',
'$substring': '"s"',
'$number': '0',
'$limit': '1'
};
SCSSCompletion.colorProposals = [
{ func: 'red($color)', desc: l10n.t('Gets the red component of a color.') },
{ func: 'green($color)', desc: l10n.t('Gets the green component of a color.') },
{ func: 'blue($color)', desc: l10n.t('Gets the blue component of a color.') },
{ func: 'mix($color, $color, [$weight])', desc: l10n.t('Mixes two colors together.') },
{ func: 'hue($color)', desc: l10n.t('Gets the hue component of a color.') },
{ func: 'saturation($color)', desc: l10n.t('Gets the saturation component of a color.') },
{ func: 'lightness($color)', desc: l10n.t('Gets the lightness component of a color.') },
{ func: 'adjust-hue($color, $degrees)', desc: l10n.t('Changes the hue of a color.') },
{ func: 'lighten($color, $amount)', desc: l10n.t('Makes a color lighter.') },
{ func: 'darken($color, $amount)', desc: l10n.t('Makes a color darker.') },
{ func: 'saturate($color, $amount)', desc: l10n.t('Makes a color more saturated.') },
{ func: 'desaturate($color, $amount)', desc: l10n.t('Makes a color less saturated.') },
{ func: 'grayscale($color)', desc: l10n.t('Converts a color to grayscale.') },
{ func: 'complement($color)', desc: l10n.t('Returns the complement of a color.') },
{ func: 'invert($color)', desc: l10n.t('Returns the inverse of a color.') },
{ func: 'alpha($color)', desc: l10n.t('Gets the opacity component of a color.') },
{ func: 'opacity($color)', desc: 'Gets the alpha component (opacity) of a color.' },
{ func: 'rgba($color, $alpha)', desc: l10n.t('Changes the alpha component for a color.') },
{ func: 'opacify($color, $amount)', desc: l10n.t('Makes a color more opaque.') },
{ func: 'fade-in($color, $amount)', desc: l10n.t('Makes a color more opaque.') },
{ func: 'transparentize($color, $amount)', desc: l10n.t('Makes a color more transparent.') },
{ func: 'fade-out($color, $amount)', desc: l10n.t('Makes a color more transparent.') },
{ func: 'adjust-color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha])', desc: l10n.t('Increases or decreases one or more components of a color.') },
{ func: 'scale-color($color, [$red], [$green], [$blue], [$saturation], [$lightness], [$alpha])', desc: l10n.t('Fluidly scales one or more properties of a color.') },
{ func: 'change-color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha])', desc: l10n.t('Changes one or more properties of a color.') },
{ func: 'ie-hex-str($color)', desc: l10n.t('Converts a color into the format understood by IE filters.') }
];
SCSSCompletion.selectorFuncs = [
{ func: 'selector-nest($selectors…)', desc: l10n.t('Nests selector beneath one another like they would be nested in the stylesheet.') },
{ func: 'selector-append($selectors…)', desc: l10n.t('Appends selectors to one another without spaces in between.') },
{ func: 'selector-extend($selector, $extendee, $extender)', desc: l10n.t('Extends $extendee with $extender within $selector.') },
{ func: 'selector-replace($selector, $original, $replacement)', desc: l10n.t('Replaces $original with $replacement within $selector.') },
{ func: 'selector-unify($selector1, $selector2)', desc: l10n.t('Unifies two selectors to produce a selector that matches elements matched by both.') },
{ func: 'is-superselector($super, $sub)', desc: l10n.t('Returns whether $super matches all the elements $sub does, and possibly more.') },
{ func: 'simple-selectors($selector)', desc: l10n.t('Returns the simple selectors that comprise a compound selector.') },
{ func: 'selector-parse($selector)', desc: l10n.t('Parses a selector into the format returned by &.') }
];
SCSSCompletion.builtInFuncs = [
{ func: 'unquote($string)', desc: l10n.t('Removes quotes from a string.') },
{ func: 'quote($string)', desc: l10n.t('Adds quotes to a string.') },
{ func: 'str-length($string)', desc: l10n.t('Returns the number of characters in a string.') },
{ func: 'str-insert($string, $insert, $index)', desc: l10n.t('Inserts $insert into $string at $index.') },
{ func: 'str-index($string, $substring)', desc: l10n.t('Returns the index of the first occurance of $substring in $string.') },
{ func: 'str-slice($string, $start-at, [$end-at])', desc: l10n.t('Extracts a substring from $string.') },
{ func: 'to-upper-case($string)', desc: l10n.t('Converts a string to upper case.') },
{ func: 'to-lower-case($string)', desc: l10n.t('Converts a string to lower case.') },
{ func: 'percentage($number)', desc: l10n.t('Converts a unitless number to a percentage.'), type: 'percentage' },
{ func: 'round($number)', desc: l10n.t('Rounds a number to the nearest whole number.') },
{ func: 'ceil($number)', desc: l10n.t('Rounds a number up to the next whole number.') },
{ func: 'floor($number)', desc: l10n.t('Rounds a number down to the previous whole number.') },
{ func: 'abs($number)', desc: l10n.t('Returns the absolute value of a number.') },
{ func: 'min($numbers)', desc: l10n.t('Finds the minimum of several numbers.') },
{ func: 'max($numbers)', desc: l10n.t('Finds the maximum of several numbers.') },
{ func: 'random([$limit])', desc: l10n.t('Returns a random number.') },
{ func: 'length($list)', desc: l10n.t('Returns the length of a list.') },
{ func: 'nth($list, $n)', desc: l10n.t('Returns a specific item in a list.') },
{ func: 'set-nth($list, $n, $value)', desc: l10n.t('Replaces the nth item in a list.') },
{ func: 'join($list1, $list2, [$separator])', desc: l10n.t('Joins together two lists into one.') },
{ func: 'append($list1, $val, [$separator])', desc: l10n.t('Appends a single value onto the end of a list.') },
{ func: 'zip($lists)', desc: l10n.t('Combines several lists into a single multidimensional list.') },
{ func: 'index($list, $value)', desc: l10n.t('Returns the position of a value within a list.') },
{ func: 'list-separator(#list)', desc: l10n.t('Returns the separator of a list.') },
{ func: 'map-get($map, $key)', desc: l10n.t('Returns the value in a map associated with a given key.') },
{ func: 'map-merge($map1, $map2)', desc: l10n.t('Merges two maps together into a new map.') },
{ func: 'map-remove($map, $keys)', desc: l10n.t('Returns a new map with keys removed.') },
{ func: 'map-keys($map)', desc: l10n.t('Returns a list of all keys in a map.') },
{ func: 'map-values($map)', desc: l10n.t('Returns a list of all values in a map.') },
{ func: 'map-has-key($map, $key)', desc: l10n.t('Returns whether a map has a value associated with a given key.') },
{ func: 'keywords($args)', desc: l10n.t('Returns the keywords passed to a function that takes variable arguments.') },
{ func: 'feature-exists($feature)', desc: l10n.t('Returns whether a feature exists in the current Sass runtime.') },
{ func: 'variable-exists($name)', desc: l10n.t('Returns whether a variable with the given name exists in the current scope.') },
{ func: 'global-variable-exists($name)', desc: l10n.t('Returns whether a variable with the given name exists in the global scope.') },
{ func: 'function-exists($name)', desc: l10n.t('Returns whether a function with the given name exists.') },
{ func: 'mixin-exists($name)', desc: l10n.t('Returns whether a mixin with the given name exists.') },
{ func: 'inspect($value)', desc: l10n.t('Returns the string representation of a value as it would be represented in Sass.') },
{ func: 'type-of($value)', desc: l10n.t('Returns the type of a value.') },
{ func: 'unit($number)', desc: l10n.t('Returns the unit(s) associated with a number.') },
{ func: 'unitless($number)', desc: l10n.t('Returns whether a number has units.') },
{ func: 'comparable($number1, $number2)', desc: l10n.t('Returns whether two numbers can be added, subtracted, or compared.') },
{ func: 'call($name, $args…)', desc: l10n.t('Dynamically calls a Sass function.') }
];
SCSSCompletion.scssAtDirectives = [
{
label: "@extend",
documentation: l10n.t("Inherits the styles of another selector."),
kind: CompletionItemKind.Keyword
},
{
label: "@at-root",
documentation: l10n.t("Causes one or more rules to be emitted at the root of the document."),
kind: CompletionItemKind.Keyword
},
{
label: "@debug",
documentation: l10n.t("Prints the value of an expression to the standard error output stream. Useful for debugging complicated Sass files."),
kind: CompletionItemKind.Keyword
},
{
label: "@warn",
documentation: l10n.t("Prints the value of an expression to the standard error output stream. Useful for libraries that need to warn users of deprecations or recovering from minor mixin usage mistakes. Warnings can be turned off with the `--quiet` command-line option or the `:quiet` Sass option."),
kind: CompletionItemKind.Keyword
},
{
label: "@error",
documentation: l10n.t("Throws the value of an expression as a fatal error with stack trace. Useful for validating arguments to mixins and functions."),
kind: CompletionItemKind.Keyword
},
{
label: "@if",
documentation: l10n.t("Includes the body if the expression does not evaluate to `false` or `null`."),
insertText: "@if ${1:expr} {\n\t$0\n}",
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Keyword
},
{
label: "@for",
documentation: l10n.t("For loop that repeatedly outputs a set of styles for each `$var` in the `from/through` or `from/to` clause."),
insertText: "@for \\$${1:var} from ${2:start} ${3|to,through|} ${4:end} {\n\t$0\n}",
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Keyword
},
{
label: "@each",
documentation: l10n.t("Each loop that sets `$var` to each item in the list or map, then outputs the styles it contains using that value of `$var`."),
insertText: "@each \\$${1:var} in ${2:list} {\n\t$0\n}",
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Keyword
},
{
label: "@while",
documentation: l10n.t("While loop that takes an expression and repeatedly outputs the nested styles until the statement evaluates to `false`."),
insertText: "@while ${1:condition} {\n\t$0\n}",
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Keyword
},
{
label: "@mixin",
documentation: l10n.t("Defines styles that can be re-used throughout the stylesheet with `@include`."),
insertText: "@mixin ${1:name} {\n\t$0\n}",
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Keyword
},
{
label: "@include",
documentation: l10n.t("Includes the styles defined by another mixin into the current rule."),
kind: CompletionItemKind.Keyword
},
{
label: "@function",
documentation: l10n.t("Defines complex operations that can be re-used throughout stylesheets."),
kind: CompletionItemKind.Keyword
}
];
SCSSCompletion.scssModuleLoaders = [
{
label: "@use",
documentation: l10n.t("Loads mixins, functions, and variables from other Sass stylesheets as 'modules', and combines CSS from multiple stylesheets together."),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/at-rules/use' }],
insertText: "@use $0;",
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Keyword
},
{
label: "@forward",
documentation: l10n.t("Loads a Sass stylesheet and makes its mixins, functions, and variables available when this stylesheet is loaded with the @use rule."),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/at-rules/forward' }],
insertText: "@forward $0;",
insertTextFormat: InsertTextFormat.Snippet,
kind: CompletionItemKind.Keyword
},
];
SCSSCompletion.scssModuleBuiltIns = [
{
label: 'sass:math',
documentation: l10n.t('Provides functions that operate on numbers.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/math' }]
},
{
label: 'sass:string',
documentation: l10n.t('Makes it easy to combine, search, or split apart strings.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/string' }]
},
{
label: 'sass:color',
documentation: l10n.t('Generates new colors based on existing ones, making it easy to build color themes.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/color' }]
},
{
label: 'sass:list',
documentation: l10n.t('Lets you access and modify values in lists.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/list' }]
},
{
label: 'sass:map',
documentation: l10n.t('Makes it possible to look up the value associated with a key in a map, and much more.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/map' }]
},
{
label: 'sass:selector',
documentation: l10n.t('Provides access to Sasss powerful selector engine.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/selector' }]
},
{
label: 'sass:meta',
documentation: l10n.t('Exposes the details of Sasss inner workings.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/meta' }]
},
];
export { SCSSCompletion };
/**
* Todo @Pine: Remove this and do it through custom data
*/
function addReferencesToDocumentation(items) {
items.forEach(i => {
if (i.documentation && i.references && i.references.length > 0) {
const markdownDoc = typeof i.documentation === 'string'
? { kind: 'markdown', value: i.documentation }
: { kind: 'markdown', value: i.documentation.value };
markdownDoc.value += '\n\n';
markdownDoc.value += i.references
.map(r => {
return `[${r.name}](${r.url})`;
})
.join(' | ');
i.documentation = markdownDoc;
}
});
}

View file

@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* 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 { CSSNavigation } from './cssNavigation';
import * as nodes from '../parser/cssNodes';
import { URI, Utils } from 'vscode-uri';
import { startsWith } from '../utils/strings';
export class SCSSNavigation extends CSSNavigation {
constructor(fileSystemProvider) {
super(fileSystemProvider, true);
}
isRawStringDocumentLinkNode(node) {
return (super.isRawStringDocumentLinkNode(node) ||
node.type === nodes.NodeType.Use ||
node.type === nodes.NodeType.Forward);
}
async mapReference(target, isRawLink) {
if (this.fileSystemProvider && target && isRawLink) {
const pathVariations = toPathVariations(target);
for (const variation of pathVariations) {
if (await this.fileExists(variation)) {
return variation;
}
}
}
return target;
}
async resolveReference(target, documentUri, documentContext, isRawLink = false) {
if (startsWith(target, 'sass:')) {
return undefined; // sass library
}
return super.resolveReference(target, documentUri, documentContext, isRawLink);
}
}
function toPathVariations(target) {
// No variation for links that ends with suffix
if (target.endsWith('.scss') || target.endsWith('.css')) {
return [target];
}
// If a link is like a/, try resolving a/index.scss and a/_index.scss
if (target.endsWith('/')) {
return [target + 'index.scss', target + '_index.scss'];
}
const targetUri = URI.parse(target);
const basename = Utils.basename(targetUri);
const dirname = Utils.dirname(targetUri);
if (basename.startsWith('_')) {
// No variation for links such as _a
return [Utils.joinPath(dirname, basename + '.scss').toString(true)];
}
return [
Utils.joinPath(dirname, basename + '.scss').toString(true),
Utils.joinPath(dirname, '_' + basename + '.scss').toString(true),
target + '/index.scss',
target + '/_index.scss',
Utils.joinPath(dirname, basename + '.css').toString(true)
];
}

View file

@ -0,0 +1,490 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nodes from '../parser/cssNodes';
import { Scanner } from '../parser/cssScanner';
import * as l10n from '@vscode/l10n';
export class Element {
constructor() {
this.parent = null;
this.children = null;
this.attributes = null;
}
findAttribute(name) {
if (this.attributes) {
for (const attribute of this.attributes) {
if (attribute.name === name) {
return attribute.value;
}
}
}
return null;
}
addChild(child) {
if (child instanceof Element) {
child.parent = this;
}
if (!this.children) {
this.children = [];
}
this.children.push(child);
}
append(text) {
if (this.attributes) {
const last = this.attributes[this.attributes.length - 1];
last.value = last.value + text;
}
}
prepend(text) {
if (this.attributes) {
const first = this.attributes[0];
first.value = text + first.value;
}
}
findRoot() {
let curr = this;
while (curr.parent && !(curr.parent instanceof RootElement)) {
curr = curr.parent;
}
return curr;
}
removeChild(child) {
if (this.children) {
const index = this.children.indexOf(child);
if (index !== -1) {
this.children.splice(index, 1);
return true;
}
}
return false;
}
addAttr(name, value) {
if (!this.attributes) {
this.attributes = [];
}
for (const attribute of this.attributes) {
if (attribute.name === name) {
attribute.value += ' ' + value;
return;
}
}
this.attributes.push({ name, value });
}
clone(cloneChildren = true) {
const elem = new Element();
if (this.attributes) {
elem.attributes = [];
for (const attribute of this.attributes) {
elem.addAttr(attribute.name, attribute.value);
}
}
if (cloneChildren && this.children) {
elem.children = [];
for (let index = 0; index < this.children.length; index++) {
elem.addChild(this.children[index].clone());
}
}
return elem;
}
cloneWithParent() {
const clone = this.clone(false);
if (this.parent && !(this.parent instanceof RootElement)) {
const parentClone = this.parent.cloneWithParent();
parentClone.addChild(clone);
}
return clone;
}
}
export class RootElement extends Element {
}
export class LabelElement extends Element {
constructor(label) {
super();
this.addAttr('name', label);
}
}
class MarkedStringPrinter {
constructor(quote) {
this.quote = quote;
this.result = [];
// empty
}
print(element) {
this.result = [];
if (element instanceof RootElement) {
if (element.children) {
this.doPrint(element.children, 0);
}
}
else {
this.doPrint([element], 0);
}
const value = this.result.join('\n');
return [{ language: 'html', value }];
}
doPrint(elements, indent) {
for (const element of elements) {
this.doPrintElement(element, indent);
if (element.children) {
this.doPrint(element.children, indent + 1);
}
}
}
writeLine(level, content) {
const indent = new Array(level + 1).join(' ');
this.result.push(indent + content);
}
doPrintElement(element, indent) {
const name = element.findAttribute('name');
// special case: a simple label
if (element instanceof LabelElement || name === '\u2026') {
this.writeLine(indent, name);
return;
}
// the real deal
const content = ['<'];
// element name
if (name) {
content.push(name);
}
else {
content.push('element');
}
// attributes
if (element.attributes) {
for (const attr of element.attributes) {
if (attr.name !== 'name') {
content.push(' ');
content.push(attr.name);
const value = attr.value;
if (value) {
content.push('=');
content.push(quotes.ensure(value, this.quote));
}
}
}
}
content.push('>');
this.writeLine(indent, content.join(''));
}
}
var quotes;
(function (quotes) {
function ensure(value, which) {
return which + remove(value) + which;
}
quotes.ensure = ensure;
function remove(value) {
const match = value.match(/^['"](.*)["']$/);
if (match) {
return match[1];
}
return value;
}
quotes.remove = remove;
})(quotes || (quotes = {}));
class Specificity {
constructor() {
/** Count of identifiers (e.g., `#app`) */
this.id = 0;
/** Count of attributes (`[type="number"]`), classes (`.container-fluid`), and pseudo-classes (`:hover`) */
this.attr = 0;
/** Count of tag names (`div`), and pseudo-elements (`::before`) */
this.tag = 0;
}
}
export function toElement(node, parentElement) {
let result = new Element();
for (const child of node.getChildren()) {
switch (child.type) {
case nodes.NodeType.SelectorCombinator:
if (parentElement) {
const segments = child.getText().split('&');
if (segments.length === 1) {
// should not happen
result.addAttr('name', segments[0]);
break;
}
result = parentElement.cloneWithParent();
if (segments[0]) {
const root = result.findRoot();
root.prepend(segments[0]);
}
for (let i = 1; i < segments.length; i++) {
if (i > 1) {
const clone = parentElement.cloneWithParent();
result.addChild(clone.findRoot());
result = clone;
}
result.append(segments[i]);
}
}
break;
case nodes.NodeType.SelectorPlaceholder:
if (child.matches('@at-root')) {
return result;
}
// fall through
case nodes.NodeType.ElementNameSelector:
const text = child.getText();
result.addAttr('name', text === '*' ? 'element' : unescape(text));
break;
case nodes.NodeType.ClassSelector:
result.addAttr('class', unescape(child.getText().substring(1)));
break;
case nodes.NodeType.IdentifierSelector:
result.addAttr('id', unescape(child.getText().substring(1)));
break;
case nodes.NodeType.MixinDeclaration:
result.addAttr('class', child.getName());
break;
case nodes.NodeType.PseudoSelector:
result.addAttr(unescape(child.getText()), '');
break;
case nodes.NodeType.AttributeSelector:
const selector = child;
const identifier = selector.getIdentifier();
if (identifier) {
const expression = selector.getValue();
const operator = selector.getOperator();
let value;
if (expression && operator) {
switch (unescape(operator.getText())) {
case '|=':
// excatly or followed by -words
value = `${quotes.remove(unescape(expression.getText()))}-\u2026`;
break;
case '^=':
// prefix
value = `${quotes.remove(unescape(expression.getText()))}\u2026`;
break;
case '$=':
// suffix
value = `\u2026${quotes.remove(unescape(expression.getText()))}`;
break;
case '~=':
// one of a list of words
value = ` \u2026 ${quotes.remove(unescape(expression.getText()))} \u2026 `;
break;
case '*=':
// substring
value = `\u2026${quotes.remove(unescape(expression.getText()))}\u2026`;
break;
default:
value = quotes.remove(unescape(expression.getText()));
break;
}
}
result.addAttr(unescape(identifier.getText()), value);
}
break;
}
}
return result;
}
function unescape(content) {
const scanner = new Scanner();
scanner.setSource(content);
const token = scanner.scanUnquotedString();
if (token) {
return token.text;
}
return content;
}
export class SelectorPrinting {
constructor(cssDataManager) {
this.cssDataManager = cssDataManager;
}
selectorToMarkedString(node) {
const root = selectorToElement(node);
if (root) {
const markedStrings = new MarkedStringPrinter('"').print(root);
markedStrings.push(this.selectorToSpecificityMarkedString(node));
return markedStrings;
}
else {
return [];
}
}
simpleSelectorToMarkedString(node) {
const element = toElement(node);
const markedStrings = new MarkedStringPrinter('"').print(element);
markedStrings.push(this.selectorToSpecificityMarkedString(node));
return markedStrings;
}
isPseudoElementIdentifier(text) {
const match = text.match(/^::?([\w-]+)/);
if (!match) {
return false;
}
return !!this.cssDataManager.getPseudoElement("::" + match[1]);
}
selectorToSpecificityMarkedString(node) {
//https://www.w3.org/TR/selectors-3/#specificity
const calculateScore = (node) => {
const specificity = new Specificity();
elementLoop: for (const element of node.getChildren()) {
switch (element.type) {
case nodes.NodeType.IdentifierSelector:
specificity.id++;
break;
case nodes.NodeType.ClassSelector:
case nodes.NodeType.AttributeSelector:
specificity.attr++;
break;
case nodes.NodeType.ElementNameSelector:
//ignore universal selector
if (element.matches("*")) {
break;
}
specificity.tag++;
break;
case nodes.NodeType.PseudoSelector:
const text = element.getText();
if (this.isPseudoElementIdentifier(text)) {
specificity.tag++; // pseudo element
continue elementLoop;
}
// where and child selectors have zero specificity
if (text.match(/^:where/i)) {
continue elementLoop;
}
// the most specific child selector
if (text.match(/^:(not|has|is)/i) && element.getChildren().length > 0) {
let mostSpecificListItem = new Specificity();
for (const containerElement of element.getChildren()) {
let list;
if (containerElement.type === nodes.NodeType.Undefined) { // containerElement is a list of selectors
list = containerElement.getChildren();
}
else { // containerElement is a selector
list = [containerElement];
}
for (const childElement of containerElement.getChildren()) {
const itemSpecificity = calculateScore(childElement);
if (itemSpecificity.id > mostSpecificListItem.id) {
mostSpecificListItem = itemSpecificity;
continue;
}
else if (itemSpecificity.id < mostSpecificListItem.id) {
continue;
}
if (itemSpecificity.attr > mostSpecificListItem.attr) {
mostSpecificListItem = itemSpecificity;
continue;
}
else if (itemSpecificity.attr < mostSpecificListItem.attr) {
continue;
}
if (itemSpecificity.tag > mostSpecificListItem.tag) {
mostSpecificListItem = itemSpecificity;
continue;
}
}
}
specificity.id += mostSpecificListItem.id;
specificity.attr += mostSpecificListItem.attr;
specificity.tag += mostSpecificListItem.tag;
continue elementLoop;
}
specificity.attr++; //pseudo class
continue elementLoop;
}
if (element.getChildren().length > 0) {
const itemSpecificity = calculateScore(element);
specificity.id += itemSpecificity.id;
specificity.attr += itemSpecificity.attr;
specificity.tag += itemSpecificity.tag;
}
}
return specificity;
};
const specificity = calculateScore(node);
return `[${l10n.t("Selector Specificity")}](https://developer.mozilla.org/docs/Web/CSS/Specificity): (${specificity.id}, ${specificity.attr}, ${specificity.tag})`;
}
}
class SelectorElementBuilder {
constructor(element) {
this.prev = null;
this.element = element;
}
processSelector(selector) {
let parentElement = null;
if (!(this.element instanceof RootElement)) {
if (selector.getChildren().some((c) => c.hasChildren() && c.getChild(0).type === nodes.NodeType.SelectorCombinator)) {
const curr = this.element.findRoot();
if (curr.parent instanceof RootElement) {
parentElement = this.element;
this.element = curr.parent;
this.element.removeChild(curr);
this.prev = null;
}
}
}
for (const selectorChild of selector.getChildren()) {
if (selectorChild instanceof nodes.SimpleSelector) {
if (this.prev instanceof nodes.SimpleSelector) {
const labelElement = new LabelElement('\u2026');
this.element.addChild(labelElement);
this.element = labelElement;
}
else if (this.prev && (this.prev.matches('+') || this.prev.matches('~')) && this.element.parent) {
this.element = this.element.parent;
}
if (this.prev && this.prev.matches('~')) {
this.element.addChild(new LabelElement('\u22EE'));
}
const thisElement = toElement(selectorChild, parentElement);
const root = thisElement.findRoot();
this.element.addChild(root);
this.element = thisElement;
}
if (selectorChild instanceof nodes.SimpleSelector ||
selectorChild.type === nodes.NodeType.SelectorCombinatorParent ||
selectorChild.type === nodes.NodeType.SelectorCombinatorShadowPiercingDescendant ||
selectorChild.type === nodes.NodeType.SelectorCombinatorSibling ||
selectorChild.type === nodes.NodeType.SelectorCombinatorAllSiblings) {
this.prev = selectorChild;
}
}
}
}
function isNewSelectorContext(node) {
switch (node.type) {
case nodes.NodeType.MixinDeclaration:
case nodes.NodeType.Stylesheet:
return true;
}
return false;
}
export function selectorToElement(node) {
if (node.matches('@at-root')) {
return null;
}
const root = new RootElement();
const parentRuleSets = [];
const ruleSet = node.getParent();
if (ruleSet instanceof nodes.RuleSet) {
let parent = ruleSet.getParent(); // parent of the selector's ruleset
while (parent && !isNewSelectorContext(parent)) {
if (parent instanceof nodes.RuleSet) {
if (parent.getSelectors().matches('@at-root')) {
break;
}
parentRuleSets.push(parent);
}
parent = parent.getParent();
}
}
const builder = new SelectorElementBuilder(root);
for (let i = parentRuleSets.length - 1; i >= 0; i--) {
const selector = parentRuleSets[i].getSelectors().getChild(0);
if (selector) {
builder.processSelector(selector);
}
}
builder.processSelector(node);
return root;
}

View file

@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
/**
* Takes a sorted array and a function p. The array is sorted in such a way that all elements where p(x) is false
* are located before all elements where p(x) is true.
* @returns the least x for which p(x) is true or array.length if no element fullfills the given function.
*/
export function findFirst(array, p) {
let low = 0, high = array.length;
if (high === 0) {
return 0; // no children
}
while (low < high) {
let mid = Math.floor((low + high) / 2);
if (p(array[mid])) {
high = mid;
}
else {
low = mid + 1;
}
}
return low;
}
export function includes(array, item) {
return array.indexOf(item) !== -1;
}
export function union(...arrays) {
const result = [];
for (const array of arrays) {
for (const item of array) {
if (!includes(result, item)) {
result.push(item);
}
}
}
return result;
}

View file

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export function values(obj) {
return Object.keys(obj).map(key => obj[key]);
}
export function isDefined(obj) {
return typeof obj !== 'undefined';
}

View file

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI, Utils } from 'vscode-uri';
export function dirname(uriString) {
return Utils.dirname(URI.parse(uriString)).toString(true);
}
export function joinPath(uriString, ...paths) {
return Utils.joinPath(URI.parse(uriString), ...paths).toString(true);
}

View file

@ -0,0 +1,102 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export function startsWith(haystack, needle) {
if (haystack.length < needle.length) {
return false;
}
for (let i = 0; i < needle.length; i++) {
if (haystack[i] !== needle[i]) {
return false;
}
}
return true;
}
/**
* Determines if haystack ends with needle.
*/
export function endsWith(haystack, needle) {
let diff = haystack.length - needle.length;
if (diff > 0) {
return haystack.lastIndexOf(needle) === diff;
}
else if (diff === 0) {
return haystack === needle;
}
else {
return false;
}
}
/**
* Computes the difference score for two strings. More similar strings have a higher score.
* We use largest common subsequence dynamic programming approach but penalize in the end for length differences.
* Strings that have a large length difference will get a bad default score 0.
* Complexity - both time and space O(first.length * second.length)
* Dynamic programming LCS computation http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
*
* @param first a string
* @param second a string
*/
export function difference(first, second, maxLenDelta = 4) {
let lengthDifference = Math.abs(first.length - second.length);
// We only compute score if length of the currentWord and length of entry.name are similar.
if (lengthDifference > maxLenDelta) {
return 0;
}
// Initialize LCS (largest common subsequence) matrix.
let LCS = [];
let zeroArray = [];
let i, j;
for (i = 0; i < second.length + 1; ++i) {
zeroArray.push(0);
}
for (i = 0; i < first.length + 1; ++i) {
LCS.push(zeroArray);
}
for (i = 1; i < first.length + 1; ++i) {
for (j = 1; j < second.length + 1; ++j) {
if (first[i - 1] === second[j - 1]) {
LCS[i][j] = LCS[i - 1][j - 1] + 1;
}
else {
LCS[i][j] = Math.max(LCS[i - 1][j], LCS[i][j - 1]);
}
}
}
return LCS[first.length][second.length] - Math.sqrt(lengthDifference);
}
/**
* Limit of string length.
*/
export function getLimitedString(str, ellipsis = true) {
if (!str) {
return '';
}
if (str.length < 140) {
return str;
}
return str.slice(0, 140) + (ellipsis ? '\u2026' : '');
}
/**
* Limit of string length.
*/
export function trim(str, regexp) {
const m = regexp.exec(str);
if (m && m[0].length) {
return str.substr(0, str.length - m[0].length);
}
return str;
}
export function repeat(value, count) {
let s = '';
while (count > 0) {
if ((count & 1) === 1) {
s += value;
}
value += value;
count = count >>> 1;
}
return s;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,39 @@
import { LanguageSettings, ICompletionParticipant, DocumentContext, LanguageServiceOptions, Diagnostic, Position, CompletionList, Hover, Location, DocumentHighlight, DocumentLink, SymbolInformation, Range, CodeActionContext, Command, CodeAction, ColorInformation, Color, ColorPresentation, WorkspaceEdit, FoldingRange, SelectionRange, TextDocument, ICSSDataProvider, CSSDataV1, HoverSettings, CompletionSettings, TextEdit, CSSFormatConfiguration, DocumentSymbol } from './cssLanguageTypes';
export type Stylesheet = {};
export * from './cssLanguageTypes';
export interface LanguageService {
configure(raw?: LanguageSettings): void;
setDataProviders(useDefaultDataProvider: boolean, customDataProviders: ICSSDataProvider[]): void;
doValidation(document: TextDocument, stylesheet: Stylesheet, documentSettings?: LanguageSettings): Diagnostic[];
parseStylesheet(document: TextDocument): Stylesheet;
doComplete(document: TextDocument, position: Position, stylesheet: Stylesheet, settings?: CompletionSettings): CompletionList;
doComplete2(document: TextDocument, position: Position, stylesheet: Stylesheet, documentContext: DocumentContext, settings?: CompletionSettings): Promise<CompletionList>;
setCompletionParticipants(registeredCompletionParticipants: ICompletionParticipant[]): void;
doHover(document: TextDocument, position: Position, stylesheet: Stylesheet, settings?: HoverSettings): Hover | null;
findDefinition(document: TextDocument, position: Position, stylesheet: Stylesheet): Location | null;
findReferences(document: TextDocument, position: Position, stylesheet: Stylesheet): Location[];
findDocumentHighlights(document: TextDocument, position: Position, stylesheet: Stylesheet): DocumentHighlight[];
findDocumentLinks(document: TextDocument, stylesheet: Stylesheet, documentContext: DocumentContext): DocumentLink[];
/**
* Return statically resolved links, and dynamically resolved links if `fsProvider` is proved.
*/
findDocumentLinks2(document: TextDocument, stylesheet: Stylesheet, documentContext: DocumentContext): Promise<DocumentLink[]>;
findDocumentSymbols(document: TextDocument, stylesheet: Stylesheet): SymbolInformation[];
findDocumentSymbols2(document: TextDocument, stylesheet: Stylesheet): DocumentSymbol[];
doCodeActions(document: TextDocument, range: Range, context: CodeActionContext, stylesheet: Stylesheet): Command[];
doCodeActions2(document: TextDocument, range: Range, context: CodeActionContext, stylesheet: Stylesheet): CodeAction[];
findDocumentColors(document: TextDocument, stylesheet: Stylesheet): ColorInformation[];
getColorPresentations(document: TextDocument, stylesheet: Stylesheet, color: Color, range: Range): ColorPresentation[];
prepareRename(document: TextDocument, position: Position, stylesheet: Stylesheet): Range | undefined;
doRename(document: TextDocument, position: Position, newName: string, stylesheet: Stylesheet): WorkspaceEdit;
getFoldingRanges(document: TextDocument, context?: {
rangeLimit?: number;
}): FoldingRange[];
getSelectionRanges(document: TextDocument, positions: Position[], stylesheet: Stylesheet): SelectionRange[];
format(document: TextDocument, range: Range | undefined, options: CSSFormatConfiguration): TextEdit[];
}
export declare function getDefaultCSSDataProvider(): ICSSDataProvider;
export declare function newCSSDataProvider(data: CSSDataV1): ICSSDataProvider;
export declare function getCSSLanguageService(options?: LanguageServiceOptions): LanguageService;
export declare function getSCSSLanguageService(options?: LanguageServiceOptions): LanguageService;
export declare function getLESSLanguageService(options?: LanguageServiceOptions): LanguageService;

View file

@ -0,0 +1,105 @@
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./parser/cssParser", "./services/cssCompletion", "./services/cssHover", "./services/cssNavigation", "./services/cssCodeActions", "./services/cssValidation", "./parser/scssParser", "./services/scssCompletion", "./parser/lessParser", "./services/lessCompletion", "./services/cssFolding", "./services/cssFormatter", "./languageFacts/dataManager", "./languageFacts/dataProvider", "./services/cssSelectionRange", "./services/scssNavigation", "./data/webCustomData", "./cssLanguageTypes"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLESSLanguageService = exports.getSCSSLanguageService = exports.getCSSLanguageService = exports.newCSSDataProvider = exports.getDefaultCSSDataProvider = void 0;
const cssParser_1 = require("./parser/cssParser");
const cssCompletion_1 = require("./services/cssCompletion");
const cssHover_1 = require("./services/cssHover");
const cssNavigation_1 = require("./services/cssNavigation");
const cssCodeActions_1 = require("./services/cssCodeActions");
const cssValidation_1 = require("./services/cssValidation");
const scssParser_1 = require("./parser/scssParser");
const scssCompletion_1 = require("./services/scssCompletion");
const lessParser_1 = require("./parser/lessParser");
const lessCompletion_1 = require("./services/lessCompletion");
const cssFolding_1 = require("./services/cssFolding");
const cssFormatter_1 = require("./services/cssFormatter");
const dataManager_1 = require("./languageFacts/dataManager");
const dataProvider_1 = require("./languageFacts/dataProvider");
const cssSelectionRange_1 = require("./services/cssSelectionRange");
const scssNavigation_1 = require("./services/scssNavigation");
const webCustomData_1 = require("./data/webCustomData");
__exportStar(require("./cssLanguageTypes"), exports);
function getDefaultCSSDataProvider() {
return newCSSDataProvider(webCustomData_1.cssData);
}
exports.getDefaultCSSDataProvider = getDefaultCSSDataProvider;
function newCSSDataProvider(data) {
return new dataProvider_1.CSSDataProvider(data);
}
exports.newCSSDataProvider = newCSSDataProvider;
function createFacade(parser, completion, hover, navigation, codeActions, validation, cssDataManager) {
return {
configure: (settings) => {
validation.configure(settings);
completion.configure(settings?.completion);
hover.configure(settings?.hover);
},
setDataProviders: cssDataManager.setDataProviders.bind(cssDataManager),
doValidation: validation.doValidation.bind(validation),
parseStylesheet: parser.parseStylesheet.bind(parser),
doComplete: completion.doComplete.bind(completion),
doComplete2: completion.doComplete2.bind(completion),
setCompletionParticipants: completion.setCompletionParticipants.bind(completion),
doHover: hover.doHover.bind(hover),
format: cssFormatter_1.format,
findDefinition: navigation.findDefinition.bind(navigation),
findReferences: navigation.findReferences.bind(navigation),
findDocumentHighlights: navigation.findDocumentHighlights.bind(navigation),
findDocumentLinks: navigation.findDocumentLinks.bind(navigation),
findDocumentLinks2: navigation.findDocumentLinks2.bind(navigation),
findDocumentSymbols: navigation.findSymbolInformations.bind(navigation),
findDocumentSymbols2: navigation.findDocumentSymbols.bind(navigation),
doCodeActions: codeActions.doCodeActions.bind(codeActions),
doCodeActions2: codeActions.doCodeActions2.bind(codeActions),
findDocumentColors: navigation.findDocumentColors.bind(navigation),
getColorPresentations: navigation.getColorPresentations.bind(navigation),
prepareRename: navigation.prepareRename.bind(navigation),
doRename: navigation.doRename.bind(navigation),
getFoldingRanges: cssFolding_1.getFoldingRanges,
getSelectionRanges: cssSelectionRange_1.getSelectionRanges
};
}
const defaultLanguageServiceOptions = {};
function getCSSLanguageService(options = defaultLanguageServiceOptions) {
const cssDataManager = new dataManager_1.CSSDataManager(options);
return createFacade(new cssParser_1.Parser(), new cssCompletion_1.CSSCompletion(null, options, cssDataManager), new cssHover_1.CSSHover(options && options.clientCapabilities, cssDataManager), new cssNavigation_1.CSSNavigation(options && options.fileSystemProvider, false), new cssCodeActions_1.CSSCodeActions(cssDataManager), new cssValidation_1.CSSValidation(cssDataManager), cssDataManager);
}
exports.getCSSLanguageService = getCSSLanguageService;
function getSCSSLanguageService(options = defaultLanguageServiceOptions) {
const cssDataManager = new dataManager_1.CSSDataManager(options);
return createFacade(new scssParser_1.SCSSParser(), new scssCompletion_1.SCSSCompletion(options, cssDataManager), new cssHover_1.CSSHover(options && options.clientCapabilities, cssDataManager), new scssNavigation_1.SCSSNavigation(options && options.fileSystemProvider), new cssCodeActions_1.CSSCodeActions(cssDataManager), new cssValidation_1.CSSValidation(cssDataManager), cssDataManager);
}
exports.getSCSSLanguageService = getSCSSLanguageService;
function getLESSLanguageService(options = defaultLanguageServiceOptions) {
const cssDataManager = new dataManager_1.CSSDataManager(options);
return createFacade(new lessParser_1.LESSParser(), new lessCompletion_1.LESSCompletion(options, cssDataManager), new cssHover_1.CSSHover(options && options.clientCapabilities, cssDataManager), new cssNavigation_1.CSSNavigation(options && options.fileSystemProvider, true), new cssCodeActions_1.CSSCodeActions(cssDataManager), new cssValidation_1.CSSValidation(cssDataManager), cssDataManager);
}
exports.getLESSLanguageService = getLESSLanguageService;
});

View file

@ -0,0 +1,238 @@
import { Range, Position, DocumentUri, MarkupContent, MarkupKind, Color, ColorInformation, ColorPresentation, FoldingRange, FoldingRangeKind, SelectionRange, Diagnostic, DiagnosticSeverity, CompletionItem, CompletionItemKind, CompletionList, CompletionItemTag, InsertTextFormat, DefinitionLink, SymbolInformation, SymbolKind, DocumentSymbol, Location, Hover, MarkedString, CodeActionContext, Command, CodeAction, DocumentHighlight, DocumentLink, WorkspaceEdit, TextEdit, CodeActionKind, TextDocumentEdit, VersionedTextDocumentIdentifier, DocumentHighlightKind } from 'vscode-languageserver-types';
import { TextDocument } from 'vscode-languageserver-textdocument';
export { TextDocument, Range, Position, DocumentUri, MarkupContent, MarkupKind, Color, ColorInformation, ColorPresentation, FoldingRange, FoldingRangeKind, SelectionRange, Diagnostic, DiagnosticSeverity, CompletionItem, CompletionItemKind, CompletionList, CompletionItemTag, InsertTextFormat, DefinitionLink, SymbolInformation, SymbolKind, DocumentSymbol, Location, Hover, MarkedString, CodeActionContext, Command, CodeAction, DocumentHighlight, DocumentLink, WorkspaceEdit, TextEdit, CodeActionKind, TextDocumentEdit, VersionedTextDocumentIdentifier, DocumentHighlightKind };
export type LintSettings = {
[key: string]: any;
};
export interface CompletionSettings {
triggerPropertyValueCompletion: boolean;
completePropertyWithSemicolon?: boolean;
}
export interface LanguageSettings {
validate?: boolean;
lint?: LintSettings;
completion?: CompletionSettings;
hover?: HoverSettings;
}
export interface HoverSettings {
documentation?: boolean;
references?: boolean;
}
export interface PropertyCompletionContext {
propertyName: string;
range: Range;
}
export interface PropertyValueCompletionContext {
propertyName: string;
propertyValue?: string;
range: Range;
}
export interface URILiteralCompletionContext {
uriValue: string;
position: Position;
range: Range;
}
export interface ImportPathCompletionContext {
pathValue: string;
position: Position;
range: Range;
}
export interface MixinReferenceCompletionContext {
mixinName: string;
range: Range;
}
export interface ICompletionParticipant {
onCssProperty?: (context: PropertyCompletionContext) => void;
onCssPropertyValue?: (context: PropertyValueCompletionContext) => void;
onCssURILiteralValue?: (context: URILiteralCompletionContext) => void;
onCssImportPath?: (context: ImportPathCompletionContext) => void;
onCssMixinReference?: (context: MixinReferenceCompletionContext) => void;
}
export interface DocumentContext {
resolveReference(ref: string, baseUrl: string): string | undefined;
}
/**
* Describes what LSP capabilities the client supports
*/
export interface ClientCapabilities {
/**
* The text document client capabilities
*/
textDocument?: {
/**
* Capabilities specific to completions.
*/
completion?: {
/**
* The client supports the following `CompletionItem` specific
* capabilities.
*/
completionItem?: {
/**
* Client supports the follow content formats for the documentation
* property. The order describes the preferred format of the client.
*/
documentationFormat?: MarkupKind[];
};
};
/**
* Capabilities specific to hovers.
*/
hover?: {
/**
* Client supports the follow content formats for the content
* property. The order describes the preferred format of the client.
*/
contentFormat?: MarkupKind[];
};
};
}
export declare namespace ClientCapabilities {
const LATEST: ClientCapabilities;
}
export interface LanguageServiceOptions {
/**
* Unless set to false, the default CSS data provider will be used
* along with the providers from customDataProviders.
* Defaults to true.
*/
useDefaultDataProvider?: boolean;
/**
* Provide data that could enhance the service's understanding of
* CSS property / at-rule / pseudo-class / pseudo-element
*/
customDataProviders?: ICSSDataProvider[];
/**
* Abstract file system access away from the service.
* Used for dynamic link resolving, path completion, etc.
*/
fileSystemProvider?: FileSystemProvider;
/**
* Describes the LSP capabilities the client supports.
*/
clientCapabilities?: ClientCapabilities;
}
export type EntryStatus = 'standard' | 'experimental' | 'nonstandard' | 'obsolete';
export interface IReference {
name: string;
url: string;
}
export interface IPropertyData {
name: string;
description?: string | MarkupContent;
browsers?: string[];
restrictions?: string[];
status?: EntryStatus;
syntax?: string;
values?: IValueData[];
references?: IReference[];
relevance?: number;
}
export interface IAtDirectiveData {
name: string;
description?: string | MarkupContent;
browsers?: string[];
status?: EntryStatus;
references?: IReference[];
}
export interface IPseudoClassData {
name: string;
description?: string | MarkupContent;
browsers?: string[];
status?: EntryStatus;
references?: IReference[];
}
export interface IPseudoElementData {
name: string;
description?: string | MarkupContent;
browsers?: string[];
status?: EntryStatus;
references?: IReference[];
}
export interface IValueData {
name: string;
description?: string | MarkupContent;
browsers?: string[];
status?: EntryStatus;
references?: IReference[];
}
export interface CSSDataV1 {
version: 1 | 1.1;
properties?: IPropertyData[];
atDirectives?: IAtDirectiveData[];
pseudoClasses?: IPseudoClassData[];
pseudoElements?: IPseudoElementData[];
}
export interface ICSSDataProvider {
provideProperties(): IPropertyData[];
provideAtDirectives(): IAtDirectiveData[];
providePseudoClasses(): IPseudoClassData[];
providePseudoElements(): IPseudoElementData[];
}
export declare enum FileType {
/**
* The file type is unknown.
*/
Unknown = 0,
/**
* A regular file.
*/
File = 1,
/**
* A directory.
*/
Directory = 2,
/**
* A symbolic link to a file.
*/
SymbolicLink = 64
}
export interface FileStat {
/**
* The type of the file, e.g. is a regular file, a directory, or symbolic link
* to a file.
*/
type: FileType;
/**
* The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
*/
ctime: number;
/**
* The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
*/
mtime: number;
/**
* The size in bytes.
*/
size: number;
}
export interface FileSystemProvider {
stat(uri: DocumentUri): Promise<FileStat>;
readDirectory?(uri: DocumentUri): Promise<[string, FileType][]>;
}
export interface CSSFormatConfiguration {
/** indentation size. Default: 4 */
tabSize?: number;
/** Whether to use spaces or tabs */
insertSpaces?: boolean;
/** end with a newline: Default: false */
insertFinalNewline?: boolean;
/** separate selectors with newline (e.g. "a,\nbr" or "a, br"): Default: true */
newlineBetweenSelectors?: boolean;
/** add a new line after every css rule: Default: true */
newlineBetweenRules?: boolean;
/** ensure space around selector separators: '>', '+', '~' (e.g. "a>b" -> "a > b"): Default: false */
spaceAroundSelectorSeparator?: boolean;
/** put braces on the same line as rules (`collapse`), or put braces on own line, Allman / ANSI style (`expand`). Default `collapse` */
braceStyle?: 'collapse' | 'expand';
/** whether existing line breaks before elements should be preserved. Default: true */
preserveNewLines?: boolean;
/** maximum number of line breaks to be preserved in one chunk. Default: unlimited */
maxPreserveNewLines?: number;
/** maximum amount of characters per line (0/undefined = disabled). Default: disabled. */
wrapLineLength?: number;
/** add indenting whitespace to empty lines. Default: false */
indentEmptyLines?: boolean;
/** @deprecated Use newlineBetweenSelectors instead*/
selectorSeparatorNewline?: boolean;
}

View file

@ -0,0 +1,89 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "vscode-languageserver-types", "vscode-languageserver-textdocument"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileType = exports.ClientCapabilities = exports.DocumentHighlightKind = exports.VersionedTextDocumentIdentifier = exports.TextDocumentEdit = exports.CodeActionKind = exports.TextEdit = exports.WorkspaceEdit = exports.DocumentLink = exports.DocumentHighlight = exports.CodeAction = exports.Command = exports.CodeActionContext = exports.MarkedString = exports.Hover = exports.Location = exports.DocumentSymbol = exports.SymbolKind = exports.SymbolInformation = exports.InsertTextFormat = exports.CompletionItemTag = exports.CompletionList = exports.CompletionItemKind = exports.CompletionItem = exports.DiagnosticSeverity = exports.Diagnostic = exports.SelectionRange = exports.FoldingRangeKind = exports.FoldingRange = exports.ColorPresentation = exports.ColorInformation = exports.Color = exports.MarkupKind = exports.MarkupContent = exports.DocumentUri = exports.Position = exports.Range = exports.TextDocument = void 0;
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
Object.defineProperty(exports, "Range", { enumerable: true, get: function () { return vscode_languageserver_types_1.Range; } });
Object.defineProperty(exports, "Position", { enumerable: true, get: function () { return vscode_languageserver_types_1.Position; } });
Object.defineProperty(exports, "DocumentUri", { enumerable: true, get: function () { return vscode_languageserver_types_1.DocumentUri; } });
Object.defineProperty(exports, "MarkupContent", { enumerable: true, get: function () { return vscode_languageserver_types_1.MarkupContent; } });
Object.defineProperty(exports, "MarkupKind", { enumerable: true, get: function () { return vscode_languageserver_types_1.MarkupKind; } });
Object.defineProperty(exports, "Color", { enumerable: true, get: function () { return vscode_languageserver_types_1.Color; } });
Object.defineProperty(exports, "ColorInformation", { enumerable: true, get: function () { return vscode_languageserver_types_1.ColorInformation; } });
Object.defineProperty(exports, "ColorPresentation", { enumerable: true, get: function () { return vscode_languageserver_types_1.ColorPresentation; } });
Object.defineProperty(exports, "FoldingRange", { enumerable: true, get: function () { return vscode_languageserver_types_1.FoldingRange; } });
Object.defineProperty(exports, "FoldingRangeKind", { enumerable: true, get: function () { return vscode_languageserver_types_1.FoldingRangeKind; } });
Object.defineProperty(exports, "SelectionRange", { enumerable: true, get: function () { return vscode_languageserver_types_1.SelectionRange; } });
Object.defineProperty(exports, "Diagnostic", { enumerable: true, get: function () { return vscode_languageserver_types_1.Diagnostic; } });
Object.defineProperty(exports, "DiagnosticSeverity", { enumerable: true, get: function () { return vscode_languageserver_types_1.DiagnosticSeverity; } });
Object.defineProperty(exports, "CompletionItem", { enumerable: true, get: function () { return vscode_languageserver_types_1.CompletionItem; } });
Object.defineProperty(exports, "CompletionItemKind", { enumerable: true, get: function () { return vscode_languageserver_types_1.CompletionItemKind; } });
Object.defineProperty(exports, "CompletionList", { enumerable: true, get: function () { return vscode_languageserver_types_1.CompletionList; } });
Object.defineProperty(exports, "CompletionItemTag", { enumerable: true, get: function () { return vscode_languageserver_types_1.CompletionItemTag; } });
Object.defineProperty(exports, "InsertTextFormat", { enumerable: true, get: function () { return vscode_languageserver_types_1.InsertTextFormat; } });
Object.defineProperty(exports, "SymbolInformation", { enumerable: true, get: function () { return vscode_languageserver_types_1.SymbolInformation; } });
Object.defineProperty(exports, "SymbolKind", { enumerable: true, get: function () { return vscode_languageserver_types_1.SymbolKind; } });
Object.defineProperty(exports, "DocumentSymbol", { enumerable: true, get: function () { return vscode_languageserver_types_1.DocumentSymbol; } });
Object.defineProperty(exports, "Location", { enumerable: true, get: function () { return vscode_languageserver_types_1.Location; } });
Object.defineProperty(exports, "Hover", { enumerable: true, get: function () { return vscode_languageserver_types_1.Hover; } });
Object.defineProperty(exports, "MarkedString", { enumerable: true, get: function () { return vscode_languageserver_types_1.MarkedString; } });
Object.defineProperty(exports, "CodeActionContext", { enumerable: true, get: function () { return vscode_languageserver_types_1.CodeActionContext; } });
Object.defineProperty(exports, "Command", { enumerable: true, get: function () { return vscode_languageserver_types_1.Command; } });
Object.defineProperty(exports, "CodeAction", { enumerable: true, get: function () { return vscode_languageserver_types_1.CodeAction; } });
Object.defineProperty(exports, "DocumentHighlight", { enumerable: true, get: function () { return vscode_languageserver_types_1.DocumentHighlight; } });
Object.defineProperty(exports, "DocumentLink", { enumerable: true, get: function () { return vscode_languageserver_types_1.DocumentLink; } });
Object.defineProperty(exports, "WorkspaceEdit", { enumerable: true, get: function () { return vscode_languageserver_types_1.WorkspaceEdit; } });
Object.defineProperty(exports, "TextEdit", { enumerable: true, get: function () { return vscode_languageserver_types_1.TextEdit; } });
Object.defineProperty(exports, "CodeActionKind", { enumerable: true, get: function () { return vscode_languageserver_types_1.CodeActionKind; } });
Object.defineProperty(exports, "TextDocumentEdit", { enumerable: true, get: function () { return vscode_languageserver_types_1.TextDocumentEdit; } });
Object.defineProperty(exports, "VersionedTextDocumentIdentifier", { enumerable: true, get: function () { return vscode_languageserver_types_1.VersionedTextDocumentIdentifier; } });
Object.defineProperty(exports, "DocumentHighlightKind", { enumerable: true, get: function () { return vscode_languageserver_types_1.DocumentHighlightKind; } });
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
Object.defineProperty(exports, "TextDocument", { enumerable: true, get: function () { return vscode_languageserver_textdocument_1.TextDocument; } });
var ClientCapabilities;
(function (ClientCapabilities) {
ClientCapabilities.LATEST = {
textDocument: {
completion: {
completionItem: {
documentationFormat: [vscode_languageserver_types_1.MarkupKind.Markdown, vscode_languageserver_types_1.MarkupKind.PlainText]
}
},
hover: {
contentFormat: [vscode_languageserver_types_1.MarkupKind.Markdown, vscode_languageserver_types_1.MarkupKind.PlainText]
}
}
};
})(ClientCapabilities = exports.ClientCapabilities || (exports.ClientCapabilities = {}));
var FileType;
(function (FileType) {
/**
* The file type is unknown.
*/
FileType[FileType["Unknown"] = 0] = "Unknown";
/**
* A regular file.
*/
FileType[FileType["File"] = 1] = "File";
/**
* A directory.
*/
FileType[FileType["Directory"] = 2] = "Directory";
/**
* A symbolic link to a file.
*/
FileType[FileType["SymbolicLink"] = 64] = "SymbolicLink";
})(FileType = exports.FileType || (exports.FileType = {}));
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,154 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.pageBoxDirectives = exports.svgElements = exports.html5Tags = exports.units = exports.basicShapeFunctions = exports.transitionTimingFunctions = exports.imageFunctions = exports.cssWideFunctions = exports.cssWideKeywords = exports.geometryBoxKeywords = exports.boxKeywords = exports.lineWidthKeywords = exports.lineStyleKeywords = exports.repeatStyleKeywords = exports.positionKeywords = void 0;
exports.positionKeywords = {
'bottom': 'Computes to 100% for the vertical position if one or two values are given, otherwise specifies the bottom edge as the origin for the next offset.',
'center': 'Computes to 50% (left 50%) for the horizontal position if the horizontal position is not otherwise specified, or 50% (top 50%) for the vertical position if it is.',
'left': 'Computes to 0% for the horizontal position if one or two values are given, otherwise specifies the left edge as the origin for the next offset.',
'right': 'Computes to 100% for the horizontal position if one or two values are given, otherwise specifies the right edge as the origin for the next offset.',
'top': 'Computes to 0% for the vertical position if one or two values are given, otherwise specifies the top edge as the origin for the next offset.'
};
exports.repeatStyleKeywords = {
'no-repeat': 'Placed once and not repeated in this direction.',
'repeat': 'Repeated in this direction as often as needed to cover the background painting area.',
'repeat-x': 'Computes to repeat no-repeat.',
'repeat-y': 'Computes to no-repeat repeat.',
'round': 'Repeated as often as will fit within the background positioning area. If it doesnt fit a whole number of times, it is rescaled so that it does.',
'space': 'Repeated as often as will fit within the background positioning area without being clipped and then the images are spaced out to fill the area.'
};
exports.lineStyleKeywords = {
'dashed': 'A series of square-ended dashes.',
'dotted': 'A series of round dots.',
'double': 'Two parallel solid lines with some space between them.',
'groove': 'Looks as if it were carved in the canvas.',
'hidden': 'Same as none, but has different behavior in the border conflict resolution rules for border-collapsed tables.',
'inset': 'Looks as if the content on the inside of the border is sunken into the canvas.',
'none': 'No border. Color and width are ignored.',
'outset': 'Looks as if the content on the inside of the border is coming out of the canvas.',
'ridge': 'Looks as if it were coming out of the canvas.',
'solid': 'A single line segment.'
};
exports.lineWidthKeywords = ['medium', 'thick', 'thin'];
exports.boxKeywords = {
'border-box': 'The background is painted within (clipped to) the border box.',
'content-box': 'The background is painted within (clipped to) the content box.',
'padding-box': 'The background is painted within (clipped to) the padding box.'
};
exports.geometryBoxKeywords = {
'margin-box': 'Uses the margin box as reference box.',
'fill-box': 'Uses the object bounding box as reference box.',
'stroke-box': 'Uses the stroke bounding box as reference box.',
'view-box': 'Uses the nearest SVG viewport as reference box.'
};
exports.cssWideKeywords = {
'initial': 'Represents the value specified as the propertys initial value.',
'inherit': 'Represents the computed value of the property on the elements parent.',
'unset': 'Acts as either `inherit` or `initial`, depending on whether the property is inherited or not.'
};
exports.cssWideFunctions = {
'var()': 'Evaluates the value of a custom variable.',
'calc()': 'Evaluates an mathematical expression. The following operators can be used: + - * /.'
};
exports.imageFunctions = {
'url()': 'Reference an image file by URL',
'image()': 'Provide image fallbacks and annotations.',
'-webkit-image-set()': 'Provide multiple resolutions. Remember to use unprefixed image-set() in addition.',
'image-set()': 'Provide multiple resolutions of an image and const the UA decide which is most appropriate in a given situation.',
'-moz-element()': 'Use an element in the document as an image. Remember to use unprefixed element() in addition.',
'element()': 'Use an element in the document as an image.',
'cross-fade()': 'Indicates the two images to be combined and how far along in the transition the combination is.',
'-webkit-gradient()': 'Deprecated. Use modern linear-gradient() or radial-gradient() instead.',
'-webkit-linear-gradient()': 'Linear gradient. Remember to use unprefixed version in addition.',
'-moz-linear-gradient()': 'Linear gradient. Remember to use unprefixed version in addition.',
'-o-linear-gradient()': 'Linear gradient. Remember to use unprefixed version in addition.',
'linear-gradient()': 'A linear gradient is created by specifying a straight gradient line, and then several colors placed along that line.',
'-webkit-repeating-linear-gradient()': 'Repeating Linear gradient. Remember to use unprefixed version in addition.',
'-moz-repeating-linear-gradient()': 'Repeating Linear gradient. Remember to use unprefixed version in addition.',
'-o-repeating-linear-gradient()': 'Repeating Linear gradient. Remember to use unprefixed version in addition.',
'repeating-linear-gradient()': 'Same as linear-gradient, except the color-stops are repeated infinitely in both directions, with their positions shifted by multiples of the difference between the last specified color-stops position and the first specified color-stops position.',
'-webkit-radial-gradient()': 'Radial gradient. Remember to use unprefixed version in addition.',
'-moz-radial-gradient()': 'Radial gradient. Remember to use unprefixed version in addition.',
'radial-gradient()': 'Colors emerge from a single point and smoothly spread outward in a circular or elliptical shape.',
'-webkit-repeating-radial-gradient()': 'Repeating radial gradient. Remember to use unprefixed version in addition.',
'-moz-repeating-radial-gradient()': 'Repeating radial gradient. Remember to use unprefixed version in addition.',
'repeating-radial-gradient()': 'Same as radial-gradient, except the color-stops are repeated infinitely in both directions, with their positions shifted by multiples of the difference between the last specified color-stops position and the first specified color-stops position.'
};
exports.transitionTimingFunctions = {
'ease': 'Equivalent to cubic-bezier(0.25, 0.1, 0.25, 1.0).',
'ease-in': 'Equivalent to cubic-bezier(0.42, 0, 1.0, 1.0).',
'ease-in-out': 'Equivalent to cubic-bezier(0.42, 0, 0.58, 1.0).',
'ease-out': 'Equivalent to cubic-bezier(0, 0, 0.58, 1.0).',
'linear': 'Equivalent to cubic-bezier(0.0, 0.0, 1.0, 1.0).',
'step-end': 'Equivalent to steps(1, end).',
'step-start': 'Equivalent to steps(1, start).',
'steps()': 'The first parameter specifies the number of intervals in the function. The second parameter, which is optional, is either the value “start” or “end”.',
'cubic-bezier()': 'Specifies a cubic-bezier curve. The four values specify points P1 and P2 of the curve as (x1, y1, x2, y2).',
'cubic-bezier(0.6, -0.28, 0.735, 0.045)': 'Ease-in Back. Overshoots.',
'cubic-bezier(0.68, -0.55, 0.265, 1.55)': 'Ease-in-out Back. Overshoots.',
'cubic-bezier(0.175, 0.885, 0.32, 1.275)': 'Ease-out Back. Overshoots.',
'cubic-bezier(0.6, 0.04, 0.98, 0.335)': 'Ease-in Circular. Based on half circle.',
'cubic-bezier(0.785, 0.135, 0.15, 0.86)': 'Ease-in-out Circular. Based on half circle.',
'cubic-bezier(0.075, 0.82, 0.165, 1)': 'Ease-out Circular. Based on half circle.',
'cubic-bezier(0.55, 0.055, 0.675, 0.19)': 'Ease-in Cubic. Based on power of three.',
'cubic-bezier(0.645, 0.045, 0.355, 1)': 'Ease-in-out Cubic. Based on power of three.',
'cubic-bezier(0.215, 0.610, 0.355, 1)': 'Ease-out Cubic. Based on power of three.',
'cubic-bezier(0.95, 0.05, 0.795, 0.035)': 'Ease-in Exponential. Based on two to the power ten.',
'cubic-bezier(1, 0, 0, 1)': 'Ease-in-out Exponential. Based on two to the power ten.',
'cubic-bezier(0.19, 1, 0.22, 1)': 'Ease-out Exponential. Based on two to the power ten.',
'cubic-bezier(0.47, 0, 0.745, 0.715)': 'Ease-in Sine.',
'cubic-bezier(0.445, 0.05, 0.55, 0.95)': 'Ease-in-out Sine.',
'cubic-bezier(0.39, 0.575, 0.565, 1)': 'Ease-out Sine.',
'cubic-bezier(0.55, 0.085, 0.68, 0.53)': 'Ease-in Quadratic. Based on power of two.',
'cubic-bezier(0.455, 0.03, 0.515, 0.955)': 'Ease-in-out Quadratic. Based on power of two.',
'cubic-bezier(0.25, 0.46, 0.45, 0.94)': 'Ease-out Quadratic. Based on power of two.',
'cubic-bezier(0.895, 0.03, 0.685, 0.22)': 'Ease-in Quartic. Based on power of four.',
'cubic-bezier(0.77, 0, 0.175, 1)': 'Ease-in-out Quartic. Based on power of four.',
'cubic-bezier(0.165, 0.84, 0.44, 1)': 'Ease-out Quartic. Based on power of four.',
'cubic-bezier(0.755, 0.05, 0.855, 0.06)': 'Ease-in Quintic. Based on power of five.',
'cubic-bezier(0.86, 0, 0.07, 1)': 'Ease-in-out Quintic. Based on power of five.',
'cubic-bezier(0.23, 1, 0.320, 1)': 'Ease-out Quintic. Based on power of five.'
};
exports.basicShapeFunctions = {
'circle()': 'Defines a circle.',
'ellipse()': 'Defines an ellipse.',
'inset()': 'Defines an inset rectangle.',
'polygon()': 'Defines a polygon.'
};
exports.units = {
'length': ['cap', 'ch', 'cm', 'cqb', 'cqh', 'cqi', 'cqmax', 'cqmin', 'cqw', 'dvb', 'dvh', 'dvi', 'dvw', 'em', 'ex', 'ic', 'in', 'lh', 'lvb', 'lvh', 'lvi', 'lvw', 'mm', 'pc', 'pt', 'px', 'q', 'rcap', 'rch', 'rem', 'rex', 'ric', 'rlh', 'svb', 'svh', 'svi', 'svw', 'vb', 'vh', 'vi', 'vmax', 'vmin', 'vw'],
'angle': ['deg', 'rad', 'grad', 'turn'],
'time': ['ms', 's'],
'frequency': ['Hz', 'kHz'],
'resolution': ['dpi', 'dpcm', 'dppx'],
'percentage': ['%', 'fr']
};
exports.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', 'picture', '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', 'const', 'video', 'wbr'];
exports.svgElements = ['circle', 'clipPath', 'cursor', 'defs', 'desc', 'ellipse', 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting',
'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology',
'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence', 'filter', 'foreignObject', 'g', 'hatch', 'hatchpath', 'image', 'line', 'linearGradient',
'marker', 'mask', 'mesh', 'meshpatch', 'meshrow', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialGradient', 'rect', 'set', 'solidcolor', 'stop', 'svg', 'switch',
'symbol', 'text', 'textPath', 'tspan', 'use', 'view'];
exports.pageBoxDirectives = [
'@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'
];
});

View file

@ -0,0 +1,614 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../parser/cssNodes", "@vscode/l10n"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getColorValue = exports.hwbFromColor = exports.colorFromHWB = exports.hslFromColor = exports.colorFromHSL = exports.colorFrom256RGB = exports.colorFromHex = exports.hexDigit = exports.isColorValue = exports.isColorString = exports.isColorConstructor = exports.colorKeywords = exports.colors = exports.colorFunctions = void 0;
const nodes = require("../parser/cssNodes");
const l10n = require("@vscode/l10n");
const hexColorRegExp = /(^#([0-9A-F]{3}){1,2}$)|(^#([0-9A-F]{4}){1,2}$)/i;
exports.colorFunctions = [
{
label: 'rgb',
func: 'rgb($red, $green, $blue)',
insertText: 'rgb(${1:red}, ${2:green}, ${3:blue})',
desc: l10n.t('Creates a Color from red, green, and blue values.')
},
{
label: 'rgba',
func: 'rgba($red, $green, $blue, $alpha)',
insertText: 'rgba(${1:red}, ${2:green}, ${3:blue}, ${4:alpha})',
desc: l10n.t('Creates a Color from red, green, blue, and alpha values.')
},
{
label: 'rgb relative',
func: 'rgb(from $color $red $green $blue)',
insertText: 'rgb(from ${1:color} ${2:r} ${3:g} ${4:b})',
desc: l10n.t('Creates a Color from the red, green, and blue values of another Color.')
},
{
label: 'hsl',
func: 'hsl($hue, $saturation, $lightness)',
insertText: 'hsl(${1:hue}, ${2:saturation}, ${3:lightness})',
desc: l10n.t('Creates a Color from hue, saturation, and lightness values.')
},
{
label: 'hsla',
func: 'hsla($hue, $saturation, $lightness, $alpha)',
insertText: 'hsla(${1:hue}, ${2:saturation}, ${3:lightness}, ${4:alpha})',
desc: l10n.t('Creates a Color from hue, saturation, lightness, and alpha values.')
},
{
label: 'hsl relative',
func: 'hsl(from $color $hue $saturation $lightness)',
insertText: 'hsl(from ${1:color} ${2:h} ${3:s} ${4:l})',
desc: l10n.t('Creates a Color from the hue, saturation, and lightness values of another Color.')
},
{
label: 'hwb',
func: 'hwb($hue $white $black)',
insertText: 'hwb(${1:hue} ${2:white} ${3:black})',
desc: l10n.t('Creates a Color from hue, white, and black values.')
},
{
label: 'hwb relative',
func: 'hwb(from $color $hue $white $black)',
insertText: 'hwb(from ${1:color} ${2:h} ${3:w} ${4:b})',
desc: l10n.t('Creates a Color from the hue, white, and black values of another Color.')
},
{
label: 'lab',
func: 'lab($lightness $a $b)',
insertText: 'lab(${1:lightness} ${2:a} ${3:b})',
desc: l10n.t('Creates a Color from lightness, a, and b values.')
},
{
label: 'lab relative',
func: 'lab(from $color $lightness $a $b)',
insertText: 'lab(from ${1:color} ${2:l} ${3:a} ${4:b})',
desc: l10n.t('Creates a Color from the lightness, a, and b values of another Color.')
},
{
label: 'oklab',
func: 'oklab($lightness $a $b)',
insertText: 'oklab(${1:lightness} ${2:a} ${3:b})',
desc: l10n.t('Creates a Color from lightness, a, and b values.')
},
{
label: 'oklab relative',
func: 'oklab(from $color $lightness $a $b)',
insertText: 'oklab(from ${1:color} ${2:l} ${3:a} ${4:b})',
desc: l10n.t('Creates a Color from the lightness, a, and b values of another Color.')
},
{
label: 'lch',
func: 'lch($lightness $chroma $hue)',
insertText: 'lch(${1:lightness} ${2:chroma} ${3:hue})',
desc: l10n.t('Creates a Color from lightness, chroma, and hue values.')
},
{
label: 'lch relative',
func: 'lch(from $color $lightness $chroma $hue)',
insertText: 'lch(from ${1:color} ${2:l} ${3:c} ${4:h})',
desc: l10n.t('Creates a Color from the lightness, chroma, and hue values of another Color.')
},
{
label: 'oklch',
func: 'oklch($lightness $chroma $hue)',
insertText: 'oklch(${1:lightness} ${2:chroma} ${3:hue})',
desc: l10n.t('Creates a Color from lightness, chroma, and hue values.')
},
{
label: 'oklch relative',
func: 'oklch(from $color $lightness $chroma $hue)',
insertText: 'oklch(from ${1:color} ${2:l} ${3:c} ${4:h})',
desc: l10n.t('Creates a Color from the lightness, chroma, and hue values of another Color.')
},
{
label: 'color',
func: 'color($color-space $red $green $blue)',
insertText: 'color(${1|srgb,srgb-linear,display-p3,a98-rgb,prophoto-rgb,rec2020,xyx,xyz-d50,xyz-d65|} ${2:red} ${3:green} ${4:blue})',
desc: l10n.t('Creates a Color in a specific color space from red, green, and blue values.')
},
{
label: 'color relative',
func: 'color(from $color $color-space $red $green $blue)',
insertText: 'color(from ${1:color} ${2|srgb,srgb-linear,display-p3,a98-rgb,prophoto-rgb,rec2020,xyx,xyz-d50,xyz-d65|} ${3:r} ${4:g} ${5:b})',
desc: l10n.t('Creates a Color in a specific color space from the red, green, and blue values of another Color.')
},
{
label: 'color-mix',
func: 'color-mix(in $color-space, $color $percentage, $color $percentage)',
insertText: 'color-mix(in ${1|srgb,srgb-linear,lab,oklab,xyz,xyz-d50,xyz-d65|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})',
desc: l10n.t('Mix two colors together in a rectangular color space.')
},
{
label: 'color-mix hue',
func: 'color-mix(in $color-space $interpolation-method hue, $color $percentage, $color $percentage)',
insertText: 'color-mix(in ${1|hsl,hwb,lch,oklch|} ${2|shorter hue,longer hue,increasing hue,decreasing hue|}, ${3:color} ${4:percentage}, ${5:color} ${6:percentage})',
desc: l10n.t('Mix two colors together in a polar color space.')
},
];
const colorFunctionNameRegExp = /^(rgb|rgba|hsl|hsla|hwb)$/i;
exports.colors = {
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgrey: '#a9a9a9',
darkgreen: '#006400',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkslategrey: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
grey: '#808080',
green: '#008000',
greenyellow: '#adff2f',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgrey: '#d3d3d3',
lightgreen: '#90ee90',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370d8',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#d87093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
red: '#ff0000',
rebeccapurple: '#663399',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
slategrey: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd32'
};
const colorsRegExp = new RegExp(`^(${Object.keys(exports.colors).join('|')})$`, "i");
exports.colorKeywords = {
'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.',
'transparent': 'Fully transparent. This keyword can be considered a shorthand for rgba(0,0,0,0) which is its computed value.',
};
const colorKeywordsRegExp = new RegExp(`^(${Object.keys(exports.colorKeywords).join('|')})$`, "i");
function getNumericValue(node, factor) {
const val = node.getText();
const m = val.match(/^([-+]?[0-9]*\.?[0-9]+)(%?)$/);
if (m) {
if (m[2]) {
factor = 100.0;
}
const result = parseFloat(m[1]) / factor;
if (result >= 0 && result <= 1) {
return result;
}
}
throw new Error();
}
function getAngle(node) {
const val = node.getText();
const m = val.match(/^([-+]?[0-9]*\.?[0-9]+)(deg|rad|grad|turn)?$/);
if (m) {
switch (m[2]) {
case 'deg':
return parseFloat(val) % 360;
case 'rad':
return (parseFloat(val) * 180 / Math.PI) % 360;
case 'grad':
return (parseFloat(val) * 0.9) % 360;
case 'turn':
return (parseFloat(val) * 360) % 360;
default:
if ('undefined' === typeof m[2]) {
return parseFloat(val) % 360;
}
}
}
throw new Error();
}
function isColorConstructor(node) {
const name = node.getName();
if (!name) {
return false;
}
return colorFunctionNameRegExp.test(name);
}
exports.isColorConstructor = isColorConstructor;
function isColorString(s) {
return hexColorRegExp.test(s) || colorsRegExp.test(s) || colorKeywordsRegExp.test(s);
}
exports.isColorString = isColorString;
/**
* Returns true if the node is a color value - either
* defined a hex number, as rgb or rgba function, or
* as color name.
*/
function isColorValue(node) {
if (node.type === nodes.NodeType.HexColorValue) {
return true;
}
else if (node.type === nodes.NodeType.Function) {
return isColorConstructor(node);
}
else if (node.type === nodes.NodeType.Identifier) {
if (node.parent && node.parent.type !== nodes.NodeType.Term) {
return false;
}
const candidateColor = node.getText().toLowerCase();
if (candidateColor === 'none') {
return false;
}
if (exports.colors[candidateColor]) {
return true;
}
}
return false;
}
exports.isColorValue = isColorValue;
const Digit0 = 48;
const Digit9 = 57;
const A = 65;
const F = 70;
const a = 97;
const f = 102;
function hexDigit(charCode) {
if (charCode < Digit0) {
return 0;
}
if (charCode <= Digit9) {
return charCode - Digit0;
}
if (charCode < a) {
charCode += (a - A);
}
if (charCode >= a && charCode <= f) {
return charCode - a + 10;
}
return 0;
}
exports.hexDigit = hexDigit;
function colorFromHex(text) {
if (text[0] !== '#') {
return null;
}
switch (text.length) {
case 4:
return {
red: (hexDigit(text.charCodeAt(1)) * 0x11) / 255.0,
green: (hexDigit(text.charCodeAt(2)) * 0x11) / 255.0,
blue: (hexDigit(text.charCodeAt(3)) * 0x11) / 255.0,
alpha: 1
};
case 5:
return {
red: (hexDigit(text.charCodeAt(1)) * 0x11) / 255.0,
green: (hexDigit(text.charCodeAt(2)) * 0x11) / 255.0,
blue: (hexDigit(text.charCodeAt(3)) * 0x11) / 255.0,
alpha: (hexDigit(text.charCodeAt(4)) * 0x11) / 255.0,
};
case 7:
return {
red: (hexDigit(text.charCodeAt(1)) * 0x10 + hexDigit(text.charCodeAt(2))) / 255.0,
green: (hexDigit(text.charCodeAt(3)) * 0x10 + hexDigit(text.charCodeAt(4))) / 255.0,
blue: (hexDigit(text.charCodeAt(5)) * 0x10 + hexDigit(text.charCodeAt(6))) / 255.0,
alpha: 1
};
case 9:
return {
red: (hexDigit(text.charCodeAt(1)) * 0x10 + hexDigit(text.charCodeAt(2))) / 255.0,
green: (hexDigit(text.charCodeAt(3)) * 0x10 + hexDigit(text.charCodeAt(4))) / 255.0,
blue: (hexDigit(text.charCodeAt(5)) * 0x10 + hexDigit(text.charCodeAt(6))) / 255.0,
alpha: (hexDigit(text.charCodeAt(7)) * 0x10 + hexDigit(text.charCodeAt(8))) / 255.0
};
}
return null;
}
exports.colorFromHex = colorFromHex;
function colorFrom256RGB(red, green, blue, alpha = 1.0) {
return {
red: red / 255.0,
green: green / 255.0,
blue: blue / 255.0,
alpha
};
}
exports.colorFrom256RGB = colorFrom256RGB;
function colorFromHSL(hue, sat, light, alpha = 1.0) {
hue = hue / 60.0;
if (sat === 0) {
return { red: light, green: light, blue: light, alpha };
}
else {
const hueToRgb = (t1, t2, hue) => {
while (hue < 0) {
hue += 6;
}
while (hue >= 6) {
hue -= 6;
}
if (hue < 1) {
return (t2 - t1) * hue + t1;
}
if (hue < 3) {
return t2;
}
if (hue < 4) {
return (t2 - t1) * (4 - hue) + t1;
}
return t1;
};
const t2 = light <= 0.5 ? (light * (sat + 1)) : (light + sat - (light * sat));
const t1 = light * 2 - t2;
return { red: hueToRgb(t1, t2, hue + 2), green: hueToRgb(t1, t2, hue), blue: hueToRgb(t1, t2, hue - 2), alpha };
}
}
exports.colorFromHSL = colorFromHSL;
function hslFromColor(rgba) {
const r = rgba.red;
const g = rgba.green;
const b = rgba.blue;
const a = rgba.alpha;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h = 0;
let s = 0;
const l = (min + max) / 2;
const chroma = max - min;
if (chroma > 0) {
s = Math.min((l <= 0.5 ? chroma / (2 * l) : chroma / (2 - (2 * l))), 1);
switch (max) {
case r:
h = (g - b) / chroma + (g < b ? 6 : 0);
break;
case g:
h = (b - r) / chroma + 2;
break;
case b:
h = (r - g) / chroma + 4;
break;
}
h *= 60;
h = Math.round(h);
}
return { h, s, l, a };
}
exports.hslFromColor = hslFromColor;
function colorFromHWB(hue, white, black, alpha = 1.0) {
if (white + black >= 1) {
const gray = white / (white + black);
return { red: gray, green: gray, blue: gray, alpha };
}
const rgb = colorFromHSL(hue, 1, 0.5, alpha);
let red = rgb.red;
red *= (1 - white - black);
red += white;
let green = rgb.green;
green *= (1 - white - black);
green += white;
let blue = rgb.blue;
blue *= (1 - white - black);
blue += white;
return {
red: red,
green: green,
blue: blue,
alpha
};
}
exports.colorFromHWB = colorFromHWB;
function hwbFromColor(rgba) {
const hsl = hslFromColor(rgba);
const white = Math.min(rgba.red, rgba.green, rgba.blue);
const black = 1 - Math.max(rgba.red, rgba.green, rgba.blue);
return {
h: hsl.h,
w: white,
b: black,
a: hsl.a
};
}
exports.hwbFromColor = hwbFromColor;
function getColorValue(node) {
if (node.type === nodes.NodeType.HexColorValue) {
const text = node.getText();
return colorFromHex(text);
}
else if (node.type === nodes.NodeType.Function) {
const functionNode = node;
const name = functionNode.getName();
let colorValues = functionNode.getArguments().getChildren();
if (colorValues.length === 1) {
const functionArg = colorValues[0].getChildren();
if (functionArg.length === 1 && functionArg[0].type === nodes.NodeType.Expression) {
colorValues = functionArg[0].getChildren();
if (colorValues.length === 3) {
const lastValue = colorValues[2];
if (lastValue instanceof nodes.BinaryExpression) {
const left = lastValue.getLeft(), right = lastValue.getRight(), operator = lastValue.getOperator();
if (left && right && operator && operator.matches('/')) {
colorValues = [colorValues[0], colorValues[1], left, right];
}
}
}
}
}
if (!name || colorValues.length < 3 || colorValues.length > 4) {
return null;
}
try {
const alpha = colorValues.length === 4 ? getNumericValue(colorValues[3], 1) : 1;
if (name === 'rgb' || name === 'rgba') {
return {
red: getNumericValue(colorValues[0], 255.0),
green: getNumericValue(colorValues[1], 255.0),
blue: getNumericValue(colorValues[2], 255.0),
alpha
};
}
else if (name === 'hsl' || name === 'hsla') {
const h = getAngle(colorValues[0]);
const s = getNumericValue(colorValues[1], 100.0);
const l = getNumericValue(colorValues[2], 100.0);
return colorFromHSL(h, s, l, alpha);
}
else if (name === 'hwb') {
const h = getAngle(colorValues[0]);
const w = getNumericValue(colorValues[1], 100.0);
const b = getNumericValue(colorValues[2], 100.0);
return colorFromHWB(h, w, b, alpha);
}
}
catch (e) {
// parse error on numeric value
return null;
}
}
else if (node.type === nodes.NodeType.Identifier) {
if (node.parent && node.parent.type !== nodes.NodeType.Term) {
return null;
}
const term = node.parent;
if (term && term.parent && term.parent.type === nodes.NodeType.BinaryExpression) {
const expression = term.parent;
if (expression.parent && expression.parent.type === nodes.NodeType.ListEntry && expression.parent.key === expression) {
return null;
}
}
const candidateColor = node.getText().toLowerCase();
if (candidateColor === 'none') {
return null;
}
const colorHex = exports.colors[candidateColor];
if (colorHex) {
return colorFromHex(colorHex);
}
}
return null;
}
exports.getColorValue = getColorValue;
});

View file

@ -0,0 +1,101 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../utils/objects", "../data/webCustomData", "./dataProvider"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.CSSDataManager = void 0;
const objects = require("../utils/objects");
const webCustomData_1 = require("../data/webCustomData");
const dataProvider_1 = require("./dataProvider");
class CSSDataManager {
constructor(options) {
this.dataProviders = [];
this._propertySet = {};
this._atDirectiveSet = {};
this._pseudoClassSet = {};
this._pseudoElementSet = {};
this._properties = [];
this._atDirectives = [];
this._pseudoClasses = [];
this._pseudoElements = [];
this.setDataProviders(options?.useDefaultDataProvider !== false, options?.customDataProviders || []);
}
setDataProviders(builtIn, providers) {
this.dataProviders = [];
if (builtIn) {
this.dataProviders.push(new dataProvider_1.CSSDataProvider(webCustomData_1.cssData));
}
this.dataProviders.push(...providers);
this.collectData();
}
/**
* Collect all data & handle duplicates
*/
collectData() {
this._propertySet = {};
this._atDirectiveSet = {};
this._pseudoClassSet = {};
this._pseudoElementSet = {};
this.dataProviders.forEach(provider => {
provider.provideProperties().forEach(p => {
if (!this._propertySet[p.name]) {
this._propertySet[p.name] = p;
}
});
provider.provideAtDirectives().forEach(p => {
if (!this._atDirectiveSet[p.name]) {
this._atDirectiveSet[p.name] = p;
}
});
provider.providePseudoClasses().forEach(p => {
if (!this._pseudoClassSet[p.name]) {
this._pseudoClassSet[p.name] = p;
}
});
provider.providePseudoElements().forEach(p => {
if (!this._pseudoElementSet[p.name]) {
this._pseudoElementSet[p.name] = p;
}
});
});
this._properties = objects.values(this._propertySet);
this._atDirectives = objects.values(this._atDirectiveSet);
this._pseudoClasses = objects.values(this._pseudoClassSet);
this._pseudoElements = objects.values(this._pseudoElementSet);
}
getProperty(name) { return this._propertySet[name]; }
getAtDirective(name) { return this._atDirectiveSet[name]; }
getPseudoClass(name) { return this._pseudoClassSet[name]; }
getPseudoElement(name) { return this._pseudoElementSet[name]; }
getProperties() {
return this._properties;
}
getAtDirectives() {
return this._atDirectives;
}
getPseudoClasses() {
return this._pseudoClasses;
}
getPseudoElements() {
return this._pseudoElements;
}
isKnownProperty(name) {
return name.toLowerCase() in this._propertySet;
}
isStandardProperty(name) {
return this.isKnownProperty(name) &&
(!this._propertySet[name.toLowerCase()].status || this._propertySet[name.toLowerCase()].status === 'standard');
}
}
exports.CSSDataManager = CSSDataManager;
});

View file

@ -0,0 +1,86 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.CSSDataProvider = void 0;
class CSSDataProvider {
/**
* Currently, unversioned data uses the V1 implementation
* In the future when the provider handles multiple versions of HTML custom data,
* use the latest implementation for unversioned data
*/
constructor(data) {
this._properties = [];
this._atDirectives = [];
this._pseudoClasses = [];
this._pseudoElements = [];
this.addData(data);
}
provideProperties() {
return this._properties;
}
provideAtDirectives() {
return this._atDirectives;
}
providePseudoClasses() {
return this._pseudoClasses;
}
providePseudoElements() {
return this._pseudoElements;
}
addData(data) {
if (Array.isArray(data.properties)) {
for (const prop of data.properties) {
if (isPropertyData(prop)) {
this._properties.push(prop);
}
}
}
if (Array.isArray(data.atDirectives)) {
for (const prop of data.atDirectives) {
if (isAtDirective(prop)) {
this._atDirectives.push(prop);
}
}
}
if (Array.isArray(data.pseudoClasses)) {
for (const prop of data.pseudoClasses) {
if (isPseudoClassData(prop)) {
this._pseudoClasses.push(prop);
}
}
}
if (Array.isArray(data.pseudoElements)) {
for (const prop of data.pseudoElements) {
if (isPseudoElementData(prop)) {
this._pseudoElements.push(prop);
}
}
}
}
}
exports.CSSDataProvider = CSSDataProvider;
function isPropertyData(d) {
return typeof d.name === 'string';
}
function isAtDirective(d) {
return typeof d.name === 'string';
}
function isPseudoClassData(d) {
return typeof d.name === 'string';
}
function isPseudoElementData(d) {
return typeof d.name === 'string';
}
});

View file

@ -0,0 +1,152 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../cssLanguageTypes"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.getBrowserLabel = exports.textToMarkedString = exports.getEntryDescription = exports.browserNames = void 0;
const cssLanguageTypes_1 = require("../cssLanguageTypes");
exports.browserNames = {
E: 'Edge',
FF: 'Firefox',
S: 'Safari',
C: 'Chrome',
IE: 'IE',
O: 'Opera'
};
function getEntryStatus(status) {
switch (status) {
case 'experimental':
return '⚠️ Property is experimental. Be cautious when using it.\n\n';
case 'nonstandard':
return '🚨️ Property is nonstandard. Avoid using it.\n\n';
case 'obsolete':
return '🚨️️️ Property is obsolete. Avoid using it.\n\n';
default:
return '';
}
}
function getEntryDescription(entry, doesSupportMarkdown, settings) {
let result;
if (doesSupportMarkdown) {
result = {
kind: 'markdown',
value: getEntryMarkdownDescription(entry, settings)
};
}
else {
result = {
kind: 'plaintext',
value: getEntryStringDescription(entry, settings)
};
}
if (result.value === '') {
return undefined;
}
return result;
}
exports.getEntryDescription = getEntryDescription;
function textToMarkedString(text) {
text = text.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
return text.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
exports.textToMarkedString = textToMarkedString;
function getEntryStringDescription(entry, settings) {
if (!entry.description || entry.description === '') {
return '';
}
if (typeof entry.description !== 'string') {
return entry.description.value;
}
let result = '';
if (settings?.documentation !== false) {
if (entry.status) {
result += getEntryStatus(entry.status);
}
result += entry.description;
const browserLabel = getBrowserLabel(entry.browsers);
if (browserLabel) {
result += '\n(' + browserLabel + ')';
}
if ('syntax' in entry) {
result += `\n\nSyntax: ${entry.syntax}`;
}
}
if (entry.references && entry.references.length > 0 && settings?.references !== false) {
if (result.length > 0) {
result += '\n\n';
}
result += entry.references.map(r => {
return `${r.name}: ${r.url}`;
}).join(' | ');
}
return result;
}
function getEntryMarkdownDescription(entry, settings) {
if (!entry.description || entry.description === '') {
return '';
}
let result = '';
if (settings?.documentation !== false) {
if (entry.status) {
result += getEntryStatus(entry.status);
}
if (typeof entry.description === 'string') {
result += textToMarkedString(entry.description);
}
else {
result += entry.description.kind === cssLanguageTypes_1.MarkupKind.Markdown ? entry.description.value : textToMarkedString(entry.description.value);
}
const browserLabel = getBrowserLabel(entry.browsers);
if (browserLabel) {
result += '\n\n(' + textToMarkedString(browserLabel) + ')';
}
if ('syntax' in entry && entry.syntax) {
result += `\n\nSyntax: ${textToMarkedString(entry.syntax)}`;
}
}
if (entry.references && entry.references.length > 0 && settings?.references !== false) {
if (result.length > 0) {
result += '\n\n';
}
result += entry.references.map(r => {
return `[${r.name}](${r.url})`;
}).join(' | ');
}
return result;
}
/**
* Input is like `["E12","FF49","C47","IE","O"]`
* Output is like `Edge 12, Firefox 49, Chrome 47, IE, Opera`
*/
function getBrowserLabel(browsers = []) {
if (browsers.length === 0) {
return null;
}
return browsers
.map(b => {
let result = '';
const matches = b.match(/([A-Z]+)(\d+)?/);
const name = matches[1];
const version = matches[2];
if (name in exports.browserNames) {
result += exports.browserNames[name];
}
if (version) {
result += ' ' + version;
}
return result;
})
.join(', ');
}
exports.getBrowserLabel = getBrowserLabel;
});

View file

@ -0,0 +1,33 @@
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./entry", "./colors", "./builtinData"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./entry"), exports);
__exportStar(require("./colors"), exports);
__exportStar(require("./builtinData"), exports);
});

View file

@ -0,0 +1,60 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "@vscode/l10n"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.ParseError = exports.CSSIssueType = void 0;
const l10n = require("@vscode/l10n");
class CSSIssueType {
constructor(id, message) {
this.id = id;
this.message = message;
}
}
exports.CSSIssueType = CSSIssueType;
exports.ParseError = {
NumberExpected: new CSSIssueType('css-numberexpected', l10n.t("number expected")),
ConditionExpected: new CSSIssueType('css-conditionexpected', l10n.t("condition expected")),
RuleOrSelectorExpected: new CSSIssueType('css-ruleorselectorexpected', l10n.t("at-rule or selector expected")),
DotExpected: new CSSIssueType('css-dotexpected', l10n.t("dot expected")),
ColonExpected: new CSSIssueType('css-colonexpected', l10n.t("colon expected")),
SemiColonExpected: new CSSIssueType('css-semicolonexpected', l10n.t("semi-colon expected")),
TermExpected: new CSSIssueType('css-termexpected', l10n.t("term expected")),
ExpressionExpected: new CSSIssueType('css-expressionexpected', l10n.t("expression expected")),
OperatorExpected: new CSSIssueType('css-operatorexpected', l10n.t("operator expected")),
IdentifierExpected: new CSSIssueType('css-identifierexpected', l10n.t("identifier expected")),
PercentageExpected: new CSSIssueType('css-percentageexpected', l10n.t("percentage expected")),
URIOrStringExpected: new CSSIssueType('css-uriorstringexpected', l10n.t("uri or string expected")),
URIExpected: new CSSIssueType('css-uriexpected', l10n.t("URI expected")),
VariableNameExpected: new CSSIssueType('css-varnameexpected', l10n.t("variable name expected")),
VariableValueExpected: new CSSIssueType('css-varvalueexpected', l10n.t("variable value expected")),
PropertyValueExpected: new CSSIssueType('css-propertyvalueexpected', l10n.t("property value expected")),
LeftCurlyExpected: new CSSIssueType('css-lcurlyexpected', l10n.t("{ expected")),
RightCurlyExpected: new CSSIssueType('css-rcurlyexpected', l10n.t("} expected")),
LeftSquareBracketExpected: new CSSIssueType('css-rbracketexpected', l10n.t("[ expected")),
RightSquareBracketExpected: new CSSIssueType('css-lbracketexpected', l10n.t("] expected")),
LeftParenthesisExpected: new CSSIssueType('css-lparentexpected', l10n.t("( expected")),
RightParenthesisExpected: new CSSIssueType('css-rparentexpected', l10n.t(") expected")),
CommaExpected: new CSSIssueType('css-commaexpected', l10n.t("comma expected")),
PageDirectiveOrDeclarationExpected: new CSSIssueType('css-pagedirordeclexpected', l10n.t("page directive or declaraton expected")),
UnknownAtRule: new CSSIssueType('css-unknownatrule', l10n.t("at-rule unknown")),
UnknownKeyword: new CSSIssueType('css-unknownkeyword', l10n.t("unknown keyword")),
SelectorExpected: new CSSIssueType('css-selectorexpected', l10n.t("selector expected")),
StringLiteralExpected: new CSSIssueType('css-stringliteralexpected', l10n.t("string literal expected")),
WhitespaceExpected: new CSSIssueType('css-whitespaceexpected', l10n.t("whitespace expected")),
MediaQueryExpected: new CSSIssueType('css-mediaqueryexpected', l10n.t("media query expected")),
IdentifierOrWildcardExpected: new CSSIssueType('css-idorwildcardexpected', l10n.t("identifier or wildcard expected")),
WildcardExpected: new CSSIssueType('css-wildcardexpected', l10n.t("wildcard expected")),
IdentifierOrVariableExpected: new CSSIssueType('css-idorvarexpected', l10n.t("identifier or variable expected")),
};
});

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,606 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.Scanner = exports.MultiLineStream = exports.TokenType = void 0;
var TokenType;
(function (TokenType) {
TokenType[TokenType["Ident"] = 0] = "Ident";
TokenType[TokenType["AtKeyword"] = 1] = "AtKeyword";
TokenType[TokenType["String"] = 2] = "String";
TokenType[TokenType["BadString"] = 3] = "BadString";
TokenType[TokenType["UnquotedString"] = 4] = "UnquotedString";
TokenType[TokenType["Hash"] = 5] = "Hash";
TokenType[TokenType["Num"] = 6] = "Num";
TokenType[TokenType["Percentage"] = 7] = "Percentage";
TokenType[TokenType["Dimension"] = 8] = "Dimension";
TokenType[TokenType["UnicodeRange"] = 9] = "UnicodeRange";
TokenType[TokenType["CDO"] = 10] = "CDO";
TokenType[TokenType["CDC"] = 11] = "CDC";
TokenType[TokenType["Colon"] = 12] = "Colon";
TokenType[TokenType["SemiColon"] = 13] = "SemiColon";
TokenType[TokenType["CurlyL"] = 14] = "CurlyL";
TokenType[TokenType["CurlyR"] = 15] = "CurlyR";
TokenType[TokenType["ParenthesisL"] = 16] = "ParenthesisL";
TokenType[TokenType["ParenthesisR"] = 17] = "ParenthesisR";
TokenType[TokenType["BracketL"] = 18] = "BracketL";
TokenType[TokenType["BracketR"] = 19] = "BracketR";
TokenType[TokenType["Whitespace"] = 20] = "Whitespace";
TokenType[TokenType["Includes"] = 21] = "Includes";
TokenType[TokenType["Dashmatch"] = 22] = "Dashmatch";
TokenType[TokenType["SubstringOperator"] = 23] = "SubstringOperator";
TokenType[TokenType["PrefixOperator"] = 24] = "PrefixOperator";
TokenType[TokenType["SuffixOperator"] = 25] = "SuffixOperator";
TokenType[TokenType["Delim"] = 26] = "Delim";
TokenType[TokenType["EMS"] = 27] = "EMS";
TokenType[TokenType["EXS"] = 28] = "EXS";
TokenType[TokenType["Length"] = 29] = "Length";
TokenType[TokenType["Angle"] = 30] = "Angle";
TokenType[TokenType["Time"] = 31] = "Time";
TokenType[TokenType["Freq"] = 32] = "Freq";
TokenType[TokenType["Exclamation"] = 33] = "Exclamation";
TokenType[TokenType["Resolution"] = 34] = "Resolution";
TokenType[TokenType["Comma"] = 35] = "Comma";
TokenType[TokenType["Charset"] = 36] = "Charset";
TokenType[TokenType["EscapedJavaScript"] = 37] = "EscapedJavaScript";
TokenType[TokenType["BadEscapedJavaScript"] = 38] = "BadEscapedJavaScript";
TokenType[TokenType["Comment"] = 39] = "Comment";
TokenType[TokenType["SingleLineComment"] = 40] = "SingleLineComment";
TokenType[TokenType["EOF"] = 41] = "EOF";
TokenType[TokenType["CustomToken"] = 42] = "CustomToken";
})(TokenType = exports.TokenType || (exports.TokenType = {}));
class MultiLineStream {
constructor(source) {
this.source = source;
this.len = source.length;
this.position = 0;
}
substring(from, to = this.position) {
return this.source.substring(from, to);
}
eos() {
return this.len <= this.position;
}
pos() {
return this.position;
}
goBackTo(pos) {
this.position = pos;
}
goBack(n) {
this.position -= n;
}
advance(n) {
this.position += n;
}
nextChar() {
return this.source.charCodeAt(this.position++) || 0;
}
peekChar(n = 0) {
return this.source.charCodeAt(this.position + n) || 0;
}
lookbackChar(n = 0) {
return this.source.charCodeAt(this.position - n) || 0;
}
advanceIfChar(ch) {
if (ch === this.source.charCodeAt(this.position)) {
this.position++;
return true;
}
return false;
}
advanceIfChars(ch) {
if (this.position + ch.length > this.source.length) {
return false;
}
let i = 0;
for (; i < ch.length; i++) {
if (this.source.charCodeAt(this.position + i) !== ch[i]) {
return false;
}
}
this.advance(i);
return true;
}
advanceWhileChar(condition) {
const posNow = this.position;
while (this.position < this.len && condition(this.source.charCodeAt(this.position))) {
this.position++;
}
return this.position - posNow;
}
}
exports.MultiLineStream = MultiLineStream;
const _a = 'a'.charCodeAt(0);
const _f = 'f'.charCodeAt(0);
const _z = 'z'.charCodeAt(0);
const _u = 'u'.charCodeAt(0);
const _A = 'A'.charCodeAt(0);
const _F = 'F'.charCodeAt(0);
const _Z = 'Z'.charCodeAt(0);
const _0 = '0'.charCodeAt(0);
const _9 = '9'.charCodeAt(0);
const _TLD = '~'.charCodeAt(0);
const _HAT = '^'.charCodeAt(0);
const _EQS = '='.charCodeAt(0);
const _PIP = '|'.charCodeAt(0);
const _MIN = '-'.charCodeAt(0);
const _USC = '_'.charCodeAt(0);
const _PRC = '%'.charCodeAt(0);
const _MUL = '*'.charCodeAt(0);
const _LPA = '('.charCodeAt(0);
const _RPA = ')'.charCodeAt(0);
const _LAN = '<'.charCodeAt(0);
const _RAN = '>'.charCodeAt(0);
const _ATS = '@'.charCodeAt(0);
const _HSH = '#'.charCodeAt(0);
const _DLR = '$'.charCodeAt(0);
const _BSL = '\\'.charCodeAt(0);
const _FSL = '/'.charCodeAt(0);
const _NWL = '\n'.charCodeAt(0);
const _CAR = '\r'.charCodeAt(0);
const _LFD = '\f'.charCodeAt(0);
const _DQO = '"'.charCodeAt(0);
const _SQO = '\''.charCodeAt(0);
const _WSP = ' '.charCodeAt(0);
const _TAB = '\t'.charCodeAt(0);
const _SEM = ';'.charCodeAt(0);
const _COL = ':'.charCodeAt(0);
const _CUL = '{'.charCodeAt(0);
const _CUR = '}'.charCodeAt(0);
const _BRL = '['.charCodeAt(0);
const _BRR = ']'.charCodeAt(0);
const _CMA = ','.charCodeAt(0);
const _DOT = '.'.charCodeAt(0);
const _BNG = '!'.charCodeAt(0);
const _QSM = '?'.charCodeAt(0);
const _PLS = '+'.charCodeAt(0);
const staticTokenTable = {};
staticTokenTable[_SEM] = TokenType.SemiColon;
staticTokenTable[_COL] = TokenType.Colon;
staticTokenTable[_CUL] = TokenType.CurlyL;
staticTokenTable[_CUR] = TokenType.CurlyR;
staticTokenTable[_BRR] = TokenType.BracketR;
staticTokenTable[_BRL] = TokenType.BracketL;
staticTokenTable[_LPA] = TokenType.ParenthesisL;
staticTokenTable[_RPA] = TokenType.ParenthesisR;
staticTokenTable[_CMA] = TokenType.Comma;
const staticUnitTable = {};
staticUnitTable['em'] = TokenType.EMS;
staticUnitTable['ex'] = TokenType.EXS;
staticUnitTable['px'] = TokenType.Length;
staticUnitTable['cm'] = TokenType.Length;
staticUnitTable['mm'] = TokenType.Length;
staticUnitTable['in'] = TokenType.Length;
staticUnitTable['pt'] = TokenType.Length;
staticUnitTable['pc'] = TokenType.Length;
staticUnitTable['deg'] = TokenType.Angle;
staticUnitTable['rad'] = TokenType.Angle;
staticUnitTable['grad'] = TokenType.Angle;
staticUnitTable['ms'] = TokenType.Time;
staticUnitTable['s'] = TokenType.Time;
staticUnitTable['hz'] = TokenType.Freq;
staticUnitTable['khz'] = TokenType.Freq;
staticUnitTable['%'] = TokenType.Percentage;
staticUnitTable['fr'] = TokenType.Percentage;
staticUnitTable['dpi'] = TokenType.Resolution;
staticUnitTable['dpcm'] = TokenType.Resolution;
class Scanner {
constructor() {
this.stream = new MultiLineStream('');
this.ignoreComment = true;
this.ignoreWhitespace = true;
this.inURL = false;
}
setSource(input) {
this.stream = new MultiLineStream(input);
}
finishToken(offset, type, text) {
return {
offset: offset,
len: this.stream.pos() - offset,
type: type,
text: text || this.stream.substring(offset)
};
}
substring(offset, len) {
return this.stream.substring(offset, offset + len);
}
pos() {
return this.stream.pos();
}
goBackTo(pos) {
this.stream.goBackTo(pos);
}
scanUnquotedString() {
const offset = this.stream.pos();
const content = [];
if (this._unquotedString(content)) {
return this.finishToken(offset, TokenType.UnquotedString, content.join(''));
}
return null;
}
scan() {
// processes all whitespaces and comments
const triviaToken = this.trivia();
if (triviaToken !== null) {
return triviaToken;
}
const offset = this.stream.pos();
// End of file/input
if (this.stream.eos()) {
return this.finishToken(offset, TokenType.EOF);
}
return this.scanNext(offset);
}
/**
* Read the range as described in https://www.w3.org/TR/CSS21/syndata.html#tokenization
* Assume the `u` has aleady been consumed
* @returns if reading the unicode was successful
*/
tryScanUnicode() {
const offset = this.stream.pos();
if (!this.stream.eos() && this._unicodeRange()) {
return this.finishToken(offset, TokenType.UnicodeRange);
}
this.stream.goBackTo(offset);
return undefined;
}
scanNext(offset) {
// CDO <!--
if (this.stream.advanceIfChars([_LAN, _BNG, _MIN, _MIN])) {
return this.finishToken(offset, TokenType.CDO);
}
// CDC -->
if (this.stream.advanceIfChars([_MIN, _MIN, _RAN])) {
return this.finishToken(offset, TokenType.CDC);
}
let content = [];
if (this.ident(content)) {
return this.finishToken(offset, TokenType.Ident, content.join(''));
}
// at-keyword
if (this.stream.advanceIfChar(_ATS)) {
content = ['@'];
if (this._name(content)) {
const keywordText = content.join('');
if (keywordText === '@charset') {
return this.finishToken(offset, TokenType.Charset, keywordText);
}
return this.finishToken(offset, TokenType.AtKeyword, keywordText);
}
else {
return this.finishToken(offset, TokenType.Delim);
}
}
// hash
if (this.stream.advanceIfChar(_HSH)) {
content = ['#'];
if (this._name(content)) {
return this.finishToken(offset, TokenType.Hash, content.join(''));
}
else {
return this.finishToken(offset, TokenType.Delim);
}
}
// Important
if (this.stream.advanceIfChar(_BNG)) {
return this.finishToken(offset, TokenType.Exclamation);
}
// Numbers
if (this._number()) {
const pos = this.stream.pos();
content = [this.stream.substring(offset, pos)];
if (this.stream.advanceIfChar(_PRC)) {
// Percentage 43%
return this.finishToken(offset, TokenType.Percentage);
}
else if (this.ident(content)) {
const dim = this.stream.substring(pos).toLowerCase();
const tokenType = staticUnitTable[dim];
if (typeof tokenType !== 'undefined') {
// Known dimension 43px
return this.finishToken(offset, tokenType, content.join(''));
}
else {
// Unknown dimension 43ft
return this.finishToken(offset, TokenType.Dimension, content.join(''));
}
}
return this.finishToken(offset, TokenType.Num);
}
// String, BadString
content = [];
let tokenType = this._string(content);
if (tokenType !== null) {
return this.finishToken(offset, tokenType, content.join(''));
}
// single character tokens
tokenType = staticTokenTable[this.stream.peekChar()];
if (typeof tokenType !== 'undefined') {
this.stream.advance(1);
return this.finishToken(offset, tokenType);
}
// includes ~=
if (this.stream.peekChar(0) === _TLD && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(offset, TokenType.Includes);
}
// DashMatch |=
if (this.stream.peekChar(0) === _PIP && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(offset, TokenType.Dashmatch);
}
// Substring operator *=
if (this.stream.peekChar(0) === _MUL && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(offset, TokenType.SubstringOperator);
}
// Substring operator ^=
if (this.stream.peekChar(0) === _HAT && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(offset, TokenType.PrefixOperator);
}
// Substring operator $=
if (this.stream.peekChar(0) === _DLR && this.stream.peekChar(1) === _EQS) {
this.stream.advance(2);
return this.finishToken(offset, TokenType.SuffixOperator);
}
// Delim
this.stream.nextChar();
return this.finishToken(offset, TokenType.Delim);
}
trivia() {
while (true) {
const offset = this.stream.pos();
if (this._whitespace()) {
if (!this.ignoreWhitespace) {
return this.finishToken(offset, TokenType.Whitespace);
}
}
else if (this.comment()) {
if (!this.ignoreComment) {
return this.finishToken(offset, TokenType.Comment);
}
}
else {
return null;
}
}
}
comment() {
if (this.stream.advanceIfChars([_FSL, _MUL])) {
let success = false, hot = false;
this.stream.advanceWhileChar((ch) => {
if (hot && ch === _FSL) {
success = true;
return false;
}
hot = ch === _MUL;
return true;
});
if (success) {
this.stream.advance(1);
}
return true;
}
return false;
}
_number() {
let npeek = 0, ch;
if (this.stream.peekChar() === _DOT) {
npeek = 1;
}
ch = this.stream.peekChar(npeek);
if (ch >= _0 && ch <= _9) {
this.stream.advance(npeek + 1);
this.stream.advanceWhileChar((ch) => {
return ch >= _0 && ch <= _9 || npeek === 0 && ch === _DOT;
});
return true;
}
return false;
}
_newline(result) {
const ch = this.stream.peekChar();
switch (ch) {
case _CAR:
case _LFD:
case _NWL:
this.stream.advance(1);
result.push(String.fromCharCode(ch));
if (ch === _CAR && this.stream.advanceIfChar(_NWL)) {
result.push('\n');
}
return true;
}
return false;
}
_escape(result, includeNewLines) {
let ch = this.stream.peekChar();
if (ch === _BSL) {
this.stream.advance(1);
ch = this.stream.peekChar();
let hexNumCount = 0;
while (hexNumCount < 6 && (ch >= _0 && ch <= _9 || ch >= _a && ch <= _f || ch >= _A && ch <= _F)) {
this.stream.advance(1);
ch = this.stream.peekChar();
hexNumCount++;
}
if (hexNumCount > 0) {
try {
const hexVal = parseInt(this.stream.substring(this.stream.pos() - hexNumCount), 16);
if (hexVal) {
result.push(String.fromCharCode(hexVal));
}
}
catch (e) {
// ignore
}
// optional whitespace or new line, not part of result text
if (ch === _WSP || ch === _TAB) {
this.stream.advance(1);
}
else {
this._newline([]);
}
return true;
}
if (ch !== _CAR && ch !== _LFD && ch !== _NWL) {
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
else if (includeNewLines) {
return this._newline(result);
}
}
return false;
}
_stringChar(closeQuote, result) {
// not closeQuote, not backslash, not newline
const ch = this.stream.peekChar();
if (ch !== 0 && ch !== closeQuote && ch !== _BSL && ch !== _CAR && ch !== _LFD && ch !== _NWL) {
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
_string(result) {
if (this.stream.peekChar() === _SQO || this.stream.peekChar() === _DQO) {
const closeQuote = this.stream.nextChar();
result.push(String.fromCharCode(closeQuote));
while (this._stringChar(closeQuote, result) || this._escape(result, true)) {
// loop
}
if (this.stream.peekChar() === closeQuote) {
this.stream.nextChar();
result.push(String.fromCharCode(closeQuote));
return TokenType.String;
}
else {
return TokenType.BadString;
}
}
return null;
}
_unquotedChar(result) {
// not closeQuote, not backslash, not newline
const ch = this.stream.peekChar();
if (ch !== 0 && ch !== _BSL && ch !== _SQO && ch !== _DQO && ch !== _LPA && ch !== _RPA && ch !== _WSP && ch !== _TAB && ch !== _NWL && ch !== _LFD && ch !== _CAR) {
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
_unquotedString(result) {
let hasContent = false;
while (this._unquotedChar(result) || this._escape(result)) {
hasContent = true;
}
return hasContent;
}
_whitespace() {
const n = this.stream.advanceWhileChar((ch) => {
return ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR;
});
return n > 0;
}
_name(result) {
let matched = false;
while (this._identChar(result) || this._escape(result)) {
matched = true;
}
return matched;
}
ident(result) {
const pos = this.stream.pos();
const hasMinus = this._minus(result);
if (hasMinus) {
if (this._minus(result) /* -- */ || this._identFirstChar(result) || this._escape(result)) {
while (this._identChar(result) || this._escape(result)) {
// loop
}
return true;
}
}
else if (this._identFirstChar(result) || this._escape(result)) {
while (this._identChar(result) || this._escape(result)) {
// loop
}
return true;
}
this.stream.goBackTo(pos);
return false;
}
_identFirstChar(result) {
const ch = this.stream.peekChar();
if (ch === _USC || // _
ch >= _a && ch <= _z || // a-z
ch >= _A && ch <= _Z || // A-Z
ch >= 0x80 && ch <= 0xFFFF) { // nonascii
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
_minus(result) {
const ch = this.stream.peekChar();
if (ch === _MIN) {
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
_identChar(result) {
const ch = this.stream.peekChar();
if (ch === _USC || // _
ch === _MIN || // -
ch >= _a && ch <= _z || // a-z
ch >= _A && ch <= _Z || // A-Z
ch >= _0 && ch <= _9 || // 0/9
ch >= 0x80 && ch <= 0xFFFF) { // nonascii
this.stream.advance(1);
result.push(String.fromCharCode(ch));
return true;
}
return false;
}
_unicodeRange() {
// follow https://www.w3.org/TR/CSS21/syndata.html#tokenization and https://www.w3.org/TR/css-syntax-3/#urange-syntax
// assume u has already been parsed
if (this.stream.advanceIfChar(_PLS)) {
const isHexDigit = (ch) => (ch >= _0 && ch <= _9 || ch >= _a && ch <= _f || ch >= _A && ch <= _F);
const codePoints = this.stream.advanceWhileChar(isHexDigit) + this.stream.advanceWhileChar(ch => ch === _QSM);
if (codePoints >= 1 && codePoints <= 6) {
if (this.stream.advanceIfChar(_MIN)) {
const digits = this.stream.advanceWhileChar(isHexDigit);
if (digits >= 1 && digits <= 6) {
return true;
}
}
else {
return true;
}
}
}
return false;
}
}
exports.Scanner = Scanner;
});

View file

@ -0,0 +1,328 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./cssNodes", "../utils/arrays"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.Symbols = exports.ScopeBuilder = exports.Symbol = exports.GlobalScope = exports.Scope = void 0;
const nodes = require("./cssNodes");
const arrays_1 = require("../utils/arrays");
class Scope {
constructor(offset, length) {
this.offset = offset;
this.length = length;
this.symbols = [];
this.parent = null;
this.children = [];
}
addChild(scope) {
this.children.push(scope);
scope.setParent(this);
}
setParent(scope) {
this.parent = scope;
}
findScope(offset, length = 0) {
if (this.offset <= offset && this.offset + this.length > offset + length || this.offset === offset && this.length === length) {
return this.findInScope(offset, length);
}
return null;
}
findInScope(offset, length = 0) {
// find the first scope child that has an offset larger than offset + length
const end = offset + length;
const idx = (0, arrays_1.findFirst)(this.children, s => s.offset > end);
if (idx === 0) {
// all scopes have offsets larger than our end
return this;
}
const res = this.children[idx - 1];
if (res.offset <= offset && res.offset + res.length >= offset + length) {
return res.findInScope(offset, length);
}
return this;
}
addSymbol(symbol) {
this.symbols.push(symbol);
}
getSymbol(name, type) {
for (let index = 0; index < this.symbols.length; index++) {
const symbol = this.symbols[index];
if (symbol.name === name && symbol.type === type) {
return symbol;
}
}
return null;
}
getSymbols() {
return this.symbols;
}
}
exports.Scope = Scope;
class GlobalScope extends Scope {
constructor() {
super(0, Number.MAX_VALUE);
}
}
exports.GlobalScope = GlobalScope;
class Symbol {
constructor(name, value, node, type) {
this.name = name;
this.value = value;
this.node = node;
this.type = type;
}
}
exports.Symbol = Symbol;
class ScopeBuilder {
constructor(scope) {
this.scope = scope;
}
addSymbol(node, name, value, type) {
if (node.offset !== -1) {
const current = this.scope.findScope(node.offset, node.length);
if (current) {
current.addSymbol(new Symbol(name, value, node, type));
}
}
}
addScope(node) {
if (node.offset !== -1) {
const current = this.scope.findScope(node.offset, node.length);
if (current && (current.offset !== node.offset || current.length !== node.length)) { // scope already known?
const newScope = new Scope(node.offset, node.length);
current.addChild(newScope);
return newScope;
}
return current;
}
return null;
}
addSymbolToChildScope(scopeNode, node, name, value, type) {
if (scopeNode && scopeNode.offset !== -1) {
const current = this.addScope(scopeNode); // create the scope or gets the existing one
if (current) {
current.addSymbol(new Symbol(name, value, node, type));
}
}
}
visitNode(node) {
switch (node.type) {
case nodes.NodeType.Keyframe:
this.addSymbol(node, node.getName(), void 0, nodes.ReferenceType.Keyframe);
return true;
case nodes.NodeType.CustomPropertyDeclaration:
return this.visitCustomPropertyDeclarationNode(node);
case nodes.NodeType.VariableDeclaration:
return this.visitVariableDeclarationNode(node);
case nodes.NodeType.Ruleset:
return this.visitRuleSet(node);
case nodes.NodeType.MixinDeclaration:
this.addSymbol(node, node.getName(), void 0, nodes.ReferenceType.Mixin);
return true;
case nodes.NodeType.FunctionDeclaration:
this.addSymbol(node, node.getName(), void 0, nodes.ReferenceType.Function);
return true;
case nodes.NodeType.FunctionParameter: {
return this.visitFunctionParameterNode(node);
}
case nodes.NodeType.Declarations:
this.addScope(node);
return true;
case nodes.NodeType.For:
const forNode = node;
const scopeNode = forNode.getDeclarations();
if (scopeNode && forNode.variable) {
this.addSymbolToChildScope(scopeNode, forNode.variable, forNode.variable.getName(), void 0, nodes.ReferenceType.Variable);
}
return true;
case nodes.NodeType.Each: {
const eachNode = node;
const scopeNode = eachNode.getDeclarations();
if (scopeNode) {
const variables = eachNode.getVariables().getChildren();
for (const variable of variables) {
this.addSymbolToChildScope(scopeNode, variable, variable.getName(), void 0, nodes.ReferenceType.Variable);
}
}
return true;
}
}
return true;
}
visitRuleSet(node) {
const current = this.scope.findScope(node.offset, node.length);
if (current) {
for (const child of node.getSelectors().getChildren()) {
if (child instanceof nodes.Selector) {
if (child.getChildren().length === 1) { // only selectors with a single element can be extended
current.addSymbol(new Symbol(child.getChild(0).getText(), void 0, child, nodes.ReferenceType.Rule));
}
}
}
}
return true;
}
visitVariableDeclarationNode(node) {
const value = node.getValue() ? node.getValue().getText() : void 0;
this.addSymbol(node, node.getName(), value, nodes.ReferenceType.Variable);
return true;
}
visitFunctionParameterNode(node) {
// parameters are part of the body scope
const scopeNode = node.getParent().getDeclarations();
if (scopeNode) {
const valueNode = node.getDefaultValue();
const value = valueNode ? valueNode.getText() : void 0;
this.addSymbolToChildScope(scopeNode, node, node.getName(), value, nodes.ReferenceType.Variable);
}
return true;
}
visitCustomPropertyDeclarationNode(node) {
const value = node.getValue() ? node.getValue().getText() : '';
this.addCSSVariable(node.getProperty(), node.getProperty().getName(), value, nodes.ReferenceType.Variable);
return true;
}
addCSSVariable(node, name, value, type) {
if (node.offset !== -1) {
this.scope.addSymbol(new Symbol(name, value, node, type));
}
}
}
exports.ScopeBuilder = ScopeBuilder;
class Symbols {
constructor(node) {
this.global = new GlobalScope();
node.acceptVisitor(new ScopeBuilder(this.global));
}
findSymbolsAtOffset(offset, referenceType) {
let scope = this.global.findScope(offset, 0);
const result = [];
const names = {};
while (scope) {
const symbols = scope.getSymbols();
for (let i = 0; i < symbols.length; i++) {
const symbol = symbols[i];
if (symbol.type === referenceType && !names[symbol.name]) {
result.push(symbol);
names[symbol.name] = true;
}
}
scope = scope.parent;
}
return result;
}
internalFindSymbol(node, referenceTypes) {
let scopeNode = node;
if (node.parent instanceof nodes.FunctionParameter && node.parent.getParent() instanceof nodes.BodyDeclaration) {
scopeNode = node.parent.getParent().getDeclarations();
}
if (node.parent instanceof nodes.FunctionArgument && node.parent.getParent() instanceof nodes.Function) {
const funcId = node.parent.getParent().getIdentifier();
if (funcId) {
const functionSymbol = this.internalFindSymbol(funcId, [nodes.ReferenceType.Function]);
if (functionSymbol) {
scopeNode = functionSymbol.node.getDeclarations();
}
}
}
if (!scopeNode) {
return null;
}
const name = node.getText();
let scope = this.global.findScope(scopeNode.offset, scopeNode.length);
while (scope) {
for (let index = 0; index < referenceTypes.length; index++) {
const type = referenceTypes[index];
const symbol = scope.getSymbol(name, type);
if (symbol) {
return symbol;
}
}
scope = scope.parent;
}
return null;
}
evaluateReferenceTypes(node) {
if (node instanceof nodes.Identifier) {
const referenceTypes = node.referenceTypes;
if (referenceTypes) {
return referenceTypes;
}
else {
if (node.isCustomProperty) {
return [nodes.ReferenceType.Variable];
}
// are a reference to a keyframe?
const decl = nodes.getParentDeclaration(node);
if (decl) {
const propertyName = decl.getNonPrefixedPropertyName();
if ((propertyName === 'animation' || propertyName === 'animation-name')
&& decl.getValue() && decl.getValue().offset === node.offset) {
return [nodes.ReferenceType.Keyframe];
}
}
}
}
else if (node instanceof nodes.Variable) {
return [nodes.ReferenceType.Variable];
}
const selector = node.findAParent(nodes.NodeType.Selector, nodes.NodeType.ExtendsReference);
if (selector) {
return [nodes.ReferenceType.Rule];
}
return null;
}
findSymbolFromNode(node) {
if (!node) {
return null;
}
while (node.type === nodes.NodeType.Interpolation) {
node = node.getParent();
}
const referenceTypes = this.evaluateReferenceTypes(node);
if (referenceTypes) {
return this.internalFindSymbol(node, referenceTypes);
}
return null;
}
matchesSymbol(node, symbol) {
if (!node) {
return false;
}
while (node.type === nodes.NodeType.Interpolation) {
node = node.getParent();
}
if (!node.matches(symbol.name)) {
return false;
}
const referenceTypes = this.evaluateReferenceTypes(node);
if (!referenceTypes || referenceTypes.indexOf(symbol.type) === -1) {
return false;
}
const nodeSymbol = this.internalFindSymbol(node, referenceTypes);
return nodeSymbol === symbol;
}
findSymbol(name, type, offset) {
let scope = this.global.findScope(offset);
while (scope) {
const symbol = scope.getSymbol(name, type);
if (symbol) {
return symbol;
}
scope = scope.parent;
}
return null;
}
}
exports.Symbols = Symbols;
});

View file

@ -0,0 +1,729 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./lessScanner", "./cssScanner", "./cssParser", "./cssNodes", "./cssErrors"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.LESSParser = void 0;
const lessScanner = require("./lessScanner");
const cssScanner_1 = require("./cssScanner");
const cssParser = require("./cssParser");
const nodes = require("./cssNodes");
const cssErrors_1 = require("./cssErrors");
/// <summary>
/// A parser for LESS
/// http://lesscss.org/
/// </summary>
class LESSParser extends cssParser.Parser {
constructor() {
super(new lessScanner.LESSScanner());
}
_parseStylesheetStatement(isNested = false) {
if (this.peek(cssScanner_1.TokenType.AtKeyword)) {
return this._parseVariableDeclaration()
|| this._parsePlugin()
|| super._parseStylesheetAtStatement(isNested);
}
return this._tryParseMixinDeclaration()
|| this._tryParseMixinReference()
|| this._parseFunction()
|| this._parseRuleset(true);
}
_parseImport() {
if (!this.peekKeyword('@import') && !this.peekKeyword('@import-once') /* deprecated in less 1.4.1 */) {
return null;
}
const node = this.create(nodes.Import);
this.consumeToken();
// less 1.4.1: @import (css) "lib"
if (this.accept(cssScanner_1.TokenType.ParenthesisL)) {
if (!this.accept(cssScanner_1.TokenType.Ident)) {
return this.finish(node, cssErrors_1.ParseError.IdentifierExpected, [cssScanner_1.TokenType.SemiColon]);
}
do {
if (!this.accept(cssScanner_1.TokenType.Comma)) {
break;
}
} while (this.accept(cssScanner_1.TokenType.Ident));
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected, [cssScanner_1.TokenType.SemiColon]);
}
}
if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) {
return this.finish(node, cssErrors_1.ParseError.URIOrStringExpected, [cssScanner_1.TokenType.SemiColon]);
}
if (!this.peek(cssScanner_1.TokenType.SemiColon) && !this.peek(cssScanner_1.TokenType.EOF)) {
node.setMedialist(this._parseMediaQueryList());
}
return this.finish(node);
}
_parsePlugin() {
if (!this.peekKeyword('@plugin')) {
return null;
}
const node = this.createNode(nodes.NodeType.Plugin);
this.consumeToken(); // @import
if (!node.addChild(this._parseStringLiteral())) {
return this.finish(node, cssErrors_1.ParseError.StringLiteralExpected);
}
if (!this.accept(cssScanner_1.TokenType.SemiColon)) {
return this.finish(node, cssErrors_1.ParseError.SemiColonExpected);
}
return this.finish(node);
}
_parseMediaQuery() {
const node = super._parseMediaQuery();
if (!node) {
const node = this.create(nodes.MediaQuery);
if (node.addChild(this._parseVariable())) {
return this.finish(node);
}
return null;
}
return node;
}
_parseMediaDeclaration(isNested = false) {
return this._tryParseRuleset(isNested)
|| this._tryToParseDeclaration()
|| this._tryParseMixinDeclaration()
|| this._tryParseMixinReference()
|| this._parseDetachedRuleSetMixin()
|| this._parseStylesheetStatement(isNested);
}
_parseMediaFeatureName() {
return this._parseIdent() || this._parseVariable();
}
_parseVariableDeclaration(panic = []) {
const node = this.create(nodes.VariableDeclaration);
const mark = this.mark();
if (!node.setVariable(this._parseVariable(true))) {
return null;
}
if (this.accept(cssScanner_1.TokenType.Colon)) {
if (this.prevToken) {
node.colonPosition = this.prevToken.offset;
}
if (node.setValue(this._parseDetachedRuleSet())) {
node.needsSemicolon = false;
}
else if (!node.setValue(this._parseExpr())) {
return this.finish(node, cssErrors_1.ParseError.VariableValueExpected, [], panic);
}
node.addChild(this._parsePrio());
}
else {
this.restoreAtMark(mark);
return null; // at keyword, but no ':', not a variable declaration but some at keyword
}
if (this.peek(cssScanner_1.TokenType.SemiColon)) {
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
}
return this.finish(node);
}
_parseDetachedRuleSet() {
let mark = this.mark();
// "Anonymous mixin" used in each() and possibly a generic type in the future
if (this.peekDelim('#') || this.peekDelim('.')) {
this.consumeToken();
if (!this.hasWhitespace() && this.accept(cssScanner_1.TokenType.ParenthesisL)) {
let node = this.create(nodes.MixinDeclaration);
if (node.getParameters().addChild(this._parseMixinParameter())) {
while (this.accept(cssScanner_1.TokenType.Comma) || this.accept(cssScanner_1.TokenType.SemiColon)) {
if (this.peek(cssScanner_1.TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseMixinParameter())) {
this.markError(node, cssErrors_1.ParseError.IdentifierExpected, [], [cssScanner_1.TokenType.ParenthesisR]);
}
}
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
this.restoreAtMark(mark);
return null;
}
}
else {
this.restoreAtMark(mark);
return null;
}
}
if (!this.peek(cssScanner_1.TokenType.CurlyL)) {
return null;
}
const content = this.create(nodes.BodyDeclaration);
this._parseBody(content, this._parseDetachedRuleSetBody.bind(this));
return this.finish(content);
}
_parseDetachedRuleSetBody() {
return this._tryParseKeyframeSelector() || this._parseRuleSetDeclaration();
}
_addLookupChildren(node) {
if (!node.addChild(this._parseLookupValue())) {
return false;
}
let expectsValue = false;
while (true) {
if (this.peek(cssScanner_1.TokenType.BracketL)) {
expectsValue = true;
}
if (!node.addChild(this._parseLookupValue())) {
break;
}
expectsValue = false;
}
return !expectsValue;
}
_parseLookupValue() {
const node = this.create(nodes.Node);
const mark = this.mark();
if (!this.accept(cssScanner_1.TokenType.BracketL)) {
this.restoreAtMark(mark);
return null;
}
if (((node.addChild(this._parseVariable(false, true)) ||
node.addChild(this._parsePropertyIdentifier())) &&
this.accept(cssScanner_1.TokenType.BracketR)) || this.accept(cssScanner_1.TokenType.BracketR)) {
return node;
}
this.restoreAtMark(mark);
return null;
}
_parseVariable(declaration = false, insideLookup = false) {
const isPropertyReference = !declaration && this.peekDelim('$');
if (!this.peekDelim('@') && !isPropertyReference && !this.peek(cssScanner_1.TokenType.AtKeyword)) {
return null;
}
const node = this.create(nodes.Variable);
const mark = this.mark();
while (this.acceptDelim('@') || (!declaration && this.acceptDelim('$'))) {
if (this.hasWhitespace()) {
this.restoreAtMark(mark);
return null;
}
}
if (!this.accept(cssScanner_1.TokenType.AtKeyword) && !this.accept(cssScanner_1.TokenType.Ident)) {
this.restoreAtMark(mark);
return null;
}
if (!insideLookup && this.peek(cssScanner_1.TokenType.BracketL)) {
if (!this._addLookupChildren(node)) {
this.restoreAtMark(mark);
return null;
}
}
return node;
}
_parseTermExpression() {
return this._parseVariable() ||
this._parseEscaped() ||
super._parseTermExpression() || // preference for colors before mixin references
this._tryParseMixinReference(false);
}
_parseEscaped() {
if (this.peek(cssScanner_1.TokenType.EscapedJavaScript) ||
this.peek(cssScanner_1.TokenType.BadEscapedJavaScript)) {
const node = this.createNode(nodes.NodeType.EscapedValue);
this.consumeToken();
return this.finish(node);
}
if (this.peekDelim('~')) {
const node = this.createNode(nodes.NodeType.EscapedValue);
this.consumeToken();
if (this.accept(cssScanner_1.TokenType.String) || this.accept(cssScanner_1.TokenType.EscapedJavaScript)) {
return this.finish(node);
}
else {
return this.finish(node, cssErrors_1.ParseError.TermExpected);
}
}
return null;
}
_parseOperator() {
const node = this._parseGuardOperator();
if (node) {
return node;
}
else {
return super._parseOperator();
}
}
_parseGuardOperator() {
if (this.peekDelim('>')) {
const node = this.createNode(nodes.NodeType.Operator);
this.consumeToken();
this.acceptDelim('=');
return node;
}
else if (this.peekDelim('=')) {
const node = this.createNode(nodes.NodeType.Operator);
this.consumeToken();
this.acceptDelim('<');
return node;
}
else if (this.peekDelim('<')) {
const node = this.createNode(nodes.NodeType.Operator);
this.consumeToken();
this.acceptDelim('=');
return node;
}
return null;
}
_parseRuleSetDeclaration() {
if (this.peek(cssScanner_1.TokenType.AtKeyword)) {
return this._parseKeyframe()
|| this._parseMedia(true)
|| this._parseImport()
|| this._parseSupports(true) // @supports
|| this._parseLayer() // @layer
|| this._parsePropertyAtRule() // @property
|| this._parseDetachedRuleSetMixin() // less detached ruleset mixin
|| this._parseVariableDeclaration() // Variable declarations
|| this._parseRuleSetDeclarationAtStatement();
}
return this._tryParseMixinDeclaration()
|| this._tryParseRuleset(true) // nested ruleset
|| this._tryParseMixinReference() // less mixin reference
|| this._parseFunction()
|| this._parseExtend() // less extend declaration
|| this._parseDeclaration(); // try css ruleset declaration as the last option
}
_parseKeyframeIdent() {
return this._parseIdent([nodes.ReferenceType.Keyframe]) || this._parseVariable();
}
_parseKeyframeSelector() {
return this._parseDetachedRuleSetMixin() // less detached ruleset mixin
|| super._parseKeyframeSelector();
}
// public _parseSimpleSelectorBody(): nodes.Node | null {
// return this._parseNestingSelector() || super._parseSimpleSelectorBody();
// }
_parseSelector(isNested) {
// CSS Guards
const node = this.create(nodes.Selector);
let hasContent = false;
if (isNested) {
// nested selectors can start with a combinator
hasContent = node.addChild(this._parseCombinator());
}
while (node.addChild(this._parseSimpleSelector())) {
hasContent = true;
const mark = this.mark();
if (node.addChild(this._parseGuard()) && this.peek(cssScanner_1.TokenType.CurlyL)) {
break;
}
this.restoreAtMark(mark);
node.addChild(this._parseCombinator()); // optional
}
return hasContent ? this.finish(node) : null;
}
_parseNestingSelector() {
if (this.peekDelim('&')) {
const node = this.createNode(nodes.NodeType.SelectorCombinator);
this.consumeToken();
while (!this.hasWhitespace() && (this.acceptDelim('-') || this.accept(cssScanner_1.TokenType.Num) || this.accept(cssScanner_1.TokenType.Dimension) || node.addChild(this._parseIdent()) || this.acceptDelim('&'))) {
// support &-foo
}
return this.finish(node);
}
return null;
}
_parseSelectorIdent() {
if (!this.peekInterpolatedIdent()) {
return null;
}
const node = this.createNode(nodes.NodeType.SelectorInterpolation);
const hasContent = this._acceptInterpolatedIdent(node);
return hasContent ? this.finish(node) : null;
}
_parsePropertyIdentifier(inLookup = false) {
const propertyRegex = /^[\w-]+/;
if (!this.peekInterpolatedIdent() && !this.peekRegExp(this.token.type, propertyRegex)) {
return null;
}
const mark = this.mark();
const node = this.create(nodes.Identifier);
node.isCustomProperty = this.acceptDelim('-') && this.acceptDelim('-');
let childAdded = false;
if (!inLookup) {
if (node.isCustomProperty) {
childAdded = this._acceptInterpolatedIdent(node);
}
else {
childAdded = this._acceptInterpolatedIdent(node, propertyRegex);
}
}
else {
if (node.isCustomProperty) {
childAdded = node.addChild(this._parseIdent());
}
else {
childAdded = node.addChild(this._parseRegexp(propertyRegex));
}
}
if (!childAdded) {
this.restoreAtMark(mark);
return null;
}
if (!inLookup && !this.hasWhitespace()) {
this.acceptDelim('+');
if (!this.hasWhitespace()) {
this.acceptIdent('_');
}
}
return this.finish(node);
}
peekInterpolatedIdent() {
return this.peek(cssScanner_1.TokenType.Ident) ||
this.peekDelim('@') ||
this.peekDelim('$') ||
this.peekDelim('-');
}
_acceptInterpolatedIdent(node, identRegex) {
let hasContent = false;
const indentInterpolation = () => {
const pos = this.mark();
if (this.acceptDelim('-')) {
if (!this.hasWhitespace()) {
this.acceptDelim('-');
}
if (this.hasWhitespace()) {
this.restoreAtMark(pos);
return null;
}
}
return this._parseInterpolation();
};
const accept = identRegex ?
() => this.acceptRegexp(identRegex) :
() => this.accept(cssScanner_1.TokenType.Ident);
while (accept() ||
node.addChild(this._parseInterpolation() ||
this.try(indentInterpolation))) {
hasContent = true;
if (this.hasWhitespace()) {
break;
}
}
return hasContent;
}
_parseInterpolation() {
// @{name} Variable or
// ${name} Property
const mark = this.mark();
if (this.peekDelim('@') || this.peekDelim('$')) {
const node = this.createNode(nodes.NodeType.Interpolation);
this.consumeToken();
if (this.hasWhitespace() || !this.accept(cssScanner_1.TokenType.CurlyL)) {
this.restoreAtMark(mark);
return null;
}
if (!node.addChild(this._parseIdent())) {
return this.finish(node, cssErrors_1.ParseError.IdentifierExpected);
}
if (!this.accept(cssScanner_1.TokenType.CurlyR)) {
return this.finish(node, cssErrors_1.ParseError.RightCurlyExpected);
}
return this.finish(node);
}
return null;
}
_tryParseMixinDeclaration() {
const mark = this.mark();
const node = this.create(nodes.MixinDeclaration);
if (!node.setIdentifier(this._parseMixinDeclarationIdentifier()) || !this.accept(cssScanner_1.TokenType.ParenthesisL)) {
this.restoreAtMark(mark);
return null;
}
if (node.getParameters().addChild(this._parseMixinParameter())) {
while (this.accept(cssScanner_1.TokenType.Comma) || this.accept(cssScanner_1.TokenType.SemiColon)) {
if (this.peek(cssScanner_1.TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseMixinParameter())) {
this.markError(node, cssErrors_1.ParseError.IdentifierExpected, [], [cssScanner_1.TokenType.ParenthesisR]);
}
}
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
this.restoreAtMark(mark);
return null;
}
node.setGuard(this._parseGuard());
if (!this.peek(cssScanner_1.TokenType.CurlyL)) {
this.restoreAtMark(mark);
return null;
}
return this._parseBody(node, this._parseMixInBodyDeclaration.bind(this));
}
_parseMixInBodyDeclaration() {
return this._parseFontFace() || this._parseRuleSetDeclaration();
}
_parseMixinDeclarationIdentifier() {
let identifier;
if (this.peekDelim('#') || this.peekDelim('.')) {
identifier = this.create(nodes.Identifier);
this.consumeToken(); // # or .
if (this.hasWhitespace() || !identifier.addChild(this._parseIdent())) {
return null;
}
}
else if (this.peek(cssScanner_1.TokenType.Hash)) {
identifier = this.create(nodes.Identifier);
this.consumeToken(); // TokenType.Hash
}
else {
return null;
}
identifier.referenceTypes = [nodes.ReferenceType.Mixin];
return this.finish(identifier);
}
_parsePseudo() {
if (!this.peek(cssScanner_1.TokenType.Colon)) {
return null;
}
const mark = this.mark();
const node = this.create(nodes.ExtendsReference);
this.consumeToken(); // :
if (this.acceptIdent('extend')) {
return this._completeExtends(node);
}
this.restoreAtMark(mark);
return super._parsePseudo();
}
_parseExtend() {
if (!this.peekDelim('&')) {
return null;
}
const mark = this.mark();
const node = this.create(nodes.ExtendsReference);
this.consumeToken(); // &
if (this.hasWhitespace() || !this.accept(cssScanner_1.TokenType.Colon) || !this.acceptIdent('extend')) {
this.restoreAtMark(mark);
return null;
}
return this._completeExtends(node);
}
_completeExtends(node) {
if (!this.accept(cssScanner_1.TokenType.ParenthesisL)) {
return this.finish(node, cssErrors_1.ParseError.LeftParenthesisExpected);
}
const selectors = node.getSelectors();
if (!selectors.addChild(this._parseSelector(true))) {
return this.finish(node, cssErrors_1.ParseError.SelectorExpected);
}
while (this.accept(cssScanner_1.TokenType.Comma)) {
if (!selectors.addChild(this._parseSelector(true))) {
return this.finish(node, cssErrors_1.ParseError.SelectorExpected);
}
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected);
}
return this.finish(node);
}
_parseDetachedRuleSetMixin() {
if (!this.peek(cssScanner_1.TokenType.AtKeyword)) {
return null;
}
const mark = this.mark();
const node = this.create(nodes.MixinReference);
if (node.addChild(this._parseVariable(true)) && (this.hasWhitespace() || !this.accept(cssScanner_1.TokenType.ParenthesisL))) {
this.restoreAtMark(mark);
return null;
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected);
}
return this.finish(node);
}
_tryParseMixinReference(atRoot = true) {
const mark = this.mark();
const node = this.create(nodes.MixinReference);
let identifier = this._parseMixinDeclarationIdentifier();
while (identifier) {
this.acceptDelim('>');
const nextId = this._parseMixinDeclarationIdentifier();
if (nextId) {
node.getNamespaces().addChild(identifier);
identifier = nextId;
}
else {
break;
}
}
if (!node.setIdentifier(identifier)) {
this.restoreAtMark(mark);
return null;
}
let hasArguments = false;
if (this.accept(cssScanner_1.TokenType.ParenthesisL)) {
hasArguments = true;
if (node.getArguments().addChild(this._parseMixinArgument())) {
while (this.accept(cssScanner_1.TokenType.Comma) || this.accept(cssScanner_1.TokenType.SemiColon)) {
if (this.peek(cssScanner_1.TokenType.ParenthesisR)) {
break;
}
if (!node.getArguments().addChild(this._parseMixinArgument())) {
return this.finish(node, cssErrors_1.ParseError.ExpressionExpected);
}
}
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected);
}
identifier.referenceTypes = [nodes.ReferenceType.Mixin];
}
else {
identifier.referenceTypes = [nodes.ReferenceType.Mixin, nodes.ReferenceType.Rule];
}
if (this.peek(cssScanner_1.TokenType.BracketL)) {
if (!atRoot) {
this._addLookupChildren(node);
}
}
else {
node.addChild(this._parsePrio());
}
if (!hasArguments && !this.peek(cssScanner_1.TokenType.SemiColon) && !this.peek(cssScanner_1.TokenType.CurlyR) && !this.peek(cssScanner_1.TokenType.EOF)) {
this.restoreAtMark(mark);
return null;
}
return this.finish(node);
}
_parseMixinArgument() {
// [variableName ':'] expression | variableName '...'
const node = this.create(nodes.FunctionArgument);
const pos = this.mark();
const argument = this._parseVariable();
if (argument) {
if (!this.accept(cssScanner_1.TokenType.Colon)) {
this.restoreAtMark(pos);
}
else {
node.setIdentifier(argument);
}
}
if (node.setValue(this._parseDetachedRuleSet() || this._parseExpr(true))) {
return this.finish(node);
}
this.restoreAtMark(pos);
return null;
}
_parseMixinParameter() {
const node = this.create(nodes.FunctionParameter);
// special rest variable: @rest...
if (this.peekKeyword('@rest')) {
const restNode = this.create(nodes.Node);
this.consumeToken();
if (!this.accept(lessScanner.Ellipsis)) {
return this.finish(node, cssErrors_1.ParseError.DotExpected, [], [cssScanner_1.TokenType.Comma, cssScanner_1.TokenType.ParenthesisR]);
}
node.setIdentifier(this.finish(restNode));
return this.finish(node);
}
// special const args: ...
if (this.peek(lessScanner.Ellipsis)) {
const varargsNode = this.create(nodes.Node);
this.consumeToken();
node.setIdentifier(this.finish(varargsNode));
return this.finish(node);
}
let hasContent = false;
// default variable declaration: @param: 12 or @name
if (node.setIdentifier(this._parseVariable())) {
this.accept(cssScanner_1.TokenType.Colon);
hasContent = true;
}
if (!node.setDefaultValue(this._parseDetachedRuleSet() || this._parseExpr(true)) && !hasContent) {
return null;
}
return this.finish(node);
}
_parseGuard() {
if (!this.peekIdent('when')) {
return null;
}
const node = this.create(nodes.LessGuard);
this.consumeToken(); // when
node.isNegated = this.acceptIdent('not');
if (!node.getConditions().addChild(this._parseGuardCondition())) {
return this.finish(node, cssErrors_1.ParseError.ConditionExpected);
}
while (this.acceptIdent('and') || this.accept(cssScanner_1.TokenType.Comma)) {
if (!node.getConditions().addChild(this._parseGuardCondition())) {
return this.finish(node, cssErrors_1.ParseError.ConditionExpected);
}
}
return this.finish(node);
}
_parseGuardCondition() {
if (!this.peek(cssScanner_1.TokenType.ParenthesisL)) {
return null;
}
const node = this.create(nodes.GuardCondition);
this.consumeToken(); // ParenthesisL
if (!node.addChild(this._parseExpr())) {
// empty (?)
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected);
}
return this.finish(node);
}
_parseFunction() {
const pos = this.mark();
const node = this.create(nodes.Function);
if (!node.setIdentifier(this._parseFunctionIdentifier())) {
return null;
}
if (this.hasWhitespace() || !this.accept(cssScanner_1.TokenType.ParenthesisL)) {
this.restoreAtMark(pos);
return null;
}
if (node.getArguments().addChild(this._parseMixinArgument())) {
while (this.accept(cssScanner_1.TokenType.Comma) || this.accept(cssScanner_1.TokenType.SemiColon)) {
if (this.peek(cssScanner_1.TokenType.ParenthesisR)) {
break;
}
if (!node.getArguments().addChild(this._parseMixinArgument())) {
return this.finish(node, cssErrors_1.ParseError.ExpressionExpected);
}
}
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected);
}
return this.finish(node);
}
_parseFunctionIdentifier() {
if (this.peekDelim('%')) {
const node = this.create(nodes.Identifier);
node.referenceTypes = [nodes.ReferenceType.Function];
this.consumeToken();
return this.finish(node);
}
return super._parseFunctionIdentifier();
}
_parseURLArgument() {
const pos = this.mark();
const node = super._parseURLArgument();
if (!node || !this.peek(cssScanner_1.TokenType.ParenthesisR)) {
this.restoreAtMark(pos);
const node = this.create(nodes.Node);
node.addChild(this._parseBinaryExpr());
return this.finish(node);
}
return node;
}
}
exports.LESSParser = LESSParser;
});

View file

@ -0,0 +1,70 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./cssScanner"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.LESSScanner = exports.Ellipsis = void 0;
const scanner = require("./cssScanner");
const _FSL = '/'.charCodeAt(0);
const _NWL = '\n'.charCodeAt(0);
const _CAR = '\r'.charCodeAt(0);
const _LFD = '\f'.charCodeAt(0);
const _TIC = '`'.charCodeAt(0);
const _DOT = '.'.charCodeAt(0);
let customTokenValue = scanner.TokenType.CustomToken;
exports.Ellipsis = customTokenValue++;
class LESSScanner extends scanner.Scanner {
scanNext(offset) {
// LESS: escaped JavaScript code `const a = "dddd"`
const tokenType = this.escapedJavaScript();
if (tokenType !== null) {
return this.finishToken(offset, tokenType);
}
if (this.stream.advanceIfChars([_DOT, _DOT, _DOT])) {
return this.finishToken(offset, exports.Ellipsis);
}
return super.scanNext(offset);
}
comment() {
if (super.comment()) {
return true;
}
if (!this.inURL && this.stream.advanceIfChars([_FSL, _FSL])) {
this.stream.advanceWhileChar((ch) => {
switch (ch) {
case _NWL:
case _CAR:
case _LFD:
return false;
default:
return true;
}
});
return true;
}
else {
return false;
}
}
escapedJavaScript() {
const ch = this.stream.peekChar();
if (ch === _TIC) {
this.stream.advance(1);
this.stream.advanceWhileChar((ch) => { return ch !== _TIC; });
return this.stream.advanceIfChar(_TIC) ? scanner.TokenType.EscapedJavaScript : scanner.TokenType.BadEscapedJavaScript;
}
return null;
}
}
exports.LESSScanner = LESSScanner;
});

View file

@ -0,0 +1,30 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "@vscode/l10n"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.SCSSParseError = exports.SCSSIssueType = void 0;
const l10n = require("@vscode/l10n");
class SCSSIssueType {
constructor(id, message) {
this.id = id;
this.message = message;
}
}
exports.SCSSIssueType = SCSSIssueType;
exports.SCSSParseError = {
FromExpected: new SCSSIssueType('scss-fromexpected', l10n.t("'from' expected")),
ThroughOrToExpected: new SCSSIssueType('scss-throughexpected', l10n.t("'through' or 'to' expected")),
InExpected: new SCSSIssueType('scss-fromexpected', l10n.t("'in' expected")),
};
});

View file

@ -0,0 +1,823 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./scssScanner", "./cssScanner", "./cssParser", "./cssNodes", "./scssErrors", "./cssErrors"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.SCSSParser = void 0;
const scssScanner = require("./scssScanner");
const cssScanner_1 = require("./cssScanner");
const cssParser = require("./cssParser");
const nodes = require("./cssNodes");
const scssErrors_1 = require("./scssErrors");
const cssErrors_1 = require("./cssErrors");
/// <summary>
/// A parser for scss
/// http://sass-lang.com/documentation/file.SASS_REFERENCE.html
/// </summary>
class SCSSParser extends cssParser.Parser {
constructor() {
super(new scssScanner.SCSSScanner());
}
_parseStylesheetStatement(isNested = false) {
if (this.peek(cssScanner_1.TokenType.AtKeyword)) {
return this._parseWarnAndDebug() // @warn, @debug and @error statements
|| this._parseControlStatement() // @if, @while, @for, @each
|| this._parseMixinDeclaration() // @mixin
|| this._parseMixinContent() // @content
|| this._parseMixinReference() // @include
|| this._parseFunctionDeclaration() // @function
|| this._parseForward() // @forward
|| this._parseUse() // @use
|| this._parseRuleset(isNested) // @at-rule
|| super._parseStylesheetAtStatement(isNested);
}
return this._parseRuleset(true) || this._parseVariableDeclaration();
}
_parseImport() {
if (!this.peekKeyword('@import')) {
return null;
}
const node = this.create(nodes.Import);
this.consumeToken();
if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) {
return this.finish(node, cssErrors_1.ParseError.URIOrStringExpected);
}
while (this.accept(cssScanner_1.TokenType.Comma)) {
if (!node.addChild(this._parseURILiteral()) && !node.addChild(this._parseStringLiteral())) {
return this.finish(node, cssErrors_1.ParseError.URIOrStringExpected);
}
}
if (!this.peek(cssScanner_1.TokenType.SemiColon) && !this.peek(cssScanner_1.TokenType.EOF)) {
node.setMedialist(this._parseMediaQueryList());
}
return this.finish(node);
}
// scss variables: $font-size: 12px;
_parseVariableDeclaration(panic = []) {
if (!this.peek(scssScanner.VariableName)) {
return null;
}
const node = this.create(nodes.VariableDeclaration);
if (!node.setVariable(this._parseVariable())) {
return null;
}
if (!this.accept(cssScanner_1.TokenType.Colon)) {
return this.finish(node, cssErrors_1.ParseError.ColonExpected);
}
if (this.prevToken) {
node.colonPosition = this.prevToken.offset;
}
if (!node.setValue(this._parseExpr())) {
return this.finish(node, cssErrors_1.ParseError.VariableValueExpected, [], panic);
}
while (this.peek(cssScanner_1.TokenType.Exclamation)) {
if (node.addChild(this._tryParsePrio())) {
// !important
}
else {
this.consumeToken();
if (!this.peekRegExp(cssScanner_1.TokenType.Ident, /^(default|global)$/)) {
return this.finish(node, cssErrors_1.ParseError.UnknownKeyword);
}
this.consumeToken();
}
}
if (this.peek(cssScanner_1.TokenType.SemiColon)) {
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
}
return this.finish(node);
}
_parseMediaCondition() {
return this._parseInterpolation() || super._parseMediaCondition();
}
_parseMediaFeatureRangeOperator() {
return this.accept(scssScanner.SmallerEqualsOperator) || this.accept(scssScanner.GreaterEqualsOperator) || super._parseMediaFeatureRangeOperator();
}
_parseMediaFeatureName() {
return this._parseModuleMember()
|| this._parseFunction() // function before ident
|| this._parseIdent()
|| this._parseVariable();
}
_parseKeyframeSelector() {
return this._tryParseKeyframeSelector()
|| this._parseControlStatement(this._parseKeyframeSelector.bind(this))
|| this._parseVariableDeclaration()
|| this._parseMixinContent();
}
_parseVariable() {
if (!this.peek(scssScanner.VariableName)) {
return null;
}
const node = this.create(nodes.Variable);
this.consumeToken();
return node;
}
_parseModuleMember() {
const pos = this.mark();
const node = this.create(nodes.Module);
if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Module]))) {
return null;
}
if (this.hasWhitespace()
|| !this.acceptDelim('.')
|| this.hasWhitespace()) {
this.restoreAtMark(pos);
return null;
}
if (!node.addChild(this._parseVariable() || this._parseFunction())) {
return this.finish(node, cssErrors_1.ParseError.IdentifierOrVariableExpected);
}
return node;
}
_parseIdent(referenceTypes) {
if (!this.peek(cssScanner_1.TokenType.Ident) && !this.peek(scssScanner.InterpolationFunction) && !this.peekDelim('-')) {
return null;
}
const node = this.create(nodes.Identifier);
node.referenceTypes = referenceTypes;
node.isCustomProperty = this.peekRegExp(cssScanner_1.TokenType.Ident, /^--/);
let hasContent = false;
const indentInterpolation = () => {
const pos = this.mark();
if (this.acceptDelim('-')) {
if (!this.hasWhitespace()) {
this.acceptDelim('-');
}
if (this.hasWhitespace()) {
this.restoreAtMark(pos);
return null;
}
}
return this._parseInterpolation();
};
while (this.accept(cssScanner_1.TokenType.Ident) || node.addChild(indentInterpolation()) || (hasContent && this.acceptRegexp(/^[\w-]/))) {
hasContent = true;
if (this.hasWhitespace()) {
break;
}
}
return hasContent ? this.finish(node) : null;
}
_parseTermExpression() {
return this._parseModuleMember() ||
this._parseVariable() ||
this._parseNestingSelector() ||
//this._tryParsePrio() ||
super._parseTermExpression();
}
_parseInterpolation() {
if (this.peek(scssScanner.InterpolationFunction)) {
const node = this.create(nodes.Interpolation);
this.consumeToken();
if (!node.addChild(this._parseExpr()) && !this._parseNestingSelector()) {
if (this.accept(cssScanner_1.TokenType.CurlyR)) {
return this.finish(node);
}
return this.finish(node, cssErrors_1.ParseError.ExpressionExpected);
}
if (!this.accept(cssScanner_1.TokenType.CurlyR)) {
return this.finish(node, cssErrors_1.ParseError.RightCurlyExpected);
}
return this.finish(node);
}
return null;
}
_parseOperator() {
if (this.peek(scssScanner.EqualsOperator) || this.peek(scssScanner.NotEqualsOperator)
|| this.peek(scssScanner.GreaterEqualsOperator) || this.peek(scssScanner.SmallerEqualsOperator)
|| this.peekDelim('>') || this.peekDelim('<')
|| this.peekIdent('and') || this.peekIdent('or')
|| this.peekDelim('%')) {
const node = this.createNode(nodes.NodeType.Operator);
this.consumeToken();
return this.finish(node);
}
return super._parseOperator();
}
_parseUnaryOperator() {
if (this.peekIdent('not')) {
const node = this.create(nodes.Node);
this.consumeToken();
return this.finish(node);
}
return super._parseUnaryOperator();
}
_parseRuleSetDeclaration() {
if (this.peek(cssScanner_1.TokenType.AtKeyword)) {
return this._parseKeyframe() // nested @keyframe
|| this._parseImport() // nested @import
|| this._parseMedia(true) // nested @media
|| this._parseFontFace() // nested @font-face
|| this._parseWarnAndDebug() // @warn, @debug and @error statements
|| this._parseControlStatement() // @if, @while, @for, @each
|| this._parseFunctionDeclaration() // @function
|| this._parseExtends() // @extends
|| this._parseMixinReference() // @include
|| this._parseMixinContent() // @content
|| this._parseMixinDeclaration() // nested @mixin
|| this._parseRuleset(true) // @at-rule
|| this._parseSupports(true) // @supports
|| this._parseLayer() // @layer
|| this._parsePropertyAtRule() // @property
|| this._parseRuleSetDeclarationAtStatement();
}
return this._parseVariableDeclaration() // variable declaration
|| this._tryParseRuleset(true) // nested ruleset
|| this._parseDeclaration(); // try css ruleset declaration as last so in the error case, the ast will contain a declaration
}
_parseDeclaration(stopTokens) {
const custonProperty = this._tryParseCustomPropertyDeclaration(stopTokens);
if (custonProperty) {
return custonProperty;
}
const node = this.create(nodes.Declaration);
if (!node.setProperty(this._parseProperty())) {
return null;
}
if (!this.accept(cssScanner_1.TokenType.Colon)) {
return this.finish(node, cssErrors_1.ParseError.ColonExpected, [cssScanner_1.TokenType.Colon], stopTokens || [cssScanner_1.TokenType.SemiColon]);
}
if (this.prevToken) {
node.colonPosition = this.prevToken.offset;
}
let hasContent = false;
if (node.setValue(this._parseExpr())) {
hasContent = true;
node.addChild(this._parsePrio());
}
if (this.peek(cssScanner_1.TokenType.CurlyL)) {
node.setNestedProperties(this._parseNestedProperties());
}
else {
if (!hasContent) {
return this.finish(node, cssErrors_1.ParseError.PropertyValueExpected);
}
}
if (this.peek(cssScanner_1.TokenType.SemiColon)) {
node.semicolonPosition = this.token.offset; // not part of the declaration, but useful information for code assist
}
return this.finish(node);
}
_parseNestedProperties() {
const node = this.create(nodes.NestedProperties);
return this._parseBody(node, this._parseDeclaration.bind(this));
}
_parseExtends() {
if (this.peekKeyword('@extend')) {
const node = this.create(nodes.ExtendsReference);
this.consumeToken();
if (!node.getSelectors().addChild(this._parseSimpleSelector())) {
return this.finish(node, cssErrors_1.ParseError.SelectorExpected);
}
while (this.accept(cssScanner_1.TokenType.Comma)) {
node.getSelectors().addChild(this._parseSimpleSelector());
}
if (this.accept(cssScanner_1.TokenType.Exclamation)) {
if (!this.acceptIdent('optional')) {
return this.finish(node, cssErrors_1.ParseError.UnknownKeyword);
}
}
return this.finish(node);
}
return null;
}
_parseSimpleSelectorBody() {
return this._parseSelectorPlaceholder() || super._parseSimpleSelectorBody();
}
_parseNestingSelector() {
if (this.peekDelim('&')) {
const node = this.createNode(nodes.NodeType.SelectorCombinator);
this.consumeToken();
while (!this.hasWhitespace() && (this.acceptDelim('-') || this.accept(cssScanner_1.TokenType.Num) || this.accept(cssScanner_1.TokenType.Dimension) || node.addChild(this._parseIdent()) || this.acceptDelim('&'))) {
// support &-foo-1
}
return this.finish(node);
}
return null;
}
_parseSelectorPlaceholder() {
if (this.peekDelim('%')) {
const node = this.createNode(nodes.NodeType.SelectorPlaceholder);
this.consumeToken();
this._parseIdent();
return this.finish(node);
}
else if (this.peekKeyword('@at-root')) {
const node = this.createNode(nodes.NodeType.SelectorPlaceholder);
this.consumeToken();
if (this.accept(cssScanner_1.TokenType.ParenthesisL)) {
if (!this.acceptIdent('with') && !this.acceptIdent('without')) {
return this.finish(node, cssErrors_1.ParseError.IdentifierExpected);
}
if (!this.accept(cssScanner_1.TokenType.Colon)) {
return this.finish(node, cssErrors_1.ParseError.ColonExpected);
}
if (!node.addChild(this._parseIdent())) {
return this.finish(node, cssErrors_1.ParseError.IdentifierExpected);
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected, [cssScanner_1.TokenType.CurlyR]);
}
}
return this.finish(node);
}
return null;
}
_parseElementName() {
const pos = this.mark();
const node = super._parseElementName();
if (node && !this.hasWhitespace() && this.peek(cssScanner_1.TokenType.ParenthesisL)) { // for #49589
this.restoreAtMark(pos);
return null;
}
return node;
}
_tryParsePseudoIdentifier() {
return this._parseInterpolation() || super._tryParsePseudoIdentifier(); // for #49589
}
_parseWarnAndDebug() {
if (!this.peekKeyword('@debug')
&& !this.peekKeyword('@warn')
&& !this.peekKeyword('@error')) {
return null;
}
const node = this.createNode(nodes.NodeType.Debug);
this.consumeToken(); // @debug, @warn or @error
node.addChild(this._parseExpr()); // optional
return this.finish(node);
}
_parseControlStatement(parseStatement = this._parseRuleSetDeclaration.bind(this)) {
if (!this.peek(cssScanner_1.TokenType.AtKeyword)) {
return null;
}
return this._parseIfStatement(parseStatement) || this._parseForStatement(parseStatement)
|| this._parseEachStatement(parseStatement) || this._parseWhileStatement(parseStatement);
}
_parseIfStatement(parseStatement) {
if (!this.peekKeyword('@if')) {
return null;
}
return this._internalParseIfStatement(parseStatement);
}
_internalParseIfStatement(parseStatement) {
const node = this.create(nodes.IfStatement);
this.consumeToken(); // @if or if
if (!node.setExpression(this._parseExpr(true))) {
return this.finish(node, cssErrors_1.ParseError.ExpressionExpected);
}
this._parseBody(node, parseStatement);
if (this.acceptKeyword('@else')) {
if (this.peekIdent('if')) {
node.setElseClause(this._internalParseIfStatement(parseStatement));
}
else if (this.peek(cssScanner_1.TokenType.CurlyL)) {
const elseNode = this.create(nodes.ElseStatement);
this._parseBody(elseNode, parseStatement);
node.setElseClause(elseNode);
}
}
return this.finish(node);
}
_parseForStatement(parseStatement) {
if (!this.peekKeyword('@for')) {
return null;
}
const node = this.create(nodes.ForStatement);
this.consumeToken(); // @for
if (!node.setVariable(this._parseVariable())) {
return this.finish(node, cssErrors_1.ParseError.VariableNameExpected, [cssScanner_1.TokenType.CurlyR]);
}
if (!this.acceptIdent('from')) {
return this.finish(node, scssErrors_1.SCSSParseError.FromExpected, [cssScanner_1.TokenType.CurlyR]);
}
if (!node.addChild(this._parseBinaryExpr())) {
return this.finish(node, cssErrors_1.ParseError.ExpressionExpected, [cssScanner_1.TokenType.CurlyR]);
}
if (!this.acceptIdent('to') && !this.acceptIdent('through')) {
return this.finish(node, scssErrors_1.SCSSParseError.ThroughOrToExpected, [cssScanner_1.TokenType.CurlyR]);
}
if (!node.addChild(this._parseBinaryExpr())) {
return this.finish(node, cssErrors_1.ParseError.ExpressionExpected, [cssScanner_1.TokenType.CurlyR]);
}
return this._parseBody(node, parseStatement);
}
_parseEachStatement(parseStatement) {
if (!this.peekKeyword('@each')) {
return null;
}
const node = this.create(nodes.EachStatement);
this.consumeToken(); // @each
const variables = node.getVariables();
if (!variables.addChild(this._parseVariable())) {
return this.finish(node, cssErrors_1.ParseError.VariableNameExpected, [cssScanner_1.TokenType.CurlyR]);
}
while (this.accept(cssScanner_1.TokenType.Comma)) {
if (!variables.addChild(this._parseVariable())) {
return this.finish(node, cssErrors_1.ParseError.VariableNameExpected, [cssScanner_1.TokenType.CurlyR]);
}
}
this.finish(variables);
if (!this.acceptIdent('in')) {
return this.finish(node, scssErrors_1.SCSSParseError.InExpected, [cssScanner_1.TokenType.CurlyR]);
}
if (!node.addChild(this._parseExpr())) {
return this.finish(node, cssErrors_1.ParseError.ExpressionExpected, [cssScanner_1.TokenType.CurlyR]);
}
return this._parseBody(node, parseStatement);
}
_parseWhileStatement(parseStatement) {
if (!this.peekKeyword('@while')) {
return null;
}
const node = this.create(nodes.WhileStatement);
this.consumeToken(); // @while
if (!node.addChild(this._parseBinaryExpr())) {
return this.finish(node, cssErrors_1.ParseError.ExpressionExpected, [cssScanner_1.TokenType.CurlyR]);
}
return this._parseBody(node, parseStatement);
}
_parseFunctionBodyDeclaration() {
return this._parseVariableDeclaration() || this._parseReturnStatement() || this._parseWarnAndDebug()
|| this._parseControlStatement(this._parseFunctionBodyDeclaration.bind(this));
}
_parseFunctionDeclaration() {
if (!this.peekKeyword('@function')) {
return null;
}
const node = this.create(nodes.FunctionDeclaration);
this.consumeToken(); // @function
if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Function]))) {
return this.finish(node, cssErrors_1.ParseError.IdentifierExpected, [cssScanner_1.TokenType.CurlyR]);
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisL)) {
return this.finish(node, cssErrors_1.ParseError.LeftParenthesisExpected, [cssScanner_1.TokenType.CurlyR]);
}
if (node.getParameters().addChild(this._parseParameterDeclaration())) {
while (this.accept(cssScanner_1.TokenType.Comma)) {
if (this.peek(cssScanner_1.TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseParameterDeclaration())) {
return this.finish(node, cssErrors_1.ParseError.VariableNameExpected);
}
}
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected, [cssScanner_1.TokenType.CurlyR]);
}
return this._parseBody(node, this._parseFunctionBodyDeclaration.bind(this));
}
_parseReturnStatement() {
if (!this.peekKeyword('@return')) {
return null;
}
const node = this.createNode(nodes.NodeType.ReturnStatement);
this.consumeToken(); // @function
if (!node.addChild(this._parseExpr())) {
return this.finish(node, cssErrors_1.ParseError.ExpressionExpected);
}
return this.finish(node);
}
_parseMixinDeclaration() {
if (!this.peekKeyword('@mixin')) {
return null;
}
const node = this.create(nodes.MixinDeclaration);
this.consumeToken();
if (!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Mixin]))) {
return this.finish(node, cssErrors_1.ParseError.IdentifierExpected, [cssScanner_1.TokenType.CurlyR]);
}
if (this.accept(cssScanner_1.TokenType.ParenthesisL)) {
if (node.getParameters().addChild(this._parseParameterDeclaration())) {
while (this.accept(cssScanner_1.TokenType.Comma)) {
if (this.peek(cssScanner_1.TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseParameterDeclaration())) {
return this.finish(node, cssErrors_1.ParseError.VariableNameExpected);
}
}
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected, [cssScanner_1.TokenType.CurlyR]);
}
}
return this._parseBody(node, this._parseRuleSetDeclaration.bind(this));
}
_parseParameterDeclaration() {
const node = this.create(nodes.FunctionParameter);
if (!node.setIdentifier(this._parseVariable())) {
return null;
}
if (this.accept(scssScanner.Ellipsis)) {
// ok
}
if (this.accept(cssScanner_1.TokenType.Colon)) {
if (!node.setDefaultValue(this._parseExpr(true))) {
return this.finish(node, cssErrors_1.ParseError.VariableValueExpected, [], [cssScanner_1.TokenType.Comma, cssScanner_1.TokenType.ParenthesisR]);
}
}
return this.finish(node);
}
_parseMixinContent() {
if (!this.peekKeyword('@content')) {
return null;
}
const node = this.create(nodes.MixinContentReference);
this.consumeToken();
if (this.accept(cssScanner_1.TokenType.ParenthesisL)) {
if (node.getArguments().addChild(this._parseFunctionArgument())) {
while (this.accept(cssScanner_1.TokenType.Comma)) {
if (this.peek(cssScanner_1.TokenType.ParenthesisR)) {
break;
}
if (!node.getArguments().addChild(this._parseFunctionArgument())) {
return this.finish(node, cssErrors_1.ParseError.ExpressionExpected);
}
}
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected);
}
}
return this.finish(node);
}
_parseMixinReference() {
if (!this.peekKeyword('@include')) {
return null;
}
const node = this.create(nodes.MixinReference);
this.consumeToken();
// Could be module or mixin identifier, set as mixin as default.
const firstIdent = this._parseIdent([nodes.ReferenceType.Mixin]);
if (!node.setIdentifier(firstIdent)) {
return this.finish(node, cssErrors_1.ParseError.IdentifierExpected, [cssScanner_1.TokenType.CurlyR]);
}
// Is a module accessor.
if (!this.hasWhitespace() && this.acceptDelim('.') && !this.hasWhitespace()) {
const secondIdent = this._parseIdent([nodes.ReferenceType.Mixin]);
if (!secondIdent) {
return this.finish(node, cssErrors_1.ParseError.IdentifierExpected, [cssScanner_1.TokenType.CurlyR]);
}
const moduleToken = this.create(nodes.Module);
// Re-purpose first matched ident as identifier for module token.
firstIdent.referenceTypes = [nodes.ReferenceType.Module];
moduleToken.setIdentifier(firstIdent);
// Override identifier with second ident.
node.setIdentifier(secondIdent);
node.addChild(moduleToken);
}
if (this.accept(cssScanner_1.TokenType.ParenthesisL)) {
if (node.getArguments().addChild(this._parseFunctionArgument())) {
while (this.accept(cssScanner_1.TokenType.Comma)) {
if (this.peek(cssScanner_1.TokenType.ParenthesisR)) {
break;
}
if (!node.getArguments().addChild(this._parseFunctionArgument())) {
return this.finish(node, cssErrors_1.ParseError.ExpressionExpected);
}
}
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected);
}
}
if (this.peekIdent('using') || this.peek(cssScanner_1.TokenType.CurlyL)) {
node.setContent(this._parseMixinContentDeclaration());
}
return this.finish(node);
}
_parseMixinContentDeclaration() {
const node = this.create(nodes.MixinContentDeclaration);
if (this.acceptIdent('using')) {
if (!this.accept(cssScanner_1.TokenType.ParenthesisL)) {
return this.finish(node, cssErrors_1.ParseError.LeftParenthesisExpected, [cssScanner_1.TokenType.CurlyL]);
}
if (node.getParameters().addChild(this._parseParameterDeclaration())) {
while (this.accept(cssScanner_1.TokenType.Comma)) {
if (this.peek(cssScanner_1.TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseParameterDeclaration())) {
return this.finish(node, cssErrors_1.ParseError.VariableNameExpected);
}
}
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected, [cssScanner_1.TokenType.CurlyL]);
}
}
if (this.peek(cssScanner_1.TokenType.CurlyL)) {
this._parseBody(node, this._parseMixinReferenceBodyStatement.bind(this));
}
return this.finish(node);
}
_parseMixinReferenceBodyStatement() {
return this._tryParseKeyframeSelector() || this._parseRuleSetDeclaration();
}
_parseFunctionArgument() {
// [variableName ':'] expression | variableName '...'
const node = this.create(nodes.FunctionArgument);
const pos = this.mark();
const argument = this._parseVariable();
if (argument) {
if (!this.accept(cssScanner_1.TokenType.Colon)) {
if (this.accept(scssScanner.Ellipsis)) { // optional
node.setValue(argument);
return this.finish(node);
}
else {
this.restoreAtMark(pos);
}
}
else {
node.setIdentifier(argument);
}
}
if (node.setValue(this._parseExpr(true))) {
this.accept(scssScanner.Ellipsis); // #43746
node.addChild(this._parsePrio()); // #9859
return this.finish(node);
}
else if (node.setValue(this._tryParsePrio())) {
return this.finish(node);
}
return null;
}
_parseURLArgument() {
const pos = this.mark();
const node = super._parseURLArgument();
if (!node || !this.peek(cssScanner_1.TokenType.ParenthesisR)) {
this.restoreAtMark(pos);
const node = this.create(nodes.Node);
node.addChild(this._parseBinaryExpr());
return this.finish(node);
}
return node;
}
_parseOperation() {
if (!this.peek(cssScanner_1.TokenType.ParenthesisL)) {
return null;
}
const node = this.create(nodes.Node);
this.consumeToken();
while (node.addChild(this._parseListElement())) {
this.accept(cssScanner_1.TokenType.Comma); // optional
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected);
}
return this.finish(node);
}
_parseListElement() {
const node = this.create(nodes.ListEntry);
const child = this._parseBinaryExpr();
if (!child) {
return null;
}
if (this.accept(cssScanner_1.TokenType.Colon)) {
node.setKey(child);
if (!node.setValue(this._parseBinaryExpr())) {
return this.finish(node, cssErrors_1.ParseError.ExpressionExpected);
}
}
else {
node.setValue(child);
}
return this.finish(node);
}
_parseUse() {
if (!this.peekKeyword('@use')) {
return null;
}
const node = this.create(nodes.Use);
this.consumeToken(); // @use
if (!node.addChild(this._parseStringLiteral())) {
return this.finish(node, cssErrors_1.ParseError.StringLiteralExpected);
}
if (!this.peek(cssScanner_1.TokenType.SemiColon) && !this.peek(cssScanner_1.TokenType.EOF)) {
if (!this.peekRegExp(cssScanner_1.TokenType.Ident, /as|with/)) {
return this.finish(node, cssErrors_1.ParseError.UnknownKeyword);
}
if (this.acceptIdent('as') &&
(!node.setIdentifier(this._parseIdent([nodes.ReferenceType.Module])) && !this.acceptDelim('*'))) {
return this.finish(node, cssErrors_1.ParseError.IdentifierOrWildcardExpected);
}
if (this.acceptIdent('with')) {
if (!this.accept(cssScanner_1.TokenType.ParenthesisL)) {
return this.finish(node, cssErrors_1.ParseError.LeftParenthesisExpected, [cssScanner_1.TokenType.ParenthesisR]);
}
// First variable statement, no comma.
if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) {
return this.finish(node, cssErrors_1.ParseError.VariableNameExpected);
}
while (this.accept(cssScanner_1.TokenType.Comma)) {
if (this.peek(cssScanner_1.TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) {
return this.finish(node, cssErrors_1.ParseError.VariableNameExpected);
}
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected);
}
}
}
if (!this.accept(cssScanner_1.TokenType.SemiColon) && !this.accept(cssScanner_1.TokenType.EOF)) {
return this.finish(node, cssErrors_1.ParseError.SemiColonExpected);
}
return this.finish(node);
}
_parseModuleConfigDeclaration() {
const node = this.create(nodes.ModuleConfiguration);
if (!node.setIdentifier(this._parseVariable())) {
return null;
}
if (!this.accept(cssScanner_1.TokenType.Colon) || !node.setValue(this._parseExpr(true))) {
return this.finish(node, cssErrors_1.ParseError.VariableValueExpected, [], [cssScanner_1.TokenType.Comma, cssScanner_1.TokenType.ParenthesisR]);
}
if (this.accept(cssScanner_1.TokenType.Exclamation)) {
if (this.hasWhitespace() || !this.acceptIdent('default')) {
return this.finish(node, cssErrors_1.ParseError.UnknownKeyword);
}
}
return this.finish(node);
}
_parseForward() {
if (!this.peekKeyword('@forward')) {
return null;
}
const node = this.create(nodes.Forward);
this.consumeToken();
if (!node.addChild(this._parseStringLiteral())) {
return this.finish(node, cssErrors_1.ParseError.StringLiteralExpected);
}
if (this.acceptIdent('as')) {
const identifier = this._parseIdent([nodes.ReferenceType.Forward]);
if (!node.setIdentifier(identifier)) {
return this.finish(node, cssErrors_1.ParseError.IdentifierExpected);
}
// Wildcard must be the next character after the identifier string.
if (this.hasWhitespace() || !this.acceptDelim('*')) {
return this.finish(node, cssErrors_1.ParseError.WildcardExpected);
}
}
if (this.acceptIdent('with')) {
if (!this.accept(cssScanner_1.TokenType.ParenthesisL)) {
return this.finish(node, cssErrors_1.ParseError.LeftParenthesisExpected, [cssScanner_1.TokenType.ParenthesisR]);
}
// First variable statement, no comma.
if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) {
return this.finish(node, cssErrors_1.ParseError.VariableNameExpected);
}
while (this.accept(cssScanner_1.TokenType.Comma)) {
if (this.peek(cssScanner_1.TokenType.ParenthesisR)) {
break;
}
if (!node.getParameters().addChild(this._parseModuleConfigDeclaration())) {
return this.finish(node, cssErrors_1.ParseError.VariableNameExpected);
}
}
if (!this.accept(cssScanner_1.TokenType.ParenthesisR)) {
return this.finish(node, cssErrors_1.ParseError.RightParenthesisExpected);
}
}
else if (this.peekIdent('hide') || this.peekIdent('show')) {
if (!node.addChild(this._parseForwardVisibility())) {
return this.finish(node, cssErrors_1.ParseError.IdentifierOrVariableExpected);
}
}
if (!this.accept(cssScanner_1.TokenType.SemiColon) && !this.accept(cssScanner_1.TokenType.EOF)) {
return this.finish(node, cssErrors_1.ParseError.SemiColonExpected);
}
return this.finish(node);
}
_parseForwardVisibility() {
const node = this.create(nodes.ForwardVisibility);
// Assume to be "hide" or "show".
node.setIdentifier(this._parseIdent());
while (node.addChild(this._parseVariable() || this._parseIdent())) {
// Consume all variables and idents ahead.
this.accept(cssScanner_1.TokenType.Comma);
}
// More than just identifier
return node.getChildren().length > 1 ? node : null;
}
_parseSupportsCondition() {
return this._parseInterpolation() || super._parseSupportsCondition();
}
}
exports.SCSSParser = SCSSParser;
});

View file

@ -0,0 +1,108 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./cssScanner"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.SCSSScanner = exports.Module = exports.Ellipsis = exports.SmallerEqualsOperator = exports.GreaterEqualsOperator = exports.NotEqualsOperator = exports.EqualsOperator = exports.Default = exports.InterpolationFunction = exports.VariableName = void 0;
const cssScanner_1 = require("./cssScanner");
const _FSL = '/'.charCodeAt(0);
const _NWL = '\n'.charCodeAt(0);
const _CAR = '\r'.charCodeAt(0);
const _LFD = '\f'.charCodeAt(0);
const _DLR = '$'.charCodeAt(0);
const _HSH = '#'.charCodeAt(0);
const _CUL = '{'.charCodeAt(0);
const _EQS = '='.charCodeAt(0);
const _BNG = '!'.charCodeAt(0);
const _LAN = '<'.charCodeAt(0);
const _RAN = '>'.charCodeAt(0);
const _DOT = '.'.charCodeAt(0);
const _ATS = '@'.charCodeAt(0);
let customTokenValue = cssScanner_1.TokenType.CustomToken;
exports.VariableName = customTokenValue++;
exports.InterpolationFunction = customTokenValue++;
exports.Default = customTokenValue++;
exports.EqualsOperator = customTokenValue++;
exports.NotEqualsOperator = customTokenValue++;
exports.GreaterEqualsOperator = customTokenValue++;
exports.SmallerEqualsOperator = customTokenValue++;
exports.Ellipsis = customTokenValue++;
exports.Module = customTokenValue++;
class SCSSScanner extends cssScanner_1.Scanner {
scanNext(offset) {
// scss variable
if (this.stream.advanceIfChar(_DLR)) {
const content = ['$'];
if (this.ident(content)) {
return this.finishToken(offset, exports.VariableName, content.join(''));
}
else {
this.stream.goBackTo(offset);
}
}
// scss: interpolation function #{..})
if (this.stream.advanceIfChars([_HSH, _CUL])) {
return this.finishToken(offset, exports.InterpolationFunction);
}
// operator ==
if (this.stream.advanceIfChars([_EQS, _EQS])) {
return this.finishToken(offset, exports.EqualsOperator);
}
// operator !=
if (this.stream.advanceIfChars([_BNG, _EQS])) {
return this.finishToken(offset, exports.NotEqualsOperator);
}
// operators <, <=
if (this.stream.advanceIfChar(_LAN)) {
if (this.stream.advanceIfChar(_EQS)) {
return this.finishToken(offset, exports.SmallerEqualsOperator);
}
return this.finishToken(offset, cssScanner_1.TokenType.Delim);
}
// ooperators >, >=
if (this.stream.advanceIfChar(_RAN)) {
if (this.stream.advanceIfChar(_EQS)) {
return this.finishToken(offset, exports.GreaterEqualsOperator);
}
return this.finishToken(offset, cssScanner_1.TokenType.Delim);
}
// ellipis
if (this.stream.advanceIfChars([_DOT, _DOT, _DOT])) {
return this.finishToken(offset, exports.Ellipsis);
}
return super.scanNext(offset);
}
comment() {
if (super.comment()) {
return true;
}
if (!this.inURL && this.stream.advanceIfChars([_FSL, _FSL])) {
this.stream.advanceWhileChar((ch) => {
switch (ch) {
case _NWL:
case _CAR:
case _LFD:
return false;
default:
return true;
}
});
return true;
}
else {
return false;
}
}
}
exports.SCSSScanner = SCSSScanner;
});

View file

@ -0,0 +1,89 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../parser/cssNodes", "../utils/strings", "../services/lintRules", "../cssLanguageTypes", "@vscode/l10n"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.CSSCodeActions = void 0;
const nodes = require("../parser/cssNodes");
const strings_1 = require("../utils/strings");
const lintRules_1 = require("../services/lintRules");
const cssLanguageTypes_1 = require("../cssLanguageTypes");
const l10n = require("@vscode/l10n");
class CSSCodeActions {
constructor(cssDataManager) {
this.cssDataManager = cssDataManager;
}
doCodeActions(document, range, context, stylesheet) {
return this.doCodeActions2(document, range, context, stylesheet).map(ca => {
const textDocumentEdit = ca.edit && ca.edit.documentChanges && ca.edit.documentChanges[0];
return cssLanguageTypes_1.Command.create(ca.title, '_css.applyCodeAction', document.uri, document.version, textDocumentEdit && textDocumentEdit.edits);
});
}
doCodeActions2(document, range, context, stylesheet) {
const result = [];
if (context.diagnostics) {
for (const diagnostic of context.diagnostics) {
this.appendFixesForMarker(document, stylesheet, diagnostic, result);
}
}
return result;
}
getFixesForUnknownProperty(document, property, marker, result) {
const propertyName = property.getName();
const candidates = [];
this.cssDataManager.getProperties().forEach(p => {
const score = (0, strings_1.difference)(propertyName, p.name);
if (score >= propertyName.length / 2 /*score_lim*/) {
candidates.push({ property: p.name, score });
}
});
// Sort in descending order.
candidates.sort((a, b) => {
return b.score - a.score || a.property.localeCompare(b.property);
});
let maxActions = 3;
for (const candidate of candidates) {
const propertyName = candidate.property;
const title = l10n.t("Rename to '{0}'", propertyName);
const edit = cssLanguageTypes_1.TextEdit.replace(marker.range, propertyName);
const documentIdentifier = cssLanguageTypes_1.VersionedTextDocumentIdentifier.create(document.uri, document.version);
const workspaceEdit = { documentChanges: [cssLanguageTypes_1.TextDocumentEdit.create(documentIdentifier, [edit])] };
const codeAction = cssLanguageTypes_1.CodeAction.create(title, workspaceEdit, cssLanguageTypes_1.CodeActionKind.QuickFix);
codeAction.diagnostics = [marker];
result.push(codeAction);
if (--maxActions <= 0) {
return;
}
}
}
appendFixesForMarker(document, stylesheet, marker, result) {
if (marker.code !== lintRules_1.Rules.UnknownProperty.id) {
return;
}
const offset = document.offsetAt(marker.range.start);
const end = document.offsetAt(marker.range.end);
const nodepath = nodes.getNodePath(stylesheet, offset);
for (let i = nodepath.length - 1; i >= 0; i--) {
const node = nodepath[i];
if (node instanceof nodes.Declaration) {
const property = node.getProperty();
if (property && property.offset === offset && property.end === end) {
this.getFixesForUnknownProperty(document, property, marker, result);
return;
}
}
}
}
}
exports.CSSCodeActions = CSSCodeActions;
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,203 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../parser/cssScanner", "../parser/scssScanner", "../parser/lessScanner"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.getFoldingRanges = void 0;
const cssScanner_1 = require("../parser/cssScanner");
const scssScanner_1 = require("../parser/scssScanner");
const lessScanner_1 = require("../parser/lessScanner");
function getFoldingRanges(document, context) {
const ranges = computeFoldingRanges(document);
return limitFoldingRanges(ranges, context);
}
exports.getFoldingRanges = getFoldingRanges;
function computeFoldingRanges(document) {
function getStartLine(t) {
return document.positionAt(t.offset).line;
}
function getEndLine(t) {
return document.positionAt(t.offset + t.len).line;
}
function getScanner() {
switch (document.languageId) {
case 'scss':
return new scssScanner_1.SCSSScanner();
case 'less':
return new lessScanner_1.LESSScanner();
default:
return new cssScanner_1.Scanner();
}
}
function tokenToRange(t, kind) {
const startLine = getStartLine(t);
const endLine = getEndLine(t);
if (startLine !== endLine) {
return {
startLine,
endLine,
kind
};
}
else {
return null;
}
}
const ranges = [];
const delimiterStack = [];
const scanner = getScanner();
scanner.ignoreComment = false;
scanner.setSource(document.getText());
let token = scanner.scan();
let prevToken = null;
while (token.type !== cssScanner_1.TokenType.EOF) {
switch (token.type) {
case cssScanner_1.TokenType.CurlyL:
case scssScanner_1.InterpolationFunction:
{
delimiterStack.push({ line: getStartLine(token), type: 'brace', isStart: true });
break;
}
case cssScanner_1.TokenType.CurlyR: {
if (delimiterStack.length !== 0) {
const prevDelimiter = popPrevStartDelimiterOfType(delimiterStack, 'brace');
if (!prevDelimiter) {
break;
}
let endLine = getEndLine(token);
if (prevDelimiter.type === 'brace') {
/**
* Other than the case when curly brace is not on a new line by itself, for example
* .foo {
* color: red; }
* Use endLine minus one to show ending curly brace
*/
if (prevToken && getEndLine(prevToken) !== endLine) {
endLine--;
}
if (prevDelimiter.line !== endLine) {
ranges.push({
startLine: prevDelimiter.line,
endLine,
kind: undefined
});
}
}
}
break;
}
/**
* In CSS, there is no single line comment prefixed with //
* All comments are marked as `Comment`
*/
case cssScanner_1.TokenType.Comment: {
const commentRegionMarkerToDelimiter = (marker) => {
if (marker === '#region') {
return { line: getStartLine(token), type: 'comment', isStart: true };
}
else {
return { line: getEndLine(token), type: 'comment', isStart: false };
}
};
const getCurrDelimiter = (token) => {
const matches = token.text.match(/^\s*\/\*\s*(#region|#endregion)\b\s*(.*?)\s*\*\//);
if (matches) {
return commentRegionMarkerToDelimiter(matches[1]);
}
else if (document.languageId === 'scss' || document.languageId === 'less') {
const matches = token.text.match(/^\s*\/\/\s*(#region|#endregion)\b\s*(.*?)\s*/);
if (matches) {
return commentRegionMarkerToDelimiter(matches[1]);
}
}
return null;
};
const currDelimiter = getCurrDelimiter(token);
// /* */ comment region folding
// All #region and #endregion cases
if (currDelimiter) {
if (currDelimiter.isStart) {
delimiterStack.push(currDelimiter);
}
else {
const prevDelimiter = popPrevStartDelimiterOfType(delimiterStack, 'comment');
if (!prevDelimiter) {
break;
}
if (prevDelimiter.type === 'comment') {
if (prevDelimiter.line !== currDelimiter.line) {
ranges.push({
startLine: prevDelimiter.line,
endLine: currDelimiter.line,
kind: 'region'
});
}
}
}
}
// Multiline comment case
else {
const range = tokenToRange(token, 'comment');
if (range) {
ranges.push(range);
}
}
break;
}
}
prevToken = token;
token = scanner.scan();
}
return ranges;
}
function popPrevStartDelimiterOfType(stack, type) {
if (stack.length === 0) {
return null;
}
for (let i = stack.length - 1; i >= 0; i--) {
if (stack[i].type === type && stack[i].isStart) {
return stack.splice(i, 1)[0];
}
}
return null;
}
/**
* - Sort regions
* - Remove invalid regions (intersections)
* - If limit exceeds, only return `rangeLimit` amount of ranges
*/
function limitFoldingRanges(ranges, context) {
const maxRanges = context && context.rangeLimit || Number.MAX_VALUE;
const sortedRanges = ranges.sort((r1, r2) => {
let diff = r1.startLine - r2.startLine;
if (diff === 0) {
diff = r1.endLine - r2.endLine;
}
return diff;
});
const validRanges = [];
let prevEndLine = -1;
sortedRanges.forEach(r => {
if (!(r.startLine < prevEndLine && prevEndLine < r.endLine)) {
validRanges.push(r);
prevEndLine = r.endLine;
}
});
if (validRanges.length < maxRanges) {
return validRanges;
}
else {
return validRanges.slice(0, maxRanges);
}
}
});

View file

@ -0,0 +1,150 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../cssLanguageTypes", "../beautify/beautify-css", "../utils/strings"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.format = void 0;
const cssLanguageTypes_1 = require("../cssLanguageTypes");
const beautify_css_1 = require("../beautify/beautify-css");
const strings_1 = require("../utils/strings");
function format(document, range, options) {
let value = document.getText();
let includesEnd = true;
let initialIndentLevel = 0;
let inRule = false;
const tabSize = options.tabSize || 4;
if (range) {
let startOffset = document.offsetAt(range.start);
// include all leading whitespace iff at the beginning of the line
let extendedStart = startOffset;
while (extendedStart > 0 && isWhitespace(value, extendedStart - 1)) {
extendedStart--;
}
if (extendedStart === 0 || isEOL(value, extendedStart - 1)) {
startOffset = extendedStart;
}
else {
// else keep at least one whitespace
if (extendedStart < startOffset) {
startOffset = extendedStart + 1;
}
}
// include all following whitespace until the end of the line
let endOffset = document.offsetAt(range.end);
let extendedEnd = endOffset;
while (extendedEnd < value.length && isWhitespace(value, extendedEnd)) {
extendedEnd++;
}
if (extendedEnd === value.length || isEOL(value, extendedEnd)) {
endOffset = extendedEnd;
}
range = cssLanguageTypes_1.Range.create(document.positionAt(startOffset), document.positionAt(endOffset));
// Test if inside a rule
inRule = isInRule(value, startOffset);
includesEnd = endOffset === value.length;
value = value.substring(startOffset, endOffset);
if (startOffset !== 0) {
const startOfLineOffset = document.offsetAt(cssLanguageTypes_1.Position.create(range.start.line, 0));
initialIndentLevel = computeIndentLevel(document.getText(), startOfLineOffset, options);
}
if (inRule) {
value = `{\n${trimLeft(value)}`;
}
}
else {
range = cssLanguageTypes_1.Range.create(cssLanguageTypes_1.Position.create(0, 0), document.positionAt(value.length));
}
const cssOptions = {
indent_size: tabSize,
indent_char: options.insertSpaces ? ' ' : '\t',
end_with_newline: includesEnd && getFormatOption(options, 'insertFinalNewline', false),
selector_separator_newline: getFormatOption(options, 'newlineBetweenSelectors', true),
newline_between_rules: getFormatOption(options, 'newlineBetweenRules', true),
space_around_selector_separator: getFormatOption(options, 'spaceAroundSelectorSeparator', false),
brace_style: getFormatOption(options, 'braceStyle', 'collapse'),
indent_empty_lines: getFormatOption(options, 'indentEmptyLines', false),
max_preserve_newlines: getFormatOption(options, 'maxPreserveNewLines', undefined),
preserve_newlines: getFormatOption(options, 'preserveNewLines', true),
wrap_line_length: getFormatOption(options, 'wrapLineLength', undefined),
eol: '\n'
};
let result = (0, beautify_css_1.css_beautify)(value, cssOptions);
if (inRule) {
result = trimLeft(result.substring(2));
}
if (initialIndentLevel > 0) {
const indent = options.insertSpaces ? (0, strings_1.repeat)(' ', tabSize * initialIndentLevel) : (0, strings_1.repeat)('\t', initialIndentLevel);
result = result.split('\n').join('\n' + indent);
if (range.start.character === 0) {
result = indent + result; // keep the indent
}
}
return [{
range: range,
newText: result
}];
}
exports.format = format;
function trimLeft(str) {
return str.replace(/^\s+/, '');
}
const _CUL = '{'.charCodeAt(0);
const _CUR = '}'.charCodeAt(0);
function isInRule(str, offset) {
while (offset >= 0) {
const ch = str.charCodeAt(offset);
if (ch === _CUL) {
return true;
}
else if (ch === _CUR) {
return false;
}
offset--;
}
return false;
}
function getFormatOption(options, key, dflt) {
if (options && options.hasOwnProperty(key)) {
const value = options[key];
if (value !== null) {
return value;
}
}
return dflt;
}
function computeIndentLevel(content, offset, options) {
let i = offset;
let nChars = 0;
const tabSize = options.tabSize || 4;
while (i < content.length) {
const ch = content.charAt(i);
if (ch === ' ') {
nChars++;
}
else if (ch === '\t') {
nChars += tabSize;
}
else {
break;
}
i++;
}
return Math.floor(nChars / tabSize);
}
function isEOL(text, offset) {
return '\r\n'.indexOf(text.charAt(offset)) !== -1;
}
function isWhitespace(text, offset) {
return ' \t'.indexOf(text.charAt(offset)) !== -1;
}
});

View file

@ -0,0 +1,161 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../parser/cssNodes", "../languageFacts/facts", "./selectorPrinting", "../utils/strings", "../cssLanguageTypes", "../utils/objects"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.CSSHover = void 0;
const nodes = require("../parser/cssNodes");
const languageFacts = require("../languageFacts/facts");
const selectorPrinting_1 = require("./selectorPrinting");
const strings_1 = require("../utils/strings");
const cssLanguageTypes_1 = require("../cssLanguageTypes");
const objects_1 = require("../utils/objects");
class CSSHover {
constructor(clientCapabilities, cssDataManager) {
this.clientCapabilities = clientCapabilities;
this.cssDataManager = cssDataManager;
this.selectorPrinting = new selectorPrinting_1.SelectorPrinting(cssDataManager);
}
configure(settings) {
this.defaultSettings = settings;
}
doHover(document, position, stylesheet, settings = this.defaultSettings) {
function getRange(node) {
return cssLanguageTypes_1.Range.create(document.positionAt(node.offset), document.positionAt(node.end));
}
const offset = document.offsetAt(position);
const nodepath = nodes.getNodePath(stylesheet, offset);
/**
* nodepath is top-down
* Build up the hover by appending inner node's information
*/
let hover = null;
for (let i = 0; i < nodepath.length; i++) {
const node = nodepath[i];
if (node instanceof nodes.Selector) {
hover = {
contents: this.selectorPrinting.selectorToMarkedString(node),
range: getRange(node)
};
break;
}
if (node instanceof nodes.SimpleSelector) {
/**
* Some sass specific at rules such as `@at-root` are parsed as `SimpleSelector`
*/
if (!(0, strings_1.startsWith)(node.getText(), '@')) {
hover = {
contents: this.selectorPrinting.simpleSelectorToMarkedString(node),
range: getRange(node)
};
}
break;
}
if (node instanceof nodes.Declaration) {
const propertyName = node.getFullPropertyName();
const entry = this.cssDataManager.getProperty(propertyName);
if (entry) {
const contents = languageFacts.getEntryDescription(entry, this.doesSupportMarkdown(), settings);
if (contents) {
hover = {
contents,
range: getRange(node)
};
}
else {
hover = null;
}
}
continue;
}
if (node instanceof nodes.UnknownAtRule) {
const atRuleName = node.getText();
const entry = this.cssDataManager.getAtDirective(atRuleName);
if (entry) {
const contents = languageFacts.getEntryDescription(entry, this.doesSupportMarkdown(), settings);
if (contents) {
hover = {
contents,
range: getRange(node)
};
}
else {
hover = null;
}
}
continue;
}
if (node instanceof nodes.Node && node.type === nodes.NodeType.PseudoSelector) {
const selectorName = node.getText();
const entry = selectorName.slice(0, 2) === '::'
? this.cssDataManager.getPseudoElement(selectorName)
: this.cssDataManager.getPseudoClass(selectorName);
if (entry) {
const contents = languageFacts.getEntryDescription(entry, this.doesSupportMarkdown(), settings);
if (contents) {
hover = {
contents,
range: getRange(node)
};
}
else {
hover = null;
}
}
continue;
}
}
if (hover) {
hover.contents = this.convertContents(hover.contents);
}
return hover;
}
convertContents(contents) {
if (!this.doesSupportMarkdown()) {
if (typeof contents === 'string') {
return contents;
}
// MarkupContent
else if ('kind' in contents) {
return {
kind: 'plaintext',
value: contents.value
};
}
// MarkedString[]
else if (Array.isArray(contents)) {
return contents.map(c => {
return typeof c === 'string' ? c : c.value;
});
}
// MarkedString
else {
return contents.value;
}
}
return contents;
}
doesSupportMarkdown() {
if (!(0, objects_1.isDefined)(this.supportsMarkdown)) {
if (!(0, objects_1.isDefined)(this.clientCapabilities)) {
this.supportsMarkdown = true;
return this.supportsMarkdown;
}
const hover = this.clientCapabilities.textDocument && this.clientCapabilities.textDocument.hover;
this.supportsMarkdown = hover && hover.contentFormat && Array.isArray(hover.contentFormat) && hover.contentFormat.indexOf(cssLanguageTypes_1.MarkupKind.Markdown) !== -1;
}
return this.supportsMarkdown;
}
}
exports.CSSHover = CSSHover;
});

View file

@ -0,0 +1,476 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../cssLanguageTypes", "@vscode/l10n", "../parser/cssNodes", "../parser/cssSymbolScope", "../languageFacts/facts", "../utils/strings", "../utils/resources"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.CSSNavigation = void 0;
const cssLanguageTypes_1 = require("../cssLanguageTypes");
const l10n = require("@vscode/l10n");
const nodes = require("../parser/cssNodes");
const cssSymbolScope_1 = require("../parser/cssSymbolScope");
const facts_1 = require("../languageFacts/facts");
const strings_1 = require("../utils/strings");
const resources_1 = require("../utils/resources");
const startsWithSchemeRegex = /^\w+:\/\//;
const startsWithData = /^data:/;
class CSSNavigation {
constructor(fileSystemProvider, resolveModuleReferences) {
this.fileSystemProvider = fileSystemProvider;
this.resolveModuleReferences = resolveModuleReferences;
}
findDefinition(document, position, stylesheet) {
const symbols = new cssSymbolScope_1.Symbols(stylesheet);
const offset = document.offsetAt(position);
const node = nodes.getNodeAtOffset(stylesheet, offset);
if (!node) {
return null;
}
const symbol = symbols.findSymbolFromNode(node);
if (!symbol) {
return null;
}
return {
uri: document.uri,
range: getRange(symbol.node, document)
};
}
findReferences(document, position, stylesheet) {
const highlights = this.findDocumentHighlights(document, position, stylesheet);
return highlights.map(h => {
return {
uri: document.uri,
range: h.range
};
});
}
getHighlightNode(document, position, stylesheet) {
const offset = document.offsetAt(position);
let node = nodes.getNodeAtOffset(stylesheet, offset);
if (!node || node.type === nodes.NodeType.Stylesheet || node.type === nodes.NodeType.Declarations) {
return;
}
if (node.type === nodes.NodeType.Identifier && node.parent && node.parent.type === nodes.NodeType.ClassSelector) {
node = node.parent;
}
return node;
}
findDocumentHighlights(document, position, stylesheet) {
const result = [];
const node = this.getHighlightNode(document, position, stylesheet);
if (!node) {
return result;
}
const symbols = new cssSymbolScope_1.Symbols(stylesheet);
const symbol = symbols.findSymbolFromNode(node);
const name = node.getText();
stylesheet.accept(candidate => {
if (symbol) {
if (symbols.matchesSymbol(candidate, symbol)) {
result.push({
kind: getHighlightKind(candidate),
range: getRange(candidate, document)
});
return false;
}
}
else if (node && node.type === candidate.type && candidate.matches(name)) {
// Same node type and data
result.push({
kind: getHighlightKind(candidate),
range: getRange(candidate, document)
});
}
return true;
});
return result;
}
isRawStringDocumentLinkNode(node) {
return node.type === nodes.NodeType.Import;
}
findDocumentLinks(document, stylesheet, documentContext) {
const linkData = this.findUnresolvedLinks(document, stylesheet);
const resolvedLinks = [];
for (let data of linkData) {
const link = data.link;
const target = link.target;
if (!target || startsWithData.test(target)) {
// no links for data:
}
else if (startsWithSchemeRegex.test(target)) {
resolvedLinks.push(link);
}
else {
const resolved = documentContext.resolveReference(target, document.uri);
if (resolved) {
link.target = resolved;
}
resolvedLinks.push(link);
}
}
return resolvedLinks;
}
async findDocumentLinks2(document, stylesheet, documentContext) {
const linkData = this.findUnresolvedLinks(document, stylesheet);
const resolvedLinks = [];
for (let data of linkData) {
const link = data.link;
const target = link.target;
if (!target || startsWithData.test(target)) {
// no links for data:
}
else if (startsWithSchemeRegex.test(target)) {
resolvedLinks.push(link);
}
else {
const resolvedTarget = await this.resolveReference(target, document.uri, documentContext, data.isRawLink);
if (resolvedTarget !== undefined) {
link.target = resolvedTarget;
resolvedLinks.push(link);
}
}
}
return resolvedLinks;
}
findUnresolvedLinks(document, stylesheet) {
const result = [];
const collect = (uriStringNode) => {
let rawUri = uriStringNode.getText();
const range = getRange(uriStringNode, document);
// Make sure the range is not empty
if (range.start.line === range.end.line && range.start.character === range.end.character) {
return;
}
if ((0, strings_1.startsWith)(rawUri, `'`) || (0, strings_1.startsWith)(rawUri, `"`)) {
rawUri = rawUri.slice(1, -1);
}
const isRawLink = uriStringNode.parent ? this.isRawStringDocumentLinkNode(uriStringNode.parent) : false;
result.push({ link: { target: rawUri, range }, isRawLink });
};
stylesheet.accept(candidate => {
if (candidate.type === nodes.NodeType.URILiteral) {
const first = candidate.getChild(0);
if (first) {
collect(first);
}
return false;
}
/**
* In @import, it is possible to include links that do not use `url()`
* For example, `@import 'foo.css';`
*/
if (candidate.parent && this.isRawStringDocumentLinkNode(candidate.parent)) {
const rawText = candidate.getText();
if ((0, strings_1.startsWith)(rawText, `'`) || (0, strings_1.startsWith)(rawText, `"`)) {
collect(candidate);
}
return false;
}
return true;
});
return result;
}
findSymbolInformations(document, stylesheet) {
const result = [];
const addSymbolInformation = (name, kind, symbolNodeOrRange) => {
const range = symbolNodeOrRange instanceof nodes.Node ? getRange(symbolNodeOrRange, document) : symbolNodeOrRange;
const entry = {
name: name || l10n.t('<undefined>'),
kind,
location: cssLanguageTypes_1.Location.create(document.uri, range)
};
result.push(entry);
};
this.collectDocumentSymbols(document, stylesheet, addSymbolInformation);
return result;
}
findDocumentSymbols(document, stylesheet) {
const result = [];
const parents = [];
const addDocumentSymbol = (name, kind, symbolNodeOrRange, nameNodeOrRange, bodyNode) => {
const range = symbolNodeOrRange instanceof nodes.Node ? getRange(symbolNodeOrRange, document) : symbolNodeOrRange;
let selectionRange = nameNodeOrRange instanceof nodes.Node ? getRange(nameNodeOrRange, document) : nameNodeOrRange;
if (!selectionRange || !containsRange(range, selectionRange)) {
selectionRange = cssLanguageTypes_1.Range.create(range.start, range.start);
}
const entry = {
name: name || l10n.t('<undefined>'),
kind,
range,
selectionRange
};
let top = parents.pop();
while (top && !containsRange(top[1], range)) {
top = parents.pop();
}
if (top) {
const topSymbol = top[0];
if (!topSymbol.children) {
topSymbol.children = [];
}
topSymbol.children.push(entry);
parents.push(top); // put back top
}
else {
result.push(entry);
}
if (bodyNode) {
parents.push([entry, getRange(bodyNode, document)]);
}
};
this.collectDocumentSymbols(document, stylesheet, addDocumentSymbol);
return result;
}
collectDocumentSymbols(document, stylesheet, collect) {
stylesheet.accept(node => {
if (node instanceof nodes.RuleSet) {
for (const selector of node.getSelectors().getChildren()) {
if (selector instanceof nodes.Selector) {
const range = cssLanguageTypes_1.Range.create(document.positionAt(selector.offset), document.positionAt(node.end));
collect(selector.getText(), cssLanguageTypes_1.SymbolKind.Class, range, selector, node.getDeclarations());
}
}
}
else if (node instanceof nodes.VariableDeclaration) {
collect(node.getName(), cssLanguageTypes_1.SymbolKind.Variable, node, node.getVariable(), undefined);
}
else if (node instanceof nodes.MixinDeclaration) {
collect(node.getName(), cssLanguageTypes_1.SymbolKind.Method, node, node.getIdentifier(), node.getDeclarations());
}
else if (node instanceof nodes.FunctionDeclaration) {
collect(node.getName(), cssLanguageTypes_1.SymbolKind.Function, node, node.getIdentifier(), node.getDeclarations());
}
else if (node instanceof nodes.Keyframe) {
const name = l10n.t("@keyframes {0}", node.getName());
collect(name, cssLanguageTypes_1.SymbolKind.Class, node, node.getIdentifier(), node.getDeclarations());
}
else if (node instanceof nodes.FontFace) {
const name = l10n.t("@font-face");
collect(name, cssLanguageTypes_1.SymbolKind.Class, node, undefined, node.getDeclarations());
}
else if (node instanceof nodes.Media) {
const mediaList = node.getChild(0);
if (mediaList instanceof nodes.Medialist) {
const name = '@media ' + mediaList.getText();
collect(name, cssLanguageTypes_1.SymbolKind.Module, node, mediaList, node.getDeclarations());
}
}
return true;
});
}
findDocumentColors(document, stylesheet) {
const result = [];
stylesheet.accept((node) => {
const colorInfo = getColorInformation(node, document);
if (colorInfo) {
result.push(colorInfo);
}
return true;
});
return result;
}
getColorPresentations(document, stylesheet, color, range) {
const result = [];
const red256 = Math.round(color.red * 255), green256 = Math.round(color.green * 255), blue256 = Math.round(color.blue * 255);
let label;
if (color.alpha === 1) {
label = `rgb(${red256}, ${green256}, ${blue256})`;
}
else {
label = `rgba(${red256}, ${green256}, ${blue256}, ${color.alpha})`;
}
result.push({ label: label, textEdit: cssLanguageTypes_1.TextEdit.replace(range, label) });
if (color.alpha === 1) {
label = `#${toTwoDigitHex(red256)}${toTwoDigitHex(green256)}${toTwoDigitHex(blue256)}`;
}
else {
label = `#${toTwoDigitHex(red256)}${toTwoDigitHex(green256)}${toTwoDigitHex(blue256)}${toTwoDigitHex(Math.round(color.alpha * 255))}`;
}
result.push({ label: label, textEdit: cssLanguageTypes_1.TextEdit.replace(range, label) });
const hsl = (0, facts_1.hslFromColor)(color);
if (hsl.a === 1) {
label = `hsl(${hsl.h}, ${Math.round(hsl.s * 100)}%, ${Math.round(hsl.l * 100)}%)`;
}
else {
label = `hsla(${hsl.h}, ${Math.round(hsl.s * 100)}%, ${Math.round(hsl.l * 100)}%, ${hsl.a})`;
}
result.push({ label: label, textEdit: cssLanguageTypes_1.TextEdit.replace(range, label) });
const hwb = (0, facts_1.hwbFromColor)(color);
if (hwb.a === 1) {
label = `hwb(${hwb.h} ${Math.round(hwb.w * 100)}% ${Math.round(hwb.b * 100)}%)`;
}
else {
label = `hwb(${hwb.h} ${Math.round(hwb.w * 100)}% ${Math.round(hwb.b * 100)}% / ${hwb.a})`;
}
result.push({ label: label, textEdit: cssLanguageTypes_1.TextEdit.replace(range, label) });
return result;
}
prepareRename(document, position, stylesheet) {
const node = this.getHighlightNode(document, position, stylesheet);
if (node) {
return cssLanguageTypes_1.Range.create(document.positionAt(node.offset), document.positionAt(node.end));
}
}
doRename(document, position, newName, stylesheet) {
const highlights = this.findDocumentHighlights(document, position, stylesheet);
const edits = highlights.map(h => cssLanguageTypes_1.TextEdit.replace(h.range, newName));
return {
changes: { [document.uri]: edits }
};
}
async resolveModuleReference(ref, documentUri, documentContext) {
if ((0, strings_1.startsWith)(documentUri, 'file://')) {
const moduleName = getModuleNameFromPath(ref);
if (moduleName && moduleName !== '.' && moduleName !== '..') {
const rootFolderUri = documentContext.resolveReference('/', documentUri);
const documentFolderUri = (0, resources_1.dirname)(documentUri);
const modulePath = await this.resolvePathToModule(moduleName, documentFolderUri, rootFolderUri);
if (modulePath) {
const pathWithinModule = ref.substring(moduleName.length + 1);
return (0, resources_1.joinPath)(modulePath, pathWithinModule);
}
}
}
return undefined;
}
async mapReference(target, isRawLink) {
return target;
}
async resolveReference(target, documentUri, documentContext, isRawLink = false) {
// Following [css-loader](https://github.com/webpack-contrib/css-loader#url)
// and [sass-loader's](https://github.com/webpack-contrib/sass-loader#imports)
// convention, if an import path starts with ~ then use node module resolution
// *unless* it starts with "~/" as this refers to the user's home directory.
if (target[0] === '~' && target[1] !== '/' && this.fileSystemProvider) {
target = target.substring(1);
return this.mapReference(await this.resolveModuleReference(target, documentUri, documentContext), isRawLink);
}
const ref = await this.mapReference(documentContext.resolveReference(target, documentUri), isRawLink);
// Following [less-loader](https://github.com/webpack-contrib/less-loader#imports)
// and [sass-loader's](https://github.com/webpack-contrib/sass-loader#resolving-import-at-rules)
// new resolving import at-rules (~ is deprecated). The loader will first try to resolve @import as a relative path. If it cannot be resolved,
// then the loader will try to resolve @import inside node_modules.
if (this.resolveModuleReferences) {
if (ref && await this.fileExists(ref)) {
return ref;
}
const moduleReference = await this.mapReference(await this.resolveModuleReference(target, documentUri, documentContext), isRawLink);
if (moduleReference) {
return moduleReference;
}
}
// fall back. it might not exists
return ref;
}
async resolvePathToModule(_moduleName, documentFolderUri, rootFolderUri) {
// resolve the module relative to the document. We can't use `require` here as the code is webpacked.
const packPath = (0, resources_1.joinPath)(documentFolderUri, 'node_modules', _moduleName, 'package.json');
if (await this.fileExists(packPath)) {
return (0, resources_1.dirname)(packPath);
}
else if (rootFolderUri && documentFolderUri.startsWith(rootFolderUri) && (documentFolderUri.length !== rootFolderUri.length)) {
return this.resolvePathToModule(_moduleName, (0, resources_1.dirname)(documentFolderUri), rootFolderUri);
}
return undefined;
}
async fileExists(uri) {
if (!this.fileSystemProvider) {
return false;
}
try {
const stat = await this.fileSystemProvider.stat(uri);
if (stat.type === cssLanguageTypes_1.FileType.Unknown && stat.size === -1) {
return false;
}
return true;
}
catch (err) {
return false;
}
}
}
exports.CSSNavigation = CSSNavigation;
function getColorInformation(node, document) {
const color = (0, facts_1.getColorValue)(node);
if (color) {
const range = getRange(node, document);
return { color, range };
}
return null;
}
function getRange(node, document) {
return cssLanguageTypes_1.Range.create(document.positionAt(node.offset), document.positionAt(node.end));
}
/**
* Test if `otherRange` is in `range`. If the ranges are equal, will return true.
*/
function containsRange(range, otherRange) {
const otherStartLine = otherRange.start.line, otherEndLine = otherRange.end.line;
const rangeStartLine = range.start.line, rangeEndLine = range.end.line;
if (otherStartLine < rangeStartLine || otherEndLine < rangeStartLine) {
return false;
}
if (otherStartLine > rangeEndLine || otherEndLine > rangeEndLine) {
return false;
}
if (otherStartLine === rangeStartLine && otherRange.start.character < range.start.character) {
return false;
}
if (otherEndLine === rangeEndLine && otherRange.end.character > range.end.character) {
return false;
}
return true;
}
function getHighlightKind(node) {
if (node.type === nodes.NodeType.Selector) {
return cssLanguageTypes_1.DocumentHighlightKind.Write;
}
if (node instanceof nodes.Identifier) {
if (node.parent && node.parent instanceof nodes.Property) {
if (node.isCustomProperty) {
return cssLanguageTypes_1.DocumentHighlightKind.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 cssLanguageTypes_1.DocumentHighlightKind.Write;
}
}
return cssLanguageTypes_1.DocumentHighlightKind.Read;
}
function toTwoDigitHex(n) {
const r = n.toString(16);
return r.length !== 2 ? '0' + r : r;
}
function getModuleNameFromPath(path) {
const firstSlash = path.indexOf('/');
if (firstSlash === -1) {
return '';
}
// If a scoped module (starts with @) then get up until second instance of '/', or to the end of the string for root-level imports.
if (path[0] === '@') {
const secondSlash = path.indexOf('/', firstSlash + 1);
if (secondSlash === -1) {
return path;
}
return path.substring(0, secondSlash);
}
// Otherwise get until first instance of '/'
return path.substring(0, firstSlash);
}
});

View file

@ -0,0 +1,60 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../cssLanguageTypes", "../parser/cssNodes"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSelectionRanges = void 0;
const cssLanguageTypes_1 = require("../cssLanguageTypes");
const cssNodes_1 = require("../parser/cssNodes");
function getSelectionRanges(document, positions, stylesheet) {
function getSelectionRange(position) {
const applicableRanges = getApplicableRanges(position);
let current = undefined;
for (let index = applicableRanges.length - 1; index >= 0; index--) {
current = cssLanguageTypes_1.SelectionRange.create(cssLanguageTypes_1.Range.create(document.positionAt(applicableRanges[index][0]), document.positionAt(applicableRanges[index][1])), current);
}
if (!current) {
current = cssLanguageTypes_1.SelectionRange.create(cssLanguageTypes_1.Range.create(position, position));
}
return current;
}
return positions.map(getSelectionRange);
function getApplicableRanges(position) {
const offset = document.offsetAt(position);
let currNode = stylesheet.findChildAtOffset(offset, true);
if (!currNode) {
return [];
}
const result = [];
while (currNode) {
if (currNode.parent &&
currNode.offset === currNode.parent.offset &&
currNode.end === currNode.parent.end) {
currNode = currNode.parent;
continue;
}
// The `{ }` part of `.a { }`
if (currNode.type === cssNodes_1.NodeType.Declarations) {
if (offset > currNode.offset && offset < currNode.end) {
// Return `{ }` and the range inside `{` and `}`
result.push([currNode.offset + 1, currNode.end - 1]);
}
}
result.push([currNode.offset, currNode.end]);
currNode = currNode.parent;
}
return result;
}
}
exports.getSelectionRanges = getSelectionRanges;
});

View file

@ -0,0 +1,54 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../parser/cssNodes", "./lintRules", "./lint", "../cssLanguageTypes"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.CSSValidation = void 0;
const nodes = require("../parser/cssNodes");
const lintRules_1 = require("./lintRules");
const lint_1 = require("./lint");
const cssLanguageTypes_1 = require("../cssLanguageTypes");
class CSSValidation {
constructor(cssDataManager) {
this.cssDataManager = cssDataManager;
}
configure(settings) {
this.settings = settings;
}
doValidation(document, stylesheet, settings = this.settings) {
if (settings && settings.validate === false) {
return [];
}
const entries = [];
entries.push.apply(entries, nodes.ParseErrorCollector.entries(stylesheet));
entries.push.apply(entries, lint_1.LintVisitor.entries(stylesheet, document, new lintRules_1.LintConfigurationSettings(settings && settings.lint), this.cssDataManager));
const ruleIds = [];
for (const r in lintRules_1.Rules) {
ruleIds.push(lintRules_1.Rules[r].id);
}
function toDiagnostic(marker) {
const range = cssLanguageTypes_1.Range.create(document.positionAt(marker.getOffset()), document.positionAt(marker.getOffset() + marker.getLength()));
const source = document.languageId;
return {
code: marker.getRule().id,
source: source,
message: marker.getMessage(),
severity: marker.getLevel() === nodes.Level.Warning ? cssLanguageTypes_1.DiagnosticSeverity.Warning : cssLanguageTypes_1.DiagnosticSeverity.Error,
range: range
};
}
return entries.filter(entry => entry.getLevel() !== nodes.Level.Ignore).map(toDiagnostic);
}
}
exports.CSSValidation = CSSValidation;
});

View file

@ -0,0 +1,390 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./cssCompletion", "../cssLanguageTypes", "@vscode/l10n"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.LESSCompletion = void 0;
const cssCompletion_1 = require("./cssCompletion");
const cssLanguageTypes_1 = require("../cssLanguageTypes");
const l10n = require("@vscode/l10n");
class LESSCompletion extends cssCompletion_1.CSSCompletion {
constructor(lsOptions, cssDataManager) {
super('@', lsOptions, cssDataManager);
}
createFunctionProposals(proposals, existingNode, sortToEnd, result) {
for (const p of proposals) {
const item = {
label: p.name,
detail: p.example,
documentation: p.description,
textEdit: cssLanguageTypes_1.TextEdit.replace(this.getCompletionRange(existingNode), p.name + '($0)'),
insertTextFormat: cssLanguageTypes_1.InsertTextFormat.Snippet,
kind: cssLanguageTypes_1.CompletionItemKind.Function
};
if (sortToEnd) {
item.sortText = 'z';
}
result.items.push(item);
}
return result;
}
getTermProposals(entry, existingNode, result) {
let functions = LESSCompletion.builtInProposals;
if (entry) {
functions = functions.filter(f => !f.type || !entry.restrictions || entry.restrictions.indexOf(f.type) !== -1);
}
this.createFunctionProposals(functions, existingNode, true, result);
return super.getTermProposals(entry, existingNode, result);
}
getColorProposals(entry, existingNode, result) {
this.createFunctionProposals(LESSCompletion.colorProposals, existingNode, false, result);
return super.getColorProposals(entry, existingNode, result);
}
getCompletionsForDeclarationProperty(declaration, result) {
this.getCompletionsForSelector(null, true, result);
return super.getCompletionsForDeclarationProperty(declaration, result);
}
}
LESSCompletion.builtInProposals = [
// Boolean functions
{
'name': 'if',
'example': 'if(condition, trueValue [, falseValue]);',
'description': l10n.t('returns one of two values depending on a condition.')
},
{
'name': 'boolean',
'example': 'boolean(condition);',
'description': l10n.t('"store" a boolean test for later evaluation in a guard or if().')
},
// List functions
{
'name': 'length',
'example': 'length(@list);',
'description': l10n.t('returns the number of elements in a value list')
},
{
'name': 'extract',
'example': 'extract(@list, index);',
'description': l10n.t('returns a value at the specified position in the list')
},
{
'name': 'range',
'example': 'range([start, ] end [, step]);',
'description': l10n.t('generate a list spanning a range of values')
},
{
'name': 'each',
'example': 'each(@list, ruleset);',
'description': l10n.t('bind the evaluation of a ruleset to each member of a list.')
},
// Other built-ins
{
'name': 'escape',
'example': 'escape(@string);',
'description': l10n.t('URL encodes a string')
},
{
'name': 'e',
'example': 'e(@string);',
'description': l10n.t('escape string content')
},
{
'name': 'replace',
'example': 'replace(@string, @pattern, @replacement[, @flags]);',
'description': l10n.t('string replace')
},
{
'name': 'unit',
'example': 'unit(@dimension, [@unit: \'\']);',
'description': l10n.t('remove or change the unit of a dimension')
},
{
'name': 'color',
'example': 'color(@string);',
'description': l10n.t('parses a string to a color'),
'type': 'color'
},
{
'name': 'convert',
'example': 'convert(@value, unit);',
'description': l10n.t('converts numbers from one type into another')
},
{
'name': 'data-uri',
'example': 'data-uri([mimetype,] url);',
'description': l10n.t('inlines a resource and falls back to `url()`'),
'type': 'url'
},
{
'name': 'abs',
'description': l10n.t('absolute value of a number'),
'example': 'abs(number);'
},
{
'name': 'acos',
'description': l10n.t('arccosine - inverse of cosine function'),
'example': 'acos(number);'
},
{
'name': 'asin',
'description': l10n.t('arcsine - inverse of sine function'),
'example': 'asin(number);'
},
{
'name': 'ceil',
'example': 'ceil(@number);',
'description': l10n.t('rounds up to an integer')
},
{
'name': 'cos',
'description': l10n.t('cosine function'),
'example': 'cos(number);'
},
{
'name': 'floor',
'description': l10n.t('rounds down to an integer'),
'example': 'floor(@number);'
},
{
'name': 'percentage',
'description': l10n.t('converts to a %, e.g. 0.5 > 50%'),
'example': 'percentage(@number);',
'type': 'percentage'
},
{
'name': 'round',
'description': l10n.t('rounds a number to a number of places'),
'example': 'round(number, [places: 0]);'
},
{
'name': 'sqrt',
'description': l10n.t('calculates square root of a number'),
'example': 'sqrt(number);'
},
{
'name': 'sin',
'description': l10n.t('sine function'),
'example': 'sin(number);'
},
{
'name': 'tan',
'description': l10n.t('tangent function'),
'example': 'tan(number);'
},
{
'name': 'atan',
'description': l10n.t('arctangent - inverse of tangent function'),
'example': 'atan(number);'
},
{
'name': 'pi',
'description': l10n.t('returns pi'),
'example': 'pi();'
},
{
'name': 'pow',
'description': l10n.t('first argument raised to the power of the second argument'),
'example': 'pow(@base, @exponent);'
},
{
'name': 'mod',
'description': l10n.t('first argument modulus second argument'),
'example': 'mod(number, number);'
},
{
'name': 'min',
'description': l10n.t('returns the lowest of one or more values'),
'example': 'min(@x, @y);'
},
{
'name': 'max',
'description': l10n.t('returns the lowest of one or more values'),
'example': 'max(@x, @y);'
}
];
LESSCompletion.colorProposals = [
{
'name': 'argb',
'example': 'argb(@color);',
'description': l10n.t('creates a #AARRGGBB')
},
{
'name': 'hsl',
'example': 'hsl(@hue, @saturation, @lightness);',
'description': l10n.t('creates a color')
},
{
'name': 'hsla',
'example': 'hsla(@hue, @saturation, @lightness, @alpha);',
'description': l10n.t('creates a color')
},
{
'name': 'hsv',
'example': 'hsv(@hue, @saturation, @value);',
'description': l10n.t('creates a color')
},
{
'name': 'hsva',
'example': 'hsva(@hue, @saturation, @value, @alpha);',
'description': l10n.t('creates a color')
},
{
'name': 'hue',
'example': 'hue(@color);',
'description': l10n.t('returns the `hue` channel of `@color` in the HSL space')
},
{
'name': 'saturation',
'example': 'saturation(@color);',
'description': l10n.t('returns the `saturation` channel of `@color` in the HSL space')
},
{
'name': 'lightness',
'example': 'lightness(@color);',
'description': l10n.t('returns the `lightness` channel of `@color` in the HSL space')
},
{
'name': 'hsvhue',
'example': 'hsvhue(@color);',
'description': l10n.t('returns the `hue` channel of `@color` in the HSV space')
},
{
'name': 'hsvsaturation',
'example': 'hsvsaturation(@color);',
'description': l10n.t('returns the `saturation` channel of `@color` in the HSV space')
},
{
'name': 'hsvvalue',
'example': 'hsvvalue(@color);',
'description': l10n.t('returns the `value` channel of `@color` in the HSV space')
},
{
'name': 'red',
'example': 'red(@color);',
'description': l10n.t('returns the `red` channel of `@color`')
},
{
'name': 'green',
'example': 'green(@color);',
'description': l10n.t('returns the `green` channel of `@color`')
},
{
'name': 'blue',
'example': 'blue(@color);',
'description': l10n.t('returns the `blue` channel of `@color`')
},
{
'name': 'alpha',
'example': 'alpha(@color);',
'description': l10n.t('returns the `alpha` channel of `@color`')
},
{
'name': 'luma',
'example': 'luma(@color);',
'description': l10n.t('returns the `luma` value (perceptual brightness) of `@color`')
},
{
'name': 'saturate',
'example': 'saturate(@color, 10%);',
'description': l10n.t('return `@color` 10% points more saturated')
},
{
'name': 'desaturate',
'example': 'desaturate(@color, 10%);',
'description': l10n.t('return `@color` 10% points less saturated')
},
{
'name': 'lighten',
'example': 'lighten(@color, 10%);',
'description': l10n.t('return `@color` 10% points lighter')
},
{
'name': 'darken',
'example': 'darken(@color, 10%);',
'description': l10n.t('return `@color` 10% points darker')
},
{
'name': 'fadein',
'example': 'fadein(@color, 10%);',
'description': l10n.t('return `@color` 10% points less transparent')
},
{
'name': 'fadeout',
'example': 'fadeout(@color, 10%);',
'description': l10n.t('return `@color` 10% points more transparent')
},
{
'name': 'fade',
'example': 'fade(@color, 50%);',
'description': l10n.t('return `@color` with 50% transparency')
},
{
'name': 'spin',
'example': 'spin(@color, 10);',
'description': l10n.t('return `@color` with a 10 degree larger in hue')
},
{
'name': 'mix',
'example': 'mix(@color1, @color2, [@weight: 50%]);',
'description': l10n.t('return a mix of `@color1` and `@color2`')
},
{
'name': 'greyscale',
'example': 'greyscale(@color);',
'description': l10n.t('returns a grey, 100% desaturated color'),
},
{
'name': 'contrast',
'example': 'contrast(@color1, [@darkcolor: black], [@lightcolor: white], [@threshold: 43%]);',
'description': l10n.t('return `@darkcolor` if `@color1 is> 43% luma` otherwise return `@lightcolor`, see notes')
},
{
'name': 'multiply',
'example': 'multiply(@color1, @color2);'
},
{
'name': 'screen',
'example': 'screen(@color1, @color2);'
},
{
'name': 'overlay',
'example': 'overlay(@color1, @color2);'
},
{
'name': 'softlight',
'example': 'softlight(@color1, @color2);'
},
{
'name': 'hardlight',
'example': 'hardlight(@color1, @color2);'
},
{
'name': 'difference',
'example': 'difference(@color1, @color2);'
},
{
'name': 'exclusion',
'example': 'exclusion(@color1, @color2);'
},
{
'name': 'average',
'example': 'average(@color1, @color2);'
},
{
'name': 'negation',
'example': 'negation(@color1, @color2);'
}
];
exports.LESSCompletion = LESSCompletion;
});

View file

@ -0,0 +1,577 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "@vscode/l10n", "../languageFacts/facts", "../parser/cssNodes", "../utils/arrays", "./lintRules", "./lintUtil"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.LintVisitor = void 0;
const l10n = require("@vscode/l10n");
const languageFacts = require("../languageFacts/facts");
const nodes = require("../parser/cssNodes");
const arrays_1 = require("../utils/arrays");
const lintRules_1 = require("./lintRules");
const lintUtil_1 = require("./lintUtil");
class NodesByRootMap {
constructor() {
this.data = {};
}
add(root, name, node) {
let entry = this.data[root];
if (!entry) {
entry = { nodes: [], names: [] };
this.data[root] = entry;
}
entry.names.push(name);
if (node) {
entry.nodes.push(node);
}
}
}
class LintVisitor {
static entries(node, document, settings, cssDataManager, entryFilter) {
const visitor = new LintVisitor(document, settings, cssDataManager);
node.acceptVisitor(visitor);
visitor.completeValidations();
return visitor.getEntries(entryFilter);
}
constructor(document, settings, cssDataManager) {
this.cssDataManager = cssDataManager;
this.warnings = [];
this.settings = settings;
this.documentText = document.getText();
this.keyframes = new NodesByRootMap();
this.validProperties = {};
const properties = settings.getSetting(lintRules_1.Settings.ValidProperties);
if (Array.isArray(properties)) {
properties.forEach((p) => {
if (typeof p === 'string') {
const name = p.trim().toLowerCase();
if (name.length) {
this.validProperties[name] = true;
}
}
});
}
}
isValidPropertyDeclaration(element) {
const propertyName = element.fullPropertyName;
return this.validProperties[propertyName];
}
fetch(input, s) {
const elements = [];
for (const curr of input) {
if (curr.fullPropertyName === s) {
elements.push(curr);
}
}
return elements;
}
fetchWithValue(input, s, v) {
const elements = [];
for (const inputElement of input) {
if (inputElement.fullPropertyName === s) {
const expression = inputElement.node.getValue();
if (expression && this.findValueInExpression(expression, v)) {
elements.push(inputElement);
}
}
}
return elements;
}
findValueInExpression(expression, v) {
let found = false;
expression.accept(node => {
if (node.type === nodes.NodeType.Identifier && node.matches(v)) {
found = true;
}
return !found;
});
return found;
}
getEntries(filter = (nodes.Level.Warning | nodes.Level.Error)) {
return this.warnings.filter(entry => {
return (entry.getLevel() & filter) !== 0;
});
}
addEntry(node, rule, details) {
const entry = new nodes.Marker(node, rule, this.settings.getRule(rule), details);
this.warnings.push(entry);
}
getMissingNames(expected, actual) {
const expectedClone = expected.slice(0); // clone
for (let i = 0; i < actual.length; i++) {
const k = expectedClone.indexOf(actual[i]);
if (k !== -1) {
expectedClone[k] = null;
}
}
let result = null;
for (let i = 0; i < expectedClone.length; i++) {
const curr = expectedClone[i];
if (curr) {
if (result === null) {
result = l10n.t("'{0}'", curr);
}
else {
result = l10n.t("{0}, '{1}'", result, curr);
}
}
}
return result;
}
visitNode(node) {
switch (node.type) {
case nodes.NodeType.UnknownAtRule:
return this.visitUnknownAtRule(node);
case nodes.NodeType.Keyframe:
return this.visitKeyframe(node);
case nodes.NodeType.FontFace:
return this.visitFontFace(node);
case nodes.NodeType.Ruleset:
return this.visitRuleSet(node);
case nodes.NodeType.SimpleSelector:
return this.visitSimpleSelector(node);
case nodes.NodeType.Function:
return this.visitFunction(node);
case nodes.NodeType.NumericValue:
return this.visitNumericValue(node);
case nodes.NodeType.Import:
return this.visitImport(node);
case nodes.NodeType.HexColorValue:
return this.visitHexColorValue(node);
case nodes.NodeType.Prio:
return this.visitPrio(node);
case nodes.NodeType.IdentifierSelector:
return this.visitIdentifierSelector(node);
}
return true;
}
completeValidations() {
this.validateKeyframes();
}
visitUnknownAtRule(node) {
const atRuleName = node.getChild(0);
if (!atRuleName) {
return false;
}
const atDirective = this.cssDataManager.getAtDirective(atRuleName.getText());
if (atDirective) {
return false;
}
this.addEntry(atRuleName, lintRules_1.Rules.UnknownAtRules, `Unknown at rule ${atRuleName.getText()}`);
return true;
}
visitKeyframe(node) {
const keyword = node.getKeyword();
if (!keyword) {
return false;
}
const text = keyword.getText();
this.keyframes.add(node.getName(), text, (text !== '@keyframes') ? keyword : null);
return true;
}
validateKeyframes() {
// @keyframe and it's vendor specific alternatives
// @keyframe should be included
const expected = ['@-webkit-keyframes', '@-moz-keyframes', '@-o-keyframes'];
for (const name in this.keyframes.data) {
const actual = this.keyframes.data[name].names;
const needsStandard = (actual.indexOf('@keyframes') === -1);
if (!needsStandard && actual.length === 1) {
continue; // only the non-vendor specific keyword is used, that's fine, no warning
}
const missingVendorSpecific = this.getMissingNames(expected, actual);
if (missingVendorSpecific || needsStandard) {
for (const node of this.keyframes.data[name].nodes) {
if (needsStandard) {
const message = l10n.t("Always define standard rule '@keyframes' when defining keyframes.");
this.addEntry(node, lintRules_1.Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
}
if (missingVendorSpecific) {
const message = l10n.t("Always include all vendor specific rules: Missing: {0}", missingVendorSpecific);
this.addEntry(node, lintRules_1.Rules.AllVendorPrefixes, message);
}
}
}
}
return true;
}
visitSimpleSelector(node) {
/////////////////////////////////////////////////////////////
// Lint - The universal selector (*) is known to be slow.
/////////////////////////////////////////////////////////////
const firstChar = this.documentText.charAt(node.offset);
if (node.length === 1 && firstChar === '*') {
this.addEntry(node, lintRules_1.Rules.UniversalSelector);
}
return true;
}
visitIdentifierSelector(node) {
/////////////////////////////////////////////////////////////
// Lint - Avoid id selectors
/////////////////////////////////////////////////////////////
this.addEntry(node, lintRules_1.Rules.AvoidIdSelector);
return true;
}
visitImport(node) {
/////////////////////////////////////////////////////////////
// Lint - Import statements shouldn't be used, because they aren't offering parallel downloads.
/////////////////////////////////////////////////////////////
this.addEntry(node, lintRules_1.Rules.ImportStatemement);
return true;
}
visitRuleSet(node) {
/////////////////////////////////////////////////////////////
// Lint - Don't use empty rulesets.
/////////////////////////////////////////////////////////////
const declarations = node.getDeclarations();
if (!declarations) {
// syntax error
return false;
}
if (!declarations.hasChildren()) {
this.addEntry(node.getSelectors(), lintRules_1.Rules.EmptyRuleSet);
}
const propertyTable = [];
for (const element of declarations.getChildren()) {
if (element instanceof nodes.Declaration) {
propertyTable.push(new lintUtil_1.Element(element));
}
}
/////////////////////////////////////////////////////////////
// the rule warns when it finds:
// width being used with border, border-left, border-right, padding, padding-left, or padding-right
// height being used with border, border-top, border-bottom, padding, padding-top, or padding-bottom
// No error when box-sizing property is specified, as it assumes the user knows what he's doing.
// see https://github.com/CSSLint/csslint/wiki/Beware-of-box-model-size
/////////////////////////////////////////////////////////////
const boxModel = (0, lintUtil_1.default)(propertyTable);
if (boxModel.width) {
let properties = [];
if (boxModel.right.value) {
properties = (0, arrays_1.union)(properties, boxModel.right.properties);
}
if (boxModel.left.value) {
properties = (0, arrays_1.union)(properties, boxModel.left.properties);
}
if (properties.length !== 0) {
for (const item of properties) {
this.addEntry(item.node, lintRules_1.Rules.BewareOfBoxModelSize);
}
this.addEntry(boxModel.width.node, lintRules_1.Rules.BewareOfBoxModelSize);
}
}
if (boxModel.height) {
let properties = [];
if (boxModel.top.value) {
properties = (0, arrays_1.union)(properties, boxModel.top.properties);
}
if (boxModel.bottom.value) {
properties = (0, arrays_1.union)(properties, boxModel.bottom.properties);
}
if (properties.length !== 0) {
for (const item of properties) {
this.addEntry(item.node, lintRules_1.Rules.BewareOfBoxModelSize);
}
this.addEntry(boxModel.height.node, lintRules_1.Rules.BewareOfBoxModelSize);
}
}
/////////////////////////////////////////////////////////////
// Properties ignored due to display
/////////////////////////////////////////////////////////////
// With 'display: inline-block', 'float' has no effect
let displayElems = this.fetchWithValue(propertyTable, 'display', 'inline-block');
if (displayElems.length > 0) {
const elem = this.fetch(propertyTable, 'float');
for (let index = 0; index < elem.length; index++) {
const node = elem[index].node;
const value = node.getValue();
if (value && !value.matches('none')) {
this.addEntry(node, lintRules_1.Rules.PropertyIgnoredDueToDisplay, l10n.t("inline-block is ignored due to the float. If 'float' has a value other than 'none', the box is floated and 'display' is treated as 'block'"));
}
}
}
// With 'display: block', 'vertical-align' has no effect
displayElems = this.fetchWithValue(propertyTable, 'display', 'block');
if (displayElems.length > 0) {
const elem = this.fetch(propertyTable, 'vertical-align');
for (let index = 0; index < elem.length; index++) {
this.addEntry(elem[index].node, lintRules_1.Rules.PropertyIgnoredDueToDisplay, l10n.t("Property is ignored due to the display. With 'display: block', vertical-align should not be used."));
}
}
/////////////////////////////////////////////////////////////
// Avoid 'float'
/////////////////////////////////////////////////////////////
const elements = this.fetch(propertyTable, 'float');
for (let index = 0; index < elements.length; index++) {
const element = elements[index];
if (!this.isValidPropertyDeclaration(element)) {
this.addEntry(element.node, lintRules_1.Rules.AvoidFloat);
}
}
/////////////////////////////////////////////////////////////
// Don't use duplicate declarations.
/////////////////////////////////////////////////////////////
for (let i = 0; i < propertyTable.length; i++) {
const element = propertyTable[i];
if (element.fullPropertyName !== 'background' && !this.validProperties[element.fullPropertyName]) {
const value = element.node.getValue();
if (value && this.documentText.charAt(value.offset) !== '-') {
const elements = this.fetch(propertyTable, element.fullPropertyName);
if (elements.length > 1) {
for (let k = 0; k < elements.length; k++) {
const value = elements[k].node.getValue();
if (value && this.documentText.charAt(value.offset) !== '-' && elements[k] !== element) {
this.addEntry(element.node, lintRules_1.Rules.DuplicateDeclarations);
}
}
}
}
}
}
/////////////////////////////////////////////////////////////
// Unknown propery & When using a vendor-prefixed gradient, make sure to use them all.
/////////////////////////////////////////////////////////////
const isExportBlock = node.getSelectors().matches(":export");
if (!isExportBlock) {
const propertiesBySuffix = new NodesByRootMap();
let containsUnknowns = false;
for (const element of propertyTable) {
const decl = element.node;
if (this.isCSSDeclaration(decl)) {
let name = element.fullPropertyName;
const firstChar = name.charAt(0);
if (firstChar === '-') {
if (name.charAt(1) !== '-') { // avoid css variables
if (!this.cssDataManager.isKnownProperty(name) && !this.validProperties[name]) {
this.addEntry(decl.getProperty(), lintRules_1.Rules.UnknownVendorSpecificProperty);
}
const nonPrefixedName = decl.getNonPrefixedPropertyName();
propertiesBySuffix.add(nonPrefixedName, name, decl.getProperty());
}
}
else {
const fullName = name;
if (firstChar === '*' || firstChar === '_') {
this.addEntry(decl.getProperty(), lintRules_1.Rules.IEStarHack);
name = name.substr(1);
}
// _property and *property might be contributed via custom data
if (!this.cssDataManager.isKnownProperty(fullName) && !this.cssDataManager.isKnownProperty(name)) {
if (!this.validProperties[name]) {
this.addEntry(decl.getProperty(), lintRules_1.Rules.UnknownProperty, l10n.t("Unknown property: '{0}'", decl.getFullPropertyName()));
}
}
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
for (const suffix in propertiesBySuffix.data) {
const entry = propertiesBySuffix.data[suffix];
const actual = entry.names;
const needsStandard = this.cssDataManager.isStandardProperty(suffix) && (actual.indexOf(suffix) === -1);
if (!needsStandard && actual.length === 1) {
continue; // only the non-vendor specific rule is used, that's fine, no warning
}
/**
* We should ignore missing standard properties, if there's an explicit contextual reference to a
* vendor specific pseudo-element selector with the same vendor (prefix)
*
* (See https://github.com/microsoft/vscode/issues/164350)
*/
const entriesThatNeedStandard = new Set(needsStandard ? entry.nodes : []);
if (needsStandard) {
const pseudoElements = this.getContextualVendorSpecificPseudoElements(node);
for (const node of entry.nodes) {
const propertyName = node.getName();
const prefix = propertyName.substring(0, propertyName.length - suffix.length);
if (pseudoElements.some(x => x.startsWith(prefix))) {
entriesThatNeedStandard.delete(node);
}
}
}
const expected = [];
for (let i = 0, len = LintVisitor.prefixes.length; i < len; i++) {
const prefix = LintVisitor.prefixes[i];
if (this.cssDataManager.isStandardProperty(prefix + suffix)) {
expected.push(prefix + suffix);
}
}
const missingVendorSpecific = this.getMissingNames(expected, actual);
if (missingVendorSpecific || needsStandard) {
for (const node of entry.nodes) {
if (needsStandard && entriesThatNeedStandard.has(node)) {
const message = l10n.t("Also define the standard property '{0}' for compatibility", suffix);
this.addEntry(node, lintRules_1.Rules.IncludeStandardPropertyWhenUsingVendorPrefix, message);
}
if (missingVendorSpecific) {
const message = l10n.t("Always include all vendor specific properties: Missing: {0}", missingVendorSpecific);
this.addEntry(node, lintRules_1.Rules.AllVendorPrefixes, message);
}
}
}
}
}
}
return true;
}
/**
* Walks up the syntax tree (starting from given `node`) and captures vendor
* specific pseudo-element selectors.
* @returns An array of vendor specific pseudo-elements; or empty if none
* was found.
*/
getContextualVendorSpecificPseudoElements(node) {
function walkDown(s, n) {
for (const child of n.getChildren()) {
if (child.type === nodes.NodeType.PseudoSelector) {
const pseudoElement = child.getChildren()[0]?.getText();
if (pseudoElement) {
s.add(pseudoElement);
}
}
walkDown(s, child);
}
}
function walkUp(s, n) {
if (n.type === nodes.NodeType.Ruleset) {
for (const selector of n.getSelectors().getChildren()) {
walkDown(s, selector);
}
}
return n.parent ? walkUp(s, n.parent) : undefined;
}
const result = new Set();
walkUp(result, node);
return Array.from(result);
}
visitPrio(node) {
/////////////////////////////////////////////////////////////
// Don't use !important
/////////////////////////////////////////////////////////////
this.addEntry(node, lintRules_1.Rules.AvoidImportant);
return true;
}
visitNumericValue(node) {
/////////////////////////////////////////////////////////////
// 0 has no following unit
/////////////////////////////////////////////////////////////
const funcDecl = node.findParent(nodes.NodeType.Function);
if (funcDecl && funcDecl.getName() === 'calc') {
return true;
}
const decl = node.findParent(nodes.NodeType.Declaration);
if (decl) {
const declValue = decl.getValue();
if (declValue) {
const value = node.getValue();
if (!value.unit || languageFacts.units.length.indexOf(value.unit.toLowerCase()) === -1) {
return true;
}
if (parseFloat(value.value) === 0.0 && !!value.unit && !this.validProperties[decl.getFullPropertyName()]) {
this.addEntry(node, lintRules_1.Rules.ZeroWithUnit);
}
}
}
return true;
}
visitFontFace(node) {
const declarations = node.getDeclarations();
if (!declarations) {
// syntax error
return false;
}
let definesSrc = false, definesFontFamily = false;
let containsUnknowns = false;
for (const node of declarations.getChildren()) {
if (this.isCSSDeclaration(node)) {
const name = node.getProperty().getName().toLowerCase();
if (name === 'src') {
definesSrc = true;
}
if (name === 'font-family') {
definesFontFamily = true;
}
}
else {
containsUnknowns = true;
}
}
if (!containsUnknowns && (!definesSrc || !definesFontFamily)) {
this.addEntry(node, lintRules_1.Rules.RequiredPropertiesForFontFace);
}
return true;
}
isCSSDeclaration(node) {
if (node instanceof nodes.Declaration) {
if (!node.getValue()) {
return false;
}
const property = node.getProperty();
if (!property) {
return false;
}
const identifier = property.getIdentifier();
if (!identifier || identifier.containsInterpolation()) {
return false;
}
return true;
}
return false;
}
visitHexColorValue(node) {
// Rule: #eeff0011 or #eeff00 or #ef01 or #ef0
const length = node.length;
if (length !== 9 && length !== 7 && length !== 5 && length !== 4) {
this.addEntry(node, lintRules_1.Rules.HexColorLength);
}
return false;
}
visitFunction(node) {
const fnName = node.getName().toLowerCase();
let expectedAttrCount = -1;
let 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_1.Rules.ArgsInColorFunction);
}
}
return true;
}
}
LintVisitor.prefixes = [
'-ms-', '-moz-', '-o-', '-webkit-', // Quite common
// '-xv-', '-atsc-', '-wap-', '-khtml-', 'mso-', 'prince-', '-ah-', '-hp-', '-ro-', '-rim-', '-tc-' // Quite un-common
];
exports.LintVisitor = LintVisitor;
});

View file

@ -0,0 +1,90 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../parser/cssNodes", "@vscode/l10n"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.LintConfigurationSettings = exports.Settings = exports.Rules = exports.Setting = exports.Rule = void 0;
const nodes = require("../parser/cssNodes");
const l10n = require("@vscode/l10n");
const Warning = nodes.Level.Warning;
const Error = nodes.Level.Error;
const Ignore = nodes.Level.Ignore;
class Rule {
constructor(id, message, defaultValue) {
this.id = id;
this.message = message;
this.defaultValue = defaultValue;
// nothing to do
}
}
exports.Rule = Rule;
class Setting {
constructor(id, message, defaultValue) {
this.id = id;
this.message = message;
this.defaultValue = defaultValue;
// nothing to do
}
}
exports.Setting = Setting;
exports.Rules = {
AllVendorPrefixes: new Rule('compatibleVendorPrefixes', l10n.t("When using a vendor-specific prefix make sure to also include all other vendor-specific properties"), Ignore),
IncludeStandardPropertyWhenUsingVendorPrefix: new Rule('vendorPrefix', l10n.t("When using a vendor-specific prefix also include the standard property"), Warning),
DuplicateDeclarations: new Rule('duplicateProperties', l10n.t("Do not use duplicate style definitions"), Ignore),
EmptyRuleSet: new Rule('emptyRules', l10n.t("Do not use empty rulesets"), Warning),
ImportStatemement: new Rule('importStatement', l10n.t("Import statements do not load in parallel"), Ignore),
BewareOfBoxModelSize: new Rule('boxModel', l10n.t("Do not use width or height when using padding or border"), Ignore),
UniversalSelector: new Rule('universalSelector', l10n.t("The universal selector (*) is known to be slow"), Ignore),
ZeroWithUnit: new Rule('zeroUnits', l10n.t("No unit for zero needed"), Ignore),
RequiredPropertiesForFontFace: new Rule('fontFaceProperties', l10n.t("@font-face rule must define 'src' and 'font-family' properties"), Warning),
HexColorLength: new Rule('hexColorLength', l10n.t("Hex colors must consist of three, four, six or eight hex numbers"), Error),
ArgsInColorFunction: new Rule('argumentsInColorFunction', l10n.t("Invalid number of parameters"), Error),
UnknownProperty: new Rule('unknownProperties', l10n.t("Unknown property."), Warning),
UnknownAtRules: new Rule('unknownAtRules', l10n.t("Unknown at-rule."), Warning),
IEStarHack: new Rule('ieHack', l10n.t("IE hacks are only necessary when supporting IE7 and older"), Ignore),
UnknownVendorSpecificProperty: new Rule('unknownVendorSpecificProperties', l10n.t("Unknown vendor specific property."), Ignore),
PropertyIgnoredDueToDisplay: new Rule('propertyIgnoredDueToDisplay', l10n.t("Property is ignored due to the display."), Warning),
AvoidImportant: new Rule('important', l10n.t("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', l10n.t("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', l10n.t("Selectors should not contain IDs because these rules are too tightly coupled with the HTML."), Ignore),
};
exports.Settings = {
ValidProperties: new Setting('validProperties', l10n.t("A list of properties that are not validated against the `unknownProperties` rule."), [])
};
class LintConfigurationSettings {
constructor(conf = {}) {
this.conf = conf;
}
getRule(rule) {
if (this.conf.hasOwnProperty(rule.id)) {
const level = toLevel(this.conf[rule.id]);
if (level) {
return level;
}
}
return rule.defaultValue;
}
getSetting(setting) {
return this.conf[setting.id];
}
}
exports.LintConfigurationSettings = LintConfigurationSettings;
function toLevel(level) {
switch (level) {
case 'ignore': return nodes.Level.Ignore;
case 'warning': return nodes.Level.Warning;
case 'error': return nodes.Level.Error;
}
return null;
}
});

View file

@ -0,0 +1,210 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../utils/arrays"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.Element = void 0;
const arrays_1 = require("../utils/arrays");
class Element {
constructor(decl) {
this.fullPropertyName = decl.getFullPropertyName().toLowerCase();
this.node = decl;
}
}
exports.Element = Element;
function setSide(model, side, value, property) {
const state = model[side];
state.value = value;
if (value) {
if (!(0, arrays_1.includes)(state.properties, property)) {
state.properties.push(property);
}
}
}
function setAllSides(model, value, property) {
setSide(model, 'top', value, property);
setSide(model, 'right', value, property);
setSide(model, 'bottom', value, property);
setSide(model, 'left', value, property);
}
function updateModelWithValue(model, side, value, property) {
if (side === 'top' || side === 'right' ||
side === 'bottom' || side === 'left') {
setSide(model, side, value, property);
}
else {
setAllSides(model, value, property);
}
}
function updateModelWithList(model, values, property) {
switch (values.length) {
case 1:
updateModelWithValue(model, undefined, values[0], property);
break;
case 2:
updateModelWithValue(model, 'top', values[0], property);
updateModelWithValue(model, 'bottom', values[0], property);
updateModelWithValue(model, 'right', values[1], property);
updateModelWithValue(model, 'left', values[1], property);
break;
case 3:
updateModelWithValue(model, 'top', values[0], property);
updateModelWithValue(model, 'right', values[1], property);
updateModelWithValue(model, 'left', values[1], property);
updateModelWithValue(model, 'bottom', values[2], property);
break;
case 4:
updateModelWithValue(model, 'top', values[0], property);
updateModelWithValue(model, 'right', values[1], property);
updateModelWithValue(model, 'bottom', values[2], property);
updateModelWithValue(model, 'left', values[3], property);
break;
}
}
function matches(value, candidates) {
for (let candidate of candidates) {
if (value.matches(candidate)) {
return true;
}
}
return false;
}
/**
* @param allowsKeywords whether the initial value of property is zero, so keywords `initial` and `unset` count as zero
* @return `true` if this node represents a non-zero border; otherwise, `false`
*/
function checkLineWidth(value, allowsKeywords = true) {
if (allowsKeywords && matches(value, ['initial', 'unset'])) {
return false;
}
// a <length> is a value and a unit
// so use `parseFloat` to strip the unit
return parseFloat(value.getText()) !== 0;
}
function checkLineWidthList(nodes, allowsKeywords = true) {
return nodes.map(node => checkLineWidth(node, allowsKeywords));
}
/**
* @param allowsKeywords whether keywords `initial` and `unset` count as zero
* @return `true` if this node represents a non-zero border; otherwise, `false`
*/
function checkLineStyle(valueNode, allowsKeywords = true) {
if (matches(valueNode, ['none', 'hidden'])) {
return false;
}
if (allowsKeywords && matches(valueNode, ['initial', 'unset'])) {
return false;
}
return true;
}
function checkLineStyleList(nodes, allowsKeywords = true) {
return nodes.map(node => checkLineStyle(node, allowsKeywords));
}
function checkBorderShorthand(node) {
const children = node.getChildren();
// the only child can be a keyword, a <line-width>, or a <line-style>
// if either check returns false, the result is no border
if (children.length === 1) {
const value = children[0];
return checkLineWidth(value) && checkLineStyle(value);
}
// multiple children can't contain keywords
// if any child means no border, the result is no border
for (const child of children) {
const value = child;
if (!checkLineWidth(value, /* allowsKeywords: */ false) ||
!checkLineStyle(value, /* allowsKeywords: */ false)) {
return false;
}
}
return true;
}
function calculateBoxModel(propertyTable) {
const model = {
top: { value: false, properties: [] },
right: { value: false, properties: [] },
bottom: { value: false, properties: [] },
left: { value: false, properties: [] },
};
for (const property of propertyTable) {
const value = property.node.value;
if (typeof value === 'undefined') {
continue;
}
switch (property.fullPropertyName) {
case 'box-sizing':
// has `box-sizing`, bail out
return {
top: { value: false, properties: [] },
right: { value: false, properties: [] },
bottom: { value: false, properties: [] },
left: { value: false, properties: [] },
};
case 'width':
model.width = property;
break;
case 'height':
model.height = property;
break;
default:
const segments = property.fullPropertyName.split('-');
switch (segments[0]) {
case 'border':
switch (segments[1]) {
case undefined:
case 'top':
case 'right':
case 'bottom':
case 'left':
switch (segments[2]) {
case undefined:
updateModelWithValue(model, segments[1], checkBorderShorthand(value), property);
break;
case 'width':
// the initial value of `border-width` is `medium`, not zero
updateModelWithValue(model, segments[1], checkLineWidth(value, false), property);
break;
case 'style':
// the initial value of `border-style` is `none`
updateModelWithValue(model, segments[1], checkLineStyle(value, true), property);
break;
}
break;
case 'width':
// the initial value of `border-width` is `medium`, not zero
updateModelWithList(model, checkLineWidthList(value.getChildren(), false), property);
break;
case 'style':
// the initial value of `border-style` is `none`
updateModelWithList(model, checkLineStyleList(value.getChildren(), true), property);
break;
}
break;
case 'padding':
if (segments.length === 1) {
// the initial value of `padding` is zero
updateModelWithList(model, checkLineWidthList(value.getChildren(), true), property);
}
else {
// the initial value of `padding` is zero
updateModelWithValue(model, segments[1], checkLineWidth(value, true), property);
}
break;
}
break;
}
}
return model;
}
exports.default = calculateBoxModel;
});

View file

@ -0,0 +1,171 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../cssLanguageTypes", "../utils/strings", "../utils/resources"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PathCompletionParticipant = void 0;
const cssLanguageTypes_1 = require("../cssLanguageTypes");
const strings_1 = require("../utils/strings");
const resources_1 = require("../utils/resources");
class PathCompletionParticipant {
constructor(readDirectory) {
this.readDirectory = readDirectory;
this.literalCompletions = [];
this.importCompletions = [];
}
onCssURILiteralValue(context) {
this.literalCompletions.push(context);
}
onCssImportPath(context) {
this.importCompletions.push(context);
}
async computeCompletions(document, documentContext) {
const result = { items: [], isIncomplete: false };
for (const literalCompletion of this.literalCompletions) {
const uriValue = literalCompletion.uriValue;
const fullValue = stripQuotes(uriValue);
if (fullValue === '.' || fullValue === '..') {
result.isIncomplete = true;
}
else {
const items = await this.providePathSuggestions(uriValue, literalCompletion.position, literalCompletion.range, document, documentContext);
for (let item of items) {
result.items.push(item);
}
}
}
for (const importCompletion of this.importCompletions) {
const pathValue = importCompletion.pathValue;
const fullValue = stripQuotes(pathValue);
if (fullValue === '.' || fullValue === '..') {
result.isIncomplete = true;
}
else {
let suggestions = await this.providePathSuggestions(pathValue, importCompletion.position, importCompletion.range, document, documentContext);
if (document.languageId === 'scss') {
suggestions.forEach(s => {
if ((0, strings_1.startsWith)(s.label, '_') && (0, strings_1.endsWith)(s.label, '.scss')) {
if (s.textEdit) {
s.textEdit.newText = s.label.slice(1, -5);
}
else {
s.label = s.label.slice(1, -5);
}
}
});
}
for (let item of suggestions) {
result.items.push(item);
}
}
}
return result;
}
async providePathSuggestions(pathValue, position, range, document, documentContext) {
const fullValue = stripQuotes(pathValue);
const isValueQuoted = (0, strings_1.startsWith)(pathValue, `'`) || (0, strings_1.startsWith)(pathValue, `"`);
const valueBeforeCursor = isValueQuoted
? fullValue.slice(0, position.character - (range.start.character + 1))
: fullValue.slice(0, position.character - range.start.character);
const currentDocUri = document.uri;
const fullValueRange = isValueQuoted ? shiftRange(range, 1, -1) : range;
const replaceRange = pathToReplaceRange(valueBeforeCursor, fullValue, fullValueRange);
const valueBeforeLastSlash = valueBeforeCursor.substring(0, valueBeforeCursor.lastIndexOf('/') + 1); // keep the last slash
let parentDir = documentContext.resolveReference(valueBeforeLastSlash || '.', currentDocUri);
if (parentDir) {
try {
const result = [];
const infos = await this.readDirectory(parentDir);
for (const [name, type] of infos) {
// Exclude paths that start with `.`
if (name.charCodeAt(0) !== CharCode_dot && (type === cssLanguageTypes_1.FileType.Directory || (0, resources_1.joinPath)(parentDir, name) !== currentDocUri)) {
result.push(createCompletionItem(name, type === cssLanguageTypes_1.FileType.Directory, replaceRange));
}
}
return result;
}
catch (e) {
// ignore
}
}
return [];
}
}
exports.PathCompletionParticipant = PathCompletionParticipant;
const CharCode_dot = '.'.charCodeAt(0);
function stripQuotes(fullValue) {
if ((0, strings_1.startsWith)(fullValue, `'`) || (0, strings_1.startsWith)(fullValue, `"`)) {
return fullValue.slice(1, -1);
}
else {
return fullValue;
}
}
function pathToReplaceRange(valueBeforeCursor, fullValue, fullValueRange) {
let replaceRange;
const lastIndexOfSlash = valueBeforeCursor.lastIndexOf('/');
if (lastIndexOfSlash === -1) {
replaceRange = fullValueRange;
}
else {
// For cases where cursor is in the middle of attribute value, like <script src="./s|rc/test.js">
// Find the last slash before cursor, and calculate the start of replace range from there
const valueAfterLastSlash = fullValue.slice(lastIndexOfSlash + 1);
const startPos = shiftPosition(fullValueRange.end, -valueAfterLastSlash.length);
// If whitespace exists, replace until it
const whitespaceIndex = valueAfterLastSlash.indexOf(' ');
let endPos;
if (whitespaceIndex !== -1) {
endPos = shiftPosition(startPos, whitespaceIndex);
}
else {
endPos = fullValueRange.end;
}
replaceRange = cssLanguageTypes_1.Range.create(startPos, endPos);
}
return replaceRange;
}
function createCompletionItem(name, isDir, replaceRange) {
if (isDir) {
name = name + '/';
return {
label: escapePath(name),
kind: cssLanguageTypes_1.CompletionItemKind.Folder,
textEdit: cssLanguageTypes_1.TextEdit.replace(replaceRange, escapePath(name)),
command: {
title: 'Suggest',
command: 'editor.action.triggerSuggest'
}
};
}
else {
return {
label: escapePath(name),
kind: cssLanguageTypes_1.CompletionItemKind.File,
textEdit: cssLanguageTypes_1.TextEdit.replace(replaceRange, escapePath(name))
};
}
}
// Escape https://www.w3.org/TR/CSS1/#url
function escapePath(p) {
return p.replace(/(\s|\(|\)|,|"|')/g, '\\$1');
}
function shiftPosition(pos, offset) {
return cssLanguageTypes_1.Position.create(pos.line, pos.character + offset);
}
function shiftRange(range, startOffset, endOffset) {
const start = shiftPosition(range.start, startOffset);
const end = shiftPosition(range.end, endOffset);
return cssLanguageTypes_1.Range.create(start, end);
}
});

View file

@ -0,0 +1,367 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./cssCompletion", "../parser/cssNodes", "../cssLanguageTypes", "@vscode/l10n"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.SCSSCompletion = void 0;
const cssCompletion_1 = require("./cssCompletion");
const nodes = require("../parser/cssNodes");
const cssLanguageTypes_1 = require("../cssLanguageTypes");
const l10n = require("@vscode/l10n");
const sassDocumentationName = l10n.t('Sass documentation');
class SCSSCompletion extends cssCompletion_1.CSSCompletion {
constructor(lsServiceOptions, cssDataManager) {
super('$', lsServiceOptions, cssDataManager);
addReferencesToDocumentation(SCSSCompletion.scssModuleLoaders);
addReferencesToDocumentation(SCSSCompletion.scssModuleBuiltIns);
}
isImportPathParent(type) {
return type === nodes.NodeType.Forward
|| type === nodes.NodeType.Use
|| super.isImportPathParent(type);
}
getCompletionForImportPath(importPathNode, result) {
const parentType = importPathNode.getParent().type;
if (parentType === nodes.NodeType.Forward || parentType === nodes.NodeType.Use) {
for (let p of SCSSCompletion.scssModuleBuiltIns) {
const item = {
label: p.label,
documentation: p.documentation,
textEdit: cssLanguageTypes_1.TextEdit.replace(this.getCompletionRange(importPathNode), `'${p.label}'`),
kind: cssLanguageTypes_1.CompletionItemKind.Module
};
result.items.push(item);
}
}
return super.getCompletionForImportPath(importPathNode, result);
}
createReplaceFunction() {
let tabStopCounter = 1;
return (_match, p1) => {
return '\\' + p1 + ': ${' + tabStopCounter++ + ':' + (SCSSCompletion.variableDefaults[p1] || '') + '}';
};
}
createFunctionProposals(proposals, existingNode, sortToEnd, result) {
for (const p of proposals) {
const insertText = p.func.replace(/\[?(\$\w+)\]?/g, this.createReplaceFunction());
const label = p.func.substr(0, p.func.indexOf('('));
const item = {
label: label,
detail: p.func,
documentation: p.desc,
textEdit: cssLanguageTypes_1.TextEdit.replace(this.getCompletionRange(existingNode), insertText),
insertTextFormat: cssLanguageTypes_1.InsertTextFormat.Snippet,
kind: cssLanguageTypes_1.CompletionItemKind.Function
};
if (sortToEnd) {
item.sortText = 'z';
}
result.items.push(item);
}
return result;
}
getCompletionsForSelector(ruleSet, isNested, result) {
this.createFunctionProposals(SCSSCompletion.selectorFuncs, null, true, result);
return super.getCompletionsForSelector(ruleSet, isNested, result);
}
getTermProposals(entry, existingNode, result) {
let functions = SCSSCompletion.builtInFuncs;
if (entry) {
functions = functions.filter(f => !f.type || !entry.restrictions || entry.restrictions.indexOf(f.type) !== -1);
}
this.createFunctionProposals(functions, existingNode, true, result);
return super.getTermProposals(entry, existingNode, result);
}
getColorProposals(entry, existingNode, result) {
this.createFunctionProposals(SCSSCompletion.colorProposals, existingNode, false, result);
return super.getColorProposals(entry, existingNode, result);
}
getCompletionsForDeclarationProperty(declaration, result) {
this.getCompletionForAtDirectives(result);
this.getCompletionsForSelector(null, true, result);
return super.getCompletionsForDeclarationProperty(declaration, result);
}
getCompletionsForExtendsReference(_extendsRef, existingNode, result) {
const symbols = this.getSymbolContext().findSymbolsAtOffset(this.offset, nodes.ReferenceType.Rule);
for (const symbol of symbols) {
const suggest = {
label: symbol.name,
textEdit: cssLanguageTypes_1.TextEdit.replace(this.getCompletionRange(existingNode), symbol.name),
kind: cssLanguageTypes_1.CompletionItemKind.Function,
};
result.items.push(suggest);
}
return result;
}
getCompletionForAtDirectives(result) {
result.items.push(...SCSSCompletion.scssAtDirectives);
return result;
}
getCompletionForTopLevel(result) {
this.getCompletionForAtDirectives(result);
this.getCompletionForModuleLoaders(result);
super.getCompletionForTopLevel(result);
return result;
}
getCompletionForModuleLoaders(result) {
result.items.push(...SCSSCompletion.scssModuleLoaders);
return result;
}
}
SCSSCompletion.variableDefaults = {
'$red': '1',
'$green': '2',
'$blue': '3',
'$alpha': '1.0',
'$color': '#000000',
'$weight': '0.5',
'$hue': '0',
'$saturation': '0%',
'$lightness': '0%',
'$degrees': '0',
'$amount': '0',
'$string': '""',
'$substring': '"s"',
'$number': '0',
'$limit': '1'
};
SCSSCompletion.colorProposals = [
{ func: 'red($color)', desc: l10n.t('Gets the red component of a color.') },
{ func: 'green($color)', desc: l10n.t('Gets the green component of a color.') },
{ func: 'blue($color)', desc: l10n.t('Gets the blue component of a color.') },
{ func: 'mix($color, $color, [$weight])', desc: l10n.t('Mixes two colors together.') },
{ func: 'hue($color)', desc: l10n.t('Gets the hue component of a color.') },
{ func: 'saturation($color)', desc: l10n.t('Gets the saturation component of a color.') },
{ func: 'lightness($color)', desc: l10n.t('Gets the lightness component of a color.') },
{ func: 'adjust-hue($color, $degrees)', desc: l10n.t('Changes the hue of a color.') },
{ func: 'lighten($color, $amount)', desc: l10n.t('Makes a color lighter.') },
{ func: 'darken($color, $amount)', desc: l10n.t('Makes a color darker.') },
{ func: 'saturate($color, $amount)', desc: l10n.t('Makes a color more saturated.') },
{ func: 'desaturate($color, $amount)', desc: l10n.t('Makes a color less saturated.') },
{ func: 'grayscale($color)', desc: l10n.t('Converts a color to grayscale.') },
{ func: 'complement($color)', desc: l10n.t('Returns the complement of a color.') },
{ func: 'invert($color)', desc: l10n.t('Returns the inverse of a color.') },
{ func: 'alpha($color)', desc: l10n.t('Gets the opacity component of a color.') },
{ func: 'opacity($color)', desc: 'Gets the alpha component (opacity) of a color.' },
{ func: 'rgba($color, $alpha)', desc: l10n.t('Changes the alpha component for a color.') },
{ func: 'opacify($color, $amount)', desc: l10n.t('Makes a color more opaque.') },
{ func: 'fade-in($color, $amount)', desc: l10n.t('Makes a color more opaque.') },
{ func: 'transparentize($color, $amount)', desc: l10n.t('Makes a color more transparent.') },
{ func: 'fade-out($color, $amount)', desc: l10n.t('Makes a color more transparent.') },
{ func: 'adjust-color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha])', desc: l10n.t('Increases or decreases one or more components of a color.') },
{ func: 'scale-color($color, [$red], [$green], [$blue], [$saturation], [$lightness], [$alpha])', desc: l10n.t('Fluidly scales one or more properties of a color.') },
{ func: 'change-color($color, [$red], [$green], [$blue], [$hue], [$saturation], [$lightness], [$alpha])', desc: l10n.t('Changes one or more properties of a color.') },
{ func: 'ie-hex-str($color)', desc: l10n.t('Converts a color into the format understood by IE filters.') }
];
SCSSCompletion.selectorFuncs = [
{ func: 'selector-nest($selectors…)', desc: l10n.t('Nests selector beneath one another like they would be nested in the stylesheet.') },
{ func: 'selector-append($selectors…)', desc: l10n.t('Appends selectors to one another without spaces in between.') },
{ func: 'selector-extend($selector, $extendee, $extender)', desc: l10n.t('Extends $extendee with $extender within $selector.') },
{ func: 'selector-replace($selector, $original, $replacement)', desc: l10n.t('Replaces $original with $replacement within $selector.') },
{ func: 'selector-unify($selector1, $selector2)', desc: l10n.t('Unifies two selectors to produce a selector that matches elements matched by both.') },
{ func: 'is-superselector($super, $sub)', desc: l10n.t('Returns whether $super matches all the elements $sub does, and possibly more.') },
{ func: 'simple-selectors($selector)', desc: l10n.t('Returns the simple selectors that comprise a compound selector.') },
{ func: 'selector-parse($selector)', desc: l10n.t('Parses a selector into the format returned by &.') }
];
SCSSCompletion.builtInFuncs = [
{ func: 'unquote($string)', desc: l10n.t('Removes quotes from a string.') },
{ func: 'quote($string)', desc: l10n.t('Adds quotes to a string.') },
{ func: 'str-length($string)', desc: l10n.t('Returns the number of characters in a string.') },
{ func: 'str-insert($string, $insert, $index)', desc: l10n.t('Inserts $insert into $string at $index.') },
{ func: 'str-index($string, $substring)', desc: l10n.t('Returns the index of the first occurance of $substring in $string.') },
{ func: 'str-slice($string, $start-at, [$end-at])', desc: l10n.t('Extracts a substring from $string.') },
{ func: 'to-upper-case($string)', desc: l10n.t('Converts a string to upper case.') },
{ func: 'to-lower-case($string)', desc: l10n.t('Converts a string to lower case.') },
{ func: 'percentage($number)', desc: l10n.t('Converts a unitless number to a percentage.'), type: 'percentage' },
{ func: 'round($number)', desc: l10n.t('Rounds a number to the nearest whole number.') },
{ func: 'ceil($number)', desc: l10n.t('Rounds a number up to the next whole number.') },
{ func: 'floor($number)', desc: l10n.t('Rounds a number down to the previous whole number.') },
{ func: 'abs($number)', desc: l10n.t('Returns the absolute value of a number.') },
{ func: 'min($numbers)', desc: l10n.t('Finds the minimum of several numbers.') },
{ func: 'max($numbers)', desc: l10n.t('Finds the maximum of several numbers.') },
{ func: 'random([$limit])', desc: l10n.t('Returns a random number.') },
{ func: 'length($list)', desc: l10n.t('Returns the length of a list.') },
{ func: 'nth($list, $n)', desc: l10n.t('Returns a specific item in a list.') },
{ func: 'set-nth($list, $n, $value)', desc: l10n.t('Replaces the nth item in a list.') },
{ func: 'join($list1, $list2, [$separator])', desc: l10n.t('Joins together two lists into one.') },
{ func: 'append($list1, $val, [$separator])', desc: l10n.t('Appends a single value onto the end of a list.') },
{ func: 'zip($lists)', desc: l10n.t('Combines several lists into a single multidimensional list.') },
{ func: 'index($list, $value)', desc: l10n.t('Returns the position of a value within a list.') },
{ func: 'list-separator(#list)', desc: l10n.t('Returns the separator of a list.') },
{ func: 'map-get($map, $key)', desc: l10n.t('Returns the value in a map associated with a given key.') },
{ func: 'map-merge($map1, $map2)', desc: l10n.t('Merges two maps together into a new map.') },
{ func: 'map-remove($map, $keys)', desc: l10n.t('Returns a new map with keys removed.') },
{ func: 'map-keys($map)', desc: l10n.t('Returns a list of all keys in a map.') },
{ func: 'map-values($map)', desc: l10n.t('Returns a list of all values in a map.') },
{ func: 'map-has-key($map, $key)', desc: l10n.t('Returns whether a map has a value associated with a given key.') },
{ func: 'keywords($args)', desc: l10n.t('Returns the keywords passed to a function that takes variable arguments.') },
{ func: 'feature-exists($feature)', desc: l10n.t('Returns whether a feature exists in the current Sass runtime.') },
{ func: 'variable-exists($name)', desc: l10n.t('Returns whether a variable with the given name exists in the current scope.') },
{ func: 'global-variable-exists($name)', desc: l10n.t('Returns whether a variable with the given name exists in the global scope.') },
{ func: 'function-exists($name)', desc: l10n.t('Returns whether a function with the given name exists.') },
{ func: 'mixin-exists($name)', desc: l10n.t('Returns whether a mixin with the given name exists.') },
{ func: 'inspect($value)', desc: l10n.t('Returns the string representation of a value as it would be represented in Sass.') },
{ func: 'type-of($value)', desc: l10n.t('Returns the type of a value.') },
{ func: 'unit($number)', desc: l10n.t('Returns the unit(s) associated with a number.') },
{ func: 'unitless($number)', desc: l10n.t('Returns whether a number has units.') },
{ func: 'comparable($number1, $number2)', desc: l10n.t('Returns whether two numbers can be added, subtracted, or compared.') },
{ func: 'call($name, $args…)', desc: l10n.t('Dynamically calls a Sass function.') }
];
SCSSCompletion.scssAtDirectives = [
{
label: "@extend",
documentation: l10n.t("Inherits the styles of another selector."),
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
{
label: "@at-root",
documentation: l10n.t("Causes one or more rules to be emitted at the root of the document."),
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
{
label: "@debug",
documentation: l10n.t("Prints the value of an expression to the standard error output stream. Useful for debugging complicated Sass files."),
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
{
label: "@warn",
documentation: l10n.t("Prints the value of an expression to the standard error output stream. Useful for libraries that need to warn users of deprecations or recovering from minor mixin usage mistakes. Warnings can be turned off with the `--quiet` command-line option or the `:quiet` Sass option."),
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
{
label: "@error",
documentation: l10n.t("Throws the value of an expression as a fatal error with stack trace. Useful for validating arguments to mixins and functions."),
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
{
label: "@if",
documentation: l10n.t("Includes the body if the expression does not evaluate to `false` or `null`."),
insertText: "@if ${1:expr} {\n\t$0\n}",
insertTextFormat: cssLanguageTypes_1.InsertTextFormat.Snippet,
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
{
label: "@for",
documentation: l10n.t("For loop that repeatedly outputs a set of styles for each `$var` in the `from/through` or `from/to` clause."),
insertText: "@for \\$${1:var} from ${2:start} ${3|to,through|} ${4:end} {\n\t$0\n}",
insertTextFormat: cssLanguageTypes_1.InsertTextFormat.Snippet,
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
{
label: "@each",
documentation: l10n.t("Each loop that sets `$var` to each item in the list or map, then outputs the styles it contains using that value of `$var`."),
insertText: "@each \\$${1:var} in ${2:list} {\n\t$0\n}",
insertTextFormat: cssLanguageTypes_1.InsertTextFormat.Snippet,
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
{
label: "@while",
documentation: l10n.t("While loop that takes an expression and repeatedly outputs the nested styles until the statement evaluates to `false`."),
insertText: "@while ${1:condition} {\n\t$0\n}",
insertTextFormat: cssLanguageTypes_1.InsertTextFormat.Snippet,
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
{
label: "@mixin",
documentation: l10n.t("Defines styles that can be re-used throughout the stylesheet with `@include`."),
insertText: "@mixin ${1:name} {\n\t$0\n}",
insertTextFormat: cssLanguageTypes_1.InsertTextFormat.Snippet,
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
{
label: "@include",
documentation: l10n.t("Includes the styles defined by another mixin into the current rule."),
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
{
label: "@function",
documentation: l10n.t("Defines complex operations that can be re-used throughout stylesheets."),
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
}
];
SCSSCompletion.scssModuleLoaders = [
{
label: "@use",
documentation: l10n.t("Loads mixins, functions, and variables from other Sass stylesheets as 'modules', and combines CSS from multiple stylesheets together."),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/at-rules/use' }],
insertText: "@use $0;",
insertTextFormat: cssLanguageTypes_1.InsertTextFormat.Snippet,
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
{
label: "@forward",
documentation: l10n.t("Loads a Sass stylesheet and makes its mixins, functions, and variables available when this stylesheet is loaded with the @use rule."),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/at-rules/forward' }],
insertText: "@forward $0;",
insertTextFormat: cssLanguageTypes_1.InsertTextFormat.Snippet,
kind: cssLanguageTypes_1.CompletionItemKind.Keyword
},
];
SCSSCompletion.scssModuleBuiltIns = [
{
label: 'sass:math',
documentation: l10n.t('Provides functions that operate on numbers.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/math' }]
},
{
label: 'sass:string',
documentation: l10n.t('Makes it easy to combine, search, or split apart strings.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/string' }]
},
{
label: 'sass:color',
documentation: l10n.t('Generates new colors based on existing ones, making it easy to build color themes.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/color' }]
},
{
label: 'sass:list',
documentation: l10n.t('Lets you access and modify values in lists.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/list' }]
},
{
label: 'sass:map',
documentation: l10n.t('Makes it possible to look up the value associated with a key in a map, and much more.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/map' }]
},
{
label: 'sass:selector',
documentation: l10n.t('Provides access to Sasss powerful selector engine.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/selector' }]
},
{
label: 'sass:meta',
documentation: l10n.t('Exposes the details of Sasss inner workings.'),
references: [{ name: sassDocumentationName, url: 'https://sass-lang.com/documentation/modules/meta' }]
},
];
exports.SCSSCompletion = SCSSCompletion;
/**
* Todo @Pine: Remove this and do it through custom data
*/
function addReferencesToDocumentation(items) {
items.forEach(i => {
if (i.documentation && i.references && i.references.length > 0) {
const markdownDoc = typeof i.documentation === 'string'
? { kind: 'markdown', value: i.documentation }
: { kind: 'markdown', value: i.documentation.value };
markdownDoc.value += '\n\n';
markdownDoc.value += i.references
.map(r => {
return `[${r.name}](${r.url})`;
})
.join(' | ');
i.documentation = markdownDoc;
}
});
}
});

View file

@ -0,0 +1,73 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./cssNavigation", "../parser/cssNodes", "vscode-uri", "../utils/strings"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.SCSSNavigation = void 0;
const cssNavigation_1 = require("./cssNavigation");
const nodes = require("../parser/cssNodes");
const vscode_uri_1 = require("vscode-uri");
const strings_1 = require("../utils/strings");
class SCSSNavigation extends cssNavigation_1.CSSNavigation {
constructor(fileSystemProvider) {
super(fileSystemProvider, true);
}
isRawStringDocumentLinkNode(node) {
return (super.isRawStringDocumentLinkNode(node) ||
node.type === nodes.NodeType.Use ||
node.type === nodes.NodeType.Forward);
}
async mapReference(target, isRawLink) {
if (this.fileSystemProvider && target && isRawLink) {
const pathVariations = toPathVariations(target);
for (const variation of pathVariations) {
if (await this.fileExists(variation)) {
return variation;
}
}
}
return target;
}
async resolveReference(target, documentUri, documentContext, isRawLink = false) {
if ((0, strings_1.startsWith)(target, 'sass:')) {
return undefined; // sass library
}
return super.resolveReference(target, documentUri, documentContext, isRawLink);
}
}
exports.SCSSNavigation = SCSSNavigation;
function toPathVariations(target) {
// No variation for links that ends with suffix
if (target.endsWith('.scss') || target.endsWith('.css')) {
return [target];
}
// If a link is like a/, try resolving a/index.scss and a/_index.scss
if (target.endsWith('/')) {
return [target + 'index.scss', target + '_index.scss'];
}
const targetUri = vscode_uri_1.URI.parse(target);
const basename = vscode_uri_1.Utils.basename(targetUri);
const dirname = vscode_uri_1.Utils.dirname(targetUri);
if (basename.startsWith('_')) {
// No variation for links such as _a
return [vscode_uri_1.Utils.joinPath(dirname, basename + '.scss').toString(true)];
}
return [
vscode_uri_1.Utils.joinPath(dirname, basename + '.scss').toString(true),
vscode_uri_1.Utils.joinPath(dirname, '_' + basename + '.scss').toString(true),
target + '/index.scss',
target + '/_index.scss',
vscode_uri_1.Utils.joinPath(dirname, basename + '.css').toString(true)
];
}
});

View file

@ -0,0 +1,508 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../parser/cssNodes", "../parser/cssScanner", "@vscode/l10n"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.selectorToElement = exports.SelectorPrinting = exports.toElement = exports.LabelElement = exports.RootElement = exports.Element = void 0;
const nodes = require("../parser/cssNodes");
const cssScanner_1 = require("../parser/cssScanner");
const l10n = require("@vscode/l10n");
class Element {
constructor() {
this.parent = null;
this.children = null;
this.attributes = null;
}
findAttribute(name) {
if (this.attributes) {
for (const attribute of this.attributes) {
if (attribute.name === name) {
return attribute.value;
}
}
}
return null;
}
addChild(child) {
if (child instanceof Element) {
child.parent = this;
}
if (!this.children) {
this.children = [];
}
this.children.push(child);
}
append(text) {
if (this.attributes) {
const last = this.attributes[this.attributes.length - 1];
last.value = last.value + text;
}
}
prepend(text) {
if (this.attributes) {
const first = this.attributes[0];
first.value = text + first.value;
}
}
findRoot() {
let curr = this;
while (curr.parent && !(curr.parent instanceof RootElement)) {
curr = curr.parent;
}
return curr;
}
removeChild(child) {
if (this.children) {
const index = this.children.indexOf(child);
if (index !== -1) {
this.children.splice(index, 1);
return true;
}
}
return false;
}
addAttr(name, value) {
if (!this.attributes) {
this.attributes = [];
}
for (const attribute of this.attributes) {
if (attribute.name === name) {
attribute.value += ' ' + value;
return;
}
}
this.attributes.push({ name, value });
}
clone(cloneChildren = true) {
const elem = new Element();
if (this.attributes) {
elem.attributes = [];
for (const attribute of this.attributes) {
elem.addAttr(attribute.name, attribute.value);
}
}
if (cloneChildren && this.children) {
elem.children = [];
for (let index = 0; index < this.children.length; index++) {
elem.addChild(this.children[index].clone());
}
}
return elem;
}
cloneWithParent() {
const clone = this.clone(false);
if (this.parent && !(this.parent instanceof RootElement)) {
const parentClone = this.parent.cloneWithParent();
parentClone.addChild(clone);
}
return clone;
}
}
exports.Element = Element;
class RootElement extends Element {
}
exports.RootElement = RootElement;
class LabelElement extends Element {
constructor(label) {
super();
this.addAttr('name', label);
}
}
exports.LabelElement = LabelElement;
class MarkedStringPrinter {
constructor(quote) {
this.quote = quote;
this.result = [];
// empty
}
print(element) {
this.result = [];
if (element instanceof RootElement) {
if (element.children) {
this.doPrint(element.children, 0);
}
}
else {
this.doPrint([element], 0);
}
const value = this.result.join('\n');
return [{ language: 'html', value }];
}
doPrint(elements, indent) {
for (const element of elements) {
this.doPrintElement(element, indent);
if (element.children) {
this.doPrint(element.children, indent + 1);
}
}
}
writeLine(level, content) {
const indent = new Array(level + 1).join(' ');
this.result.push(indent + content);
}
doPrintElement(element, indent) {
const name = element.findAttribute('name');
// special case: a simple label
if (element instanceof LabelElement || name === '\u2026') {
this.writeLine(indent, name);
return;
}
// the real deal
const content = ['<'];
// element name
if (name) {
content.push(name);
}
else {
content.push('element');
}
// attributes
if (element.attributes) {
for (const attr of element.attributes) {
if (attr.name !== 'name') {
content.push(' ');
content.push(attr.name);
const value = attr.value;
if (value) {
content.push('=');
content.push(quotes.ensure(value, this.quote));
}
}
}
}
content.push('>');
this.writeLine(indent, content.join(''));
}
}
var quotes;
(function (quotes) {
function ensure(value, which) {
return which + remove(value) + which;
}
quotes.ensure = ensure;
function remove(value) {
const match = value.match(/^['"](.*)["']$/);
if (match) {
return match[1];
}
return value;
}
quotes.remove = remove;
})(quotes || (quotes = {}));
class Specificity {
constructor() {
/** Count of identifiers (e.g., `#app`) */
this.id = 0;
/** Count of attributes (`[type="number"]`), classes (`.container-fluid`), and pseudo-classes (`:hover`) */
this.attr = 0;
/** Count of tag names (`div`), and pseudo-elements (`::before`) */
this.tag = 0;
}
}
function toElement(node, parentElement) {
let result = new Element();
for (const child of node.getChildren()) {
switch (child.type) {
case nodes.NodeType.SelectorCombinator:
if (parentElement) {
const segments = child.getText().split('&');
if (segments.length === 1) {
// should not happen
result.addAttr('name', segments[0]);
break;
}
result = parentElement.cloneWithParent();
if (segments[0]) {
const root = result.findRoot();
root.prepend(segments[0]);
}
for (let i = 1; i < segments.length; i++) {
if (i > 1) {
const clone = parentElement.cloneWithParent();
result.addChild(clone.findRoot());
result = clone;
}
result.append(segments[i]);
}
}
break;
case nodes.NodeType.SelectorPlaceholder:
if (child.matches('@at-root')) {
return result;
}
// fall through
case nodes.NodeType.ElementNameSelector:
const text = child.getText();
result.addAttr('name', text === '*' ? 'element' : unescape(text));
break;
case nodes.NodeType.ClassSelector:
result.addAttr('class', unescape(child.getText().substring(1)));
break;
case nodes.NodeType.IdentifierSelector:
result.addAttr('id', unescape(child.getText().substring(1)));
break;
case nodes.NodeType.MixinDeclaration:
result.addAttr('class', child.getName());
break;
case nodes.NodeType.PseudoSelector:
result.addAttr(unescape(child.getText()), '');
break;
case nodes.NodeType.AttributeSelector:
const selector = child;
const identifier = selector.getIdentifier();
if (identifier) {
const expression = selector.getValue();
const operator = selector.getOperator();
let value;
if (expression && operator) {
switch (unescape(operator.getText())) {
case '|=':
// excatly or followed by -words
value = `${quotes.remove(unescape(expression.getText()))}-\u2026`;
break;
case '^=':
// prefix
value = `${quotes.remove(unescape(expression.getText()))}\u2026`;
break;
case '$=':
// suffix
value = `\u2026${quotes.remove(unescape(expression.getText()))}`;
break;
case '~=':
// one of a list of words
value = ` \u2026 ${quotes.remove(unescape(expression.getText()))} \u2026 `;
break;
case '*=':
// substring
value = `\u2026${quotes.remove(unescape(expression.getText()))}\u2026`;
break;
default:
value = quotes.remove(unescape(expression.getText()));
break;
}
}
result.addAttr(unescape(identifier.getText()), value);
}
break;
}
}
return result;
}
exports.toElement = toElement;
function unescape(content) {
const scanner = new cssScanner_1.Scanner();
scanner.setSource(content);
const token = scanner.scanUnquotedString();
if (token) {
return token.text;
}
return content;
}
class SelectorPrinting {
constructor(cssDataManager) {
this.cssDataManager = cssDataManager;
}
selectorToMarkedString(node) {
const root = selectorToElement(node);
if (root) {
const markedStrings = new MarkedStringPrinter('"').print(root);
markedStrings.push(this.selectorToSpecificityMarkedString(node));
return markedStrings;
}
else {
return [];
}
}
simpleSelectorToMarkedString(node) {
const element = toElement(node);
const markedStrings = new MarkedStringPrinter('"').print(element);
markedStrings.push(this.selectorToSpecificityMarkedString(node));
return markedStrings;
}
isPseudoElementIdentifier(text) {
const match = text.match(/^::?([\w-]+)/);
if (!match) {
return false;
}
return !!this.cssDataManager.getPseudoElement("::" + match[1]);
}
selectorToSpecificityMarkedString(node) {
//https://www.w3.org/TR/selectors-3/#specificity
const calculateScore = (node) => {
const specificity = new Specificity();
elementLoop: for (const element of node.getChildren()) {
switch (element.type) {
case nodes.NodeType.IdentifierSelector:
specificity.id++;
break;
case nodes.NodeType.ClassSelector:
case nodes.NodeType.AttributeSelector:
specificity.attr++;
break;
case nodes.NodeType.ElementNameSelector:
//ignore universal selector
if (element.matches("*")) {
break;
}
specificity.tag++;
break;
case nodes.NodeType.PseudoSelector:
const text = element.getText();
if (this.isPseudoElementIdentifier(text)) {
specificity.tag++; // pseudo element
continue elementLoop;
}
// where and child selectors have zero specificity
if (text.match(/^:where/i)) {
continue elementLoop;
}
// the most specific child selector
if (text.match(/^:(not|has|is)/i) && element.getChildren().length > 0) {
let mostSpecificListItem = new Specificity();
for (const containerElement of element.getChildren()) {
let list;
if (containerElement.type === nodes.NodeType.Undefined) { // containerElement is a list of selectors
list = containerElement.getChildren();
}
else { // containerElement is a selector
list = [containerElement];
}
for (const childElement of containerElement.getChildren()) {
const itemSpecificity = calculateScore(childElement);
if (itemSpecificity.id > mostSpecificListItem.id) {
mostSpecificListItem = itemSpecificity;
continue;
}
else if (itemSpecificity.id < mostSpecificListItem.id) {
continue;
}
if (itemSpecificity.attr > mostSpecificListItem.attr) {
mostSpecificListItem = itemSpecificity;
continue;
}
else if (itemSpecificity.attr < mostSpecificListItem.attr) {
continue;
}
if (itemSpecificity.tag > mostSpecificListItem.tag) {
mostSpecificListItem = itemSpecificity;
continue;
}
}
}
specificity.id += mostSpecificListItem.id;
specificity.attr += mostSpecificListItem.attr;
specificity.tag += mostSpecificListItem.tag;
continue elementLoop;
}
specificity.attr++; //pseudo class
continue elementLoop;
}
if (element.getChildren().length > 0) {
const itemSpecificity = calculateScore(element);
specificity.id += itemSpecificity.id;
specificity.attr += itemSpecificity.attr;
specificity.tag += itemSpecificity.tag;
}
}
return specificity;
};
const specificity = calculateScore(node);
return `[${l10n.t("Selector Specificity")}](https://developer.mozilla.org/docs/Web/CSS/Specificity): (${specificity.id}, ${specificity.attr}, ${specificity.tag})`;
}
}
exports.SelectorPrinting = SelectorPrinting;
class SelectorElementBuilder {
constructor(element) {
this.prev = null;
this.element = element;
}
processSelector(selector) {
let parentElement = null;
if (!(this.element instanceof RootElement)) {
if (selector.getChildren().some((c) => c.hasChildren() && c.getChild(0).type === nodes.NodeType.SelectorCombinator)) {
const curr = this.element.findRoot();
if (curr.parent instanceof RootElement) {
parentElement = this.element;
this.element = curr.parent;
this.element.removeChild(curr);
this.prev = null;
}
}
}
for (const selectorChild of selector.getChildren()) {
if (selectorChild instanceof nodes.SimpleSelector) {
if (this.prev instanceof nodes.SimpleSelector) {
const labelElement = new LabelElement('\u2026');
this.element.addChild(labelElement);
this.element = labelElement;
}
else if (this.prev && (this.prev.matches('+') || this.prev.matches('~')) && this.element.parent) {
this.element = this.element.parent;
}
if (this.prev && this.prev.matches('~')) {
this.element.addChild(new LabelElement('\u22EE'));
}
const thisElement = toElement(selectorChild, parentElement);
const root = thisElement.findRoot();
this.element.addChild(root);
this.element = thisElement;
}
if (selectorChild instanceof nodes.SimpleSelector ||
selectorChild.type === nodes.NodeType.SelectorCombinatorParent ||
selectorChild.type === nodes.NodeType.SelectorCombinatorShadowPiercingDescendant ||
selectorChild.type === nodes.NodeType.SelectorCombinatorSibling ||
selectorChild.type === nodes.NodeType.SelectorCombinatorAllSiblings) {
this.prev = selectorChild;
}
}
}
}
function isNewSelectorContext(node) {
switch (node.type) {
case nodes.NodeType.MixinDeclaration:
case nodes.NodeType.Stylesheet:
return true;
}
return false;
}
function selectorToElement(node) {
if (node.matches('@at-root')) {
return null;
}
const root = new RootElement();
const parentRuleSets = [];
const ruleSet = node.getParent();
if (ruleSet instanceof nodes.RuleSet) {
let parent = ruleSet.getParent(); // parent of the selector's ruleset
while (parent && !isNewSelectorContext(parent)) {
if (parent instanceof nodes.RuleSet) {
if (parent.getSelectors().matches('@at-root')) {
break;
}
parentRuleSets.push(parent);
}
parent = parent.getParent();
}
}
const builder = new SelectorElementBuilder(root);
for (let i = parentRuleSets.length - 1; i >= 0; i--) {
const selector = parentRuleSets[i].getSelectors().getChild(0);
if (selector) {
builder.processSelector(selector);
}
}
builder.processSelector(node);
return root;
}
exports.selectorToElement = selectorToElement;
});

View file

@ -0,0 +1,55 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.union = exports.includes = exports.findFirst = void 0;
/**
* Takes a sorted array and a function p. The array is sorted in such a way that all elements where p(x) is false
* are located before all elements where p(x) is true.
* @returns the least x for which p(x) is true or array.length if no element fullfills the given function.
*/
function findFirst(array, p) {
let low = 0, high = array.length;
if (high === 0) {
return 0; // no children
}
while (low < high) {
let mid = Math.floor((low + high) / 2);
if (p(array[mid])) {
high = mid;
}
else {
low = mid + 1;
}
}
return low;
}
exports.findFirst = findFirst;
function includes(array, item) {
return array.indexOf(item) !== -1;
}
exports.includes = includes;
function union(...arrays) {
const result = [];
for (const array of arrays) {
for (const item of array) {
if (!includes(result, item)) {
result.push(item);
}
}
}
return result;
}
exports.union = union;
});

View file

@ -0,0 +1,25 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.isDefined = exports.values = void 0;
function values(obj) {
return Object.keys(obj).map(key => obj[key]);
}
exports.values = values;
function isDefined(obj) {
return typeof obj !== 'undefined';
}
exports.isDefined = isDefined;
});

View file

@ -0,0 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "vscode-uri"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.joinPath = exports.dirname = void 0;
const vscode_uri_1 = require("vscode-uri");
function dirname(uriString) {
return vscode_uri_1.Utils.dirname(vscode_uri_1.URI.parse(uriString)).toString(true);
}
exports.dirname = dirname;
function joinPath(uriString, ...paths) {
return vscode_uri_1.Utils.joinPath(vscode_uri_1.URI.parse(uriString), ...paths).toString(true);
}
exports.joinPath = joinPath;
});

View file

@ -0,0 +1,120 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.repeat = exports.trim = exports.getLimitedString = exports.difference = exports.endsWith = exports.startsWith = void 0;
function startsWith(haystack, needle) {
if (haystack.length < needle.length) {
return false;
}
for (let i = 0; i < needle.length; i++) {
if (haystack[i] !== needle[i]) {
return false;
}
}
return true;
}
exports.startsWith = startsWith;
/**
* Determines if haystack ends with needle.
*/
function endsWith(haystack, needle) {
let diff = haystack.length - needle.length;
if (diff > 0) {
return haystack.lastIndexOf(needle) === diff;
}
else if (diff === 0) {
return haystack === needle;
}
else {
return false;
}
}
exports.endsWith = endsWith;
/**
* Computes the difference score for two strings. More similar strings have a higher score.
* We use largest common subsequence dynamic programming approach but penalize in the end for length differences.
* Strings that have a large length difference will get a bad default score 0.
* Complexity - both time and space O(first.length * second.length)
* Dynamic programming LCS computation http://en.wikipedia.org/wiki/Longest_common_subsequence_problem
*
* @param first a string
* @param second a string
*/
function difference(first, second, maxLenDelta = 4) {
let lengthDifference = Math.abs(first.length - second.length);
// We only compute score if length of the currentWord and length of entry.name are similar.
if (lengthDifference > maxLenDelta) {
return 0;
}
// Initialize LCS (largest common subsequence) matrix.
let LCS = [];
let zeroArray = [];
let i, j;
for (i = 0; i < second.length + 1; ++i) {
zeroArray.push(0);
}
for (i = 0; i < first.length + 1; ++i) {
LCS.push(zeroArray);
}
for (i = 1; i < first.length + 1; ++i) {
for (j = 1; j < second.length + 1; ++j) {
if (first[i - 1] === second[j - 1]) {
LCS[i][j] = LCS[i - 1][j - 1] + 1;
}
else {
LCS[i][j] = Math.max(LCS[i - 1][j], LCS[i][j - 1]);
}
}
}
return LCS[first.length][second.length] - Math.sqrt(lengthDifference);
}
exports.difference = difference;
/**
* Limit of string length.
*/
function getLimitedString(str, ellipsis = true) {
if (!str) {
return '';
}
if (str.length < 140) {
return str;
}
return str.slice(0, 140) + (ellipsis ? '\u2026' : '');
}
exports.getLimitedString = getLimitedString;
/**
* Limit of string length.
*/
function trim(str, regexp) {
const m = regexp.exec(str);
if (m && m[0].length) {
return str.substr(0, str.length - m[0].length);
}
return str;
}
exports.trim = trim;
function repeat(value, count) {
let s = '';
while (count > 0) {
if ((count & 1) === 1) {
s += value;
}
value += value;
count = count >>> 1;
}
return s;
}
exports.repeat = repeat;
});