Clean up dependencies
All checks were successful
Build and Push / build (push) Successful in 18s

This commit is contained in:
2026-02-16 15:12:59 -05:00
parent d181f77fb2
commit 2f15523a55
14941 changed files with 0 additions and 2078483 deletions

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Sergey Chikuyonok <serge.che@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,76 +0,0 @@
# Emmet markup abbreviation parser
Parses given Emmet *markup* abbreviation into AST. Parsing is performed in two steps: first it tokenizes given abbreviation (useful for syntax highlighting in editors) and then tokens are analyzed and converted into AST nodes as plain, JSON-serializable objects.
Note that AST tree in most cases cannot be used directly for output: for example, AST node produced from `.foo.bar` element misses element name and contains two `class` attributes with `foo` and `bar` values (not a single `class` with `foo bar` value).
## Usage
You can install it via npm:
```bash
npm install @emmetio/abbreviation
```
Then add it into your project:
```js
import parse from '@emmetio/abbreviation';
const tree = parse('div#foo>span.bar*3');
/* {
type: 'Abbreviation',
children: [{
type: 'AbbreviationNode',
name: 'div',
attributes: [...],
children: [...]
}]
} */
```
The returned tree contains `AbbreviationNode` items: a node with name, attributes and/or text content. E.g. an element that can be represented somehow. Repeated and grouped nodes like `a>(b+c)*3` are automatically converted and duplicated as distinct `AbbreviationNode` with distinct `.repeat` property which identifies node in repeating sequence.
## Abbreviation syntax
Emmet abbreviation element has the following basic parts:
```
name.class#id[attributes?, ...]{text value}*repeater/
```
* `name` — element name, like `div`, `span` etc. Stored as `node.name` property.
* `[attributes]` — list of attributes. Each attribute is stored as [`AbbreviationAttribute`](/src/types.ts) instance and can be accessed by `node.getAttribute(name)`. Each attribute can be written in different formats:
* `attr` — attribute with empty value.
* `attr=value` — attribute with value. The `value` may contain any character except space or `]`.
* `attr="value"` or `attr='value'` — attribute with value in quotes. Quotes are automatically removed. Expression values like `attr={value}` are supported and can be identified by `valueType: "expression"` property.
* `attr.` — boolean attribute, e.g. attribute without value, like `required` in `<input>`.
* `!attr` implicit attribute, will be outputted if its value is not empty. Used as a placeholder to preserve attribute order in output.
* `./non/attr/value` — value for default attribute. In other words, anything that doesnt match a attribute name characters. Can be a single- or double-quotted as well. Default attribute is stored with `null` as name and should be used later, for example, to resolve predefined attributes.
* `.class` — shorthand for `class` attribute. Note that an element can have multiple classes, like `.class1.class2.class3`.
* `#id` — shorthand for `id` attribute.
* `{text}` — nodes text content
* `*N` — element repeater, tells parser to create `N` copies of given node.
* `/` — optional self-closing operator. Marks element with `node.selfClosing = true`.
### Operators
Each element of abbreviation must be separated with any of these operators:
```
elem1+elem2>elem3
```
* `+` — sibling operator, adds next element as a next sibling of current element in tree.
* `>` — child operator, adds next element as a child of current element.
* `^` — climb-up operator, adds next element as a child of current elements parent node. Multiple climb-up operators are allowed, each operator moves one level up by tree.
### Groups
A set of elements could be grouped using `()`, mostly for repeating and for easier elements nesting:
```
a>(b>c+d)*4+(e+f)
```
Groups can be optionally concatenated with `+` operator.

View File

@@ -1,8 +0,0 @@
import { TokenGroup } from './parser/index.js';
import { Abbreviation, ParserOptions } from './types.js';
/**
* Converts given token-based abbreviation into simplified and unrolled node-based
* abbreviation
*/
export default function convert(abbr: TokenGroup, options?: ParserOptions): Abbreviation;
export declare function isGroup(node: any): node is TokenGroup;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,12 +0,0 @@
import parse, { type TokenGroup } from './parser/index.js';
import tokenize, { getToken, type AllTokens } from './tokenizer/index.js';
import convert from './convert.js';
import type { ParserOptions } from './types.js';
export { parse, tokenize, getToken, convert };
export * from './tokenizer/tokens.js';
export * from './types.js';
export type MarkupAbbreviation = TokenGroup;
/**
* Parses given abbreviation into node tree
*/
export default function parseAbbreviation(abbr: string | AllTokens[], options?: ParserOptions): import("./types.js").Abbreviation;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,17 +0,0 @@
import { AllTokens } from '../tokenizer/index.js';
export interface TokenScanner {
tokens: AllTokens[];
start: number;
pos: number;
size: number;
}
type TestFn = (token?: AllTokens) => boolean;
export default function tokenScanner(tokens: AllTokens[]): TokenScanner;
export declare function peek(scanner: TokenScanner): AllTokens | undefined;
export declare function next(scanner: TokenScanner): AllTokens | undefined;
export declare function slice(scanner: TokenScanner, from?: number, to?: number): AllTokens[];
export declare function readable(scanner: TokenScanner): boolean;
export declare function consume(scanner: TokenScanner, test: TestFn): boolean;
export declare function error(scanner: TokenScanner, message: string, token?: AllTokens | undefined): Error;
export declare function consumeWhile(scanner: TokenScanner, test: TestFn): boolean;
export {};

View File

@@ -1,31 +0,0 @@
import type { NameToken, ValueToken, Repeater, AllTokens, BracketType, Bracket, Operator, OperatorType, Quote } from '../tokenizer/index.js';
import type { ParserOptions } from '../types.js';
export type TokenStatement = TokenElement | TokenGroup;
export interface TokenAttribute {
name?: ValueToken[];
value?: ValueToken[];
expression?: boolean;
/**
* Indicates that current attribute was repeated multiple times in a row.
* Used to alter output of multiple shorthand attributes like `..` (double class)
*/
multiple?: boolean;
}
export interface TokenElement {
type: 'TokenElement';
name?: NameToken[];
attributes?: TokenAttribute[];
value?: ValueToken[];
repeat?: Repeater;
selfClose: boolean;
elements: TokenStatement[];
}
export interface TokenGroup {
type: 'TokenGroup';
elements: TokenStatement[];
repeat?: Repeater;
}
export default function abbreviation(abbr: AllTokens[], options?: ParserOptions): TokenGroup;
export declare function isBracket(token: AllTokens | undefined, context?: BracketType, isOpen?: boolean): token is Bracket;
export declare function isOperator(token: AllTokens | undefined, type?: OperatorType): token is Operator;
export declare function isQuote(token: AllTokens | undefined, isSingle?: boolean): token is Quote;

View File

@@ -1,6 +0,0 @@
import { ValueToken } from './tokenizer/tokens.js';
import { ConvertState } from './types.js';
/**
* Converts given value token to string
*/
export default function stringify(token: ValueToken, state: ConvertState): string;

View File

@@ -1,13 +0,0 @@
import Scanner from '@emmetio/scanner';
import type { BracketType, AllTokens } from './tokens.js';
export * from './tokens.js';
type Context = {
[ctx in BracketType]: number;
} & {
quote: number;
};
export default function tokenize(source: string): AllTokens[];
/**
* Returns next token from given scanner, if possible
*/
export declare function getToken(scanner: Scanner, ctx: Context): AllTokens | undefined;

View File

@@ -1,63 +0,0 @@
export type OperatorType = 'child' | 'sibling' | 'climb' | 'class' | 'id' | 'close' | 'equal';
export type BracketType = 'group' | 'attribute' | 'expression';
export type AllTokens = Bracket | Field | Literal | Operator | Quote | Repeater | RepeaterNumber | RepeaterPlaceholder | WhiteSpace;
export type NameToken = Literal | RepeaterNumber;
export type ValueToken = Literal | Quote | Bracket | Field | RepeaterPlaceholder | RepeaterNumber;
export interface Token {
type: string;
/** Location of token start in source */
start?: number;
/** Location of token end in source */
end?: number;
}
export interface Repeater extends Token {
type: 'Repeater';
/** How many times context element should be repeated */
count: number;
/** Position of context element in its repeating sequence */
value: number;
/** Repeater is implicit, e.g. repeated by the amount of text lines selected by user */
implicit: boolean;
}
export interface RepeaterNumber extends Token {
type: 'RepeaterNumber';
/** Size of repeater content, e.g. the amount consequent numbering characters */
size: number;
/** Should output numbering in reverse order? */
reverse: boolean;
/** Base value to start numbering from */
base: number;
/** Parent offset from which numbering should be used */
parent: number;
}
export interface RepeaterPlaceholder extends Token {
type: 'RepeaterPlaceholder';
/** Value to insert instead of placeholder */
value?: string;
}
export interface Field extends Token {
type: 'Field';
index?: number;
name: string;
}
export interface Operator extends Token {
type: 'Operator';
operator: OperatorType;
}
export interface Bracket extends Token {
type: 'Bracket';
open: boolean;
context: BracketType;
}
export interface Quote extends Token {
type: 'Quote';
single: boolean;
}
export interface Literal extends Token {
type: 'Literal';
value: string;
}
export interface WhiteSpace extends Token {
type: 'WhiteSpace';
value: string;
}

View File

@@ -1,53 +0,0 @@
import type Scanner from '@emmetio/scanner';
export declare const enum Chars {
/** `{` character */
CurlyBracketOpen = 123,
/** `}` character */
CurlyBracketClose = 125,
/** `\\` character */
Escape = 92,
/** `=` character */
Equals = 61,
/** `[` character */
SquareBracketOpen = 91,
/** `]` character */
SquareBracketClose = 93,
/** `*` character */
Asterisk = 42,
/** `#` character */
Hash = 35,
/** `$` character */
Dollar = 36,
/** `-` character */
Dash = 45,
/** `.` character */
Dot = 46,
/** `/` character */
Slash = 47,
/** `:` character */
Colon = 58,
/** `!` character */
Excl = 33,
/** `@` character */
At = 64,
/** `_` character */
Underscore = 95,
/** `(` character */
RoundBracketOpen = 40,
/** `)` character */
RoundBracketClose = 41,
/** `+` character */
Sibling = 43,
/** `>` character */
Child = 62,
/** `^` character */
Climb = 94,
/** `'` character */
SingleQuote = 39,
/** `""` character */
DoubleQuote = 34
}
/**
* If consumes escape character, sets current stream range to escaped value
*/
export declare function escaped(scanner: Scanner): boolean;

View File

@@ -1,56 +0,0 @@
import { Field, Repeater } from './tokenizer/index.js';
export interface ParserOptions {
/** Text strings to insert into implicitly repeated elements */
text?: string | string[];
/** Variable values for `${var}` tokens */
variables?: {
[name: string]: string;
};
/** Max amount of repeated elements in abbreviation */
maxRepeat?: number;
/** Enabled JSX parsing mode */
jsx?: boolean;
/** Enable inserting text into href attribute of links */
href?: boolean;
}
export interface ConvertState {
inserted: boolean;
text?: string | string[];
cleanText?: string | string[];
repeatGuard: number;
/** Context repeaters, e.g. all actual repeaters from parent */
repeaters: Repeater[];
getText(pos?: number): string;
getVariable(name: string): string;
}
export type Value = string | Field;
export type AttributeType = 'raw' | 'singleQuote' | 'doubleQuote' | 'expression';
export interface Abbreviation {
type: 'Abbreviation';
children: AbbreviationNode[];
}
export interface AbbreviationNode {
type: 'AbbreviationNode';
name?: string;
value?: Value[];
repeat?: Repeater;
attributes?: AbbreviationAttribute[];
children: AbbreviationNode[];
/** Indicates current element is self-closing, e.g. should not contain closing pair */
selfClosing?: boolean;
}
export interface AbbreviationAttribute {
name?: string;
value?: Value[];
/** Indicates type of value stored in `.value` property */
valueType: AttributeType;
/** Attribute is boolean (e.g.name equals value) */
boolean?: boolean;
/** Attribute is implied (e.g.must be outputted only if contains non-null value) */
implied?: boolean;
/**
* Internal property that indicates that given attribute was specified
* more than once as a shorthand. E.g. `..` is a multiple `class` attribute
*/
multiple?: boolean;
}

View File

@@ -1,54 +0,0 @@
{
"name": "@emmetio/abbreviation",
"version": "2.3.3",
"description": "Emmet standalone abbreviation parser",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module",
"exports": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"scripts": {
"test": "mocha",
"build": "rollup -c",
"clean": "rimraf ./dist",
"prepublishOnly": "npm run clean && npm run build && npm test"
},
"keywords": [
"emmet",
"abbreviation"
],
"author": "Sergey Chikuyonok <serge.che@gmail.com>",
"license": "MIT",
"dependencies": {
"@emmetio/scanner": "^1.0.4"
},
"devDependencies": {
"@rollup/plugin-typescript": "^10.0.1",
"@types/mocha": "^10.0.1",
"@types/node": "^18.11.18",
"mocha": "^10.2.0",
"rimraf": "^5.0.0",
"rollup": "^3.9.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
"directories": {
"test": "test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/emmetio/emmet.git"
},
"bugs": {
"url": "https://github.com/emmetio/emmet/issues"
},
"homepage": "https://github.com/emmetio/emmet#readme",
"mocha": {
"loader": "ts-node/esm",
"spec": "./test/*.ts"
},
"gitHead": "fce2127ece65adbb293a40aa0577e4558658c559"
}

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Sergey Chikuyonok <serge.che@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,39 +0,0 @@
# Emmet stylesheet abbreviation parser
Parses given Emmet *stylesheet* abbreviation into AST. Parsing is performed in two steps: first it tokenizes given abbreviation (useful for syntax highlighting in editors) and then tokens are analyzed and converted into AST nodes as plain, JSON-serializable objects.
Unlike in [markup abbreviations](/packages/abbreviation), elements in stylesheet abbreviations cannot be nested and contain attributes, but allow embedded values in element names.
## Usage
You can install it via npm:
```bash
npm install @emmetio/css-abbreviation
```
Then add it into your project:
```js
import parse from '@emmetio/css-abbreviation';
const props = parse('p10+poa');
/* [{
name: 'p',
value: [{ type: 'CSSValue', value: [...] }],
important: false
}, {
name: 'poa',
value: [],
important: false
}] */
```
The returned result is an array of `CSSProperty` items: a node with name and values.
## Abbreviation syntax
Emmet stylesheet abbreviation element may start with name and followed by values, optionally chained with `-` delimiter. In most cases, actual CSS properties doesnt have numbers in their names (or at least they are not used in abbreviation shortcuts) so a number right after alpha characters is considered as *embedded value*, as well as colors starting with `#` character: `p10`, `bg#fc0` etc. If implicit name/value boundary cant be identified, you should use `-` as value separator: `m-a`, `p10-20` etc.
### Operators
Since CSS properties cant be nested, the only available operator is `+`.

View File

@@ -1,687 +0,0 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var Scanner = require('@emmetio/scanner');
exports.OperatorType = void 0;
(function (OperatorType) {
OperatorType["Sibling"] = "+";
OperatorType["Important"] = "!";
OperatorType["ArgumentDelimiter"] = ",";
OperatorType["ValueDelimiter"] = "-";
OperatorType["PropertyDelimiter"] = ":";
})(exports.OperatorType || (exports.OperatorType = {}));
var Chars;
(function (Chars) {
/** `#` character */
Chars[Chars["Hash"] = 35] = "Hash";
/** `$` character */
Chars[Chars["Dollar"] = 36] = "Dollar";
/** `-` character */
Chars[Chars["Dash"] = 45] = "Dash";
/** `.` character */
Chars[Chars["Dot"] = 46] = "Dot";
/** `:` character */
Chars[Chars["Colon"] = 58] = "Colon";
/** `,` character */
Chars[Chars["Comma"] = 44] = "Comma";
/** `!` character */
Chars[Chars["Excl"] = 33] = "Excl";
/** `@` character */
Chars[Chars["At"] = 64] = "At";
/** `%` character */
Chars[Chars["Percent"] = 37] = "Percent";
/** `_` character */
Chars[Chars["Underscore"] = 95] = "Underscore";
/** `(` character */
Chars[Chars["RoundBracketOpen"] = 40] = "RoundBracketOpen";
/** `)` character */
Chars[Chars["RoundBracketClose"] = 41] = "RoundBracketClose";
/** `{` character */
Chars[Chars["CurlyBracketOpen"] = 123] = "CurlyBracketOpen";
/** `}` character */
Chars[Chars["CurlyBracketClose"] = 125] = "CurlyBracketClose";
/** `+` character */
Chars[Chars["Sibling"] = 43] = "Sibling";
/** `'` character */
Chars[Chars["SingleQuote"] = 39] = "SingleQuote";
/** `"` character */
Chars[Chars["DoubleQuote"] = 34] = "DoubleQuote";
/** `t` character */
Chars[Chars["Transparent"] = 116] = "Transparent";
/** `/` character */
Chars[Chars["Slash"] = 47] = "Slash";
})(Chars || (Chars = {}));
function tokenize(abbr, isValue) {
let brackets = 0;
let token;
const scanner = new Scanner(abbr);
const tokens = [];
while (!scanner.eof()) {
token = getToken(scanner, brackets === 0 && !isValue);
if (!token) {
throw scanner.error('Unexpected character');
}
if (token.type === 'Bracket') {
if (!brackets && token.open) {
mergeTokens(scanner, tokens);
}
brackets += token.open ? 1 : -1;
if (brackets < 0) {
throw scanner.error('Unexpected bracket', token.start);
}
}
tokens.push(token);
// Forcibly consume next operator after unit-less numeric value or color:
// next dash `-` must be used as value delimiter
if (shouldConsumeDashAfter(token) && (token = operator(scanner))) {
tokens.push(token);
}
}
return tokens;
}
/**
* Returns next token from given scanner, if possible
*/
function getToken(scanner, short) {
return field(scanner)
|| customProperty(scanner)
|| numberValue(scanner)
|| colorValue(scanner)
|| stringValue(scanner)
|| bracket(scanner)
|| operator(scanner)
|| whiteSpace(scanner)
|| literal(scanner, short);
}
function field(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars.Dollar) && scanner.eat(Chars.CurlyBracketOpen)) {
scanner.start = scanner.pos;
let index;
let name = '';
if (scanner.eatWhile(Scanner.isNumber)) {
// Its a field
index = Number(scanner.current());
name = scanner.eat(Chars.Colon) ? consumePlaceholder(scanner) : '';
}
else if (Scanner.isAlpha(scanner.peek())) {
// Its a variable
name = consumePlaceholder(scanner);
}
if (scanner.eat(Chars.CurlyBracketClose)) {
return {
type: 'Field',
index, name,
start,
end: scanner.pos
};
}
throw scanner.error('Expecting }');
}
// If we reached here then theres no valid field here, revert
// back to starting position
scanner.pos = start;
}
/**
* Consumes a placeholder: value right after `:` in field. Could be empty
*/
function consumePlaceholder(stream) {
const stack = [];
stream.start = stream.pos;
while (!stream.eof()) {
if (stream.eat(Chars.CurlyBracketOpen)) {
stack.push(stream.pos);
}
else if (stream.eat(Chars.CurlyBracketClose)) {
if (!stack.length) {
stream.pos--;
break;
}
stack.pop();
}
else {
stream.pos++;
}
}
if (stack.length) {
stream.pos = stack.pop();
throw stream.error(`Expecting }`);
}
return stream.current();
}
/**
* Consumes literal from given scanner
* @param short Use short notation for consuming value.
* The difference between “short” and “full” notation is that first one uses
* alpha characters only and used for extracting keywords from abbreviation,
* while “full” notation also supports numbers and dashes
*/
function literal(scanner, short) {
const start = scanner.pos;
if (scanner.eat(isIdentPrefix)) {
// SCSS or LESS variable
// NB a bit dirty hack: if abbreviation starts with identifier prefix,
// consume alpha characters only to allow embedded variables
scanner.eatWhile(start ? isKeyword : isLiteral$1);
}
else if (scanner.eat(Scanner.isAlphaWord)) {
scanner.eatWhile(short ? isLiteral$1 : isKeyword);
}
else {
// Allow dots only at the beginning of literal
scanner.eat(Chars.Dot);
scanner.eatWhile(isLiteral$1);
}
if (start !== scanner.pos) {
scanner.start = start;
return createLiteral(scanner, scanner.start = start);
}
}
function createLiteral(scanner, start = scanner.start, end = scanner.pos) {
return {
type: 'Literal',
value: scanner.substring(start, end),
start,
end
};
}
/**
* Consumes numeric CSS value (number with optional unit) from current stream,
* if possible
*/
function numberValue(scanner) {
const start = scanner.pos;
if (consumeNumber(scanner)) {
scanner.start = start;
const rawValue = scanner.current();
// eat unit, which can be a % or alpha word
scanner.start = scanner.pos;
scanner.eat(Chars.Percent) || scanner.eatWhile(Scanner.isAlphaWord);
return {
type: 'NumberValue',
value: Number(rawValue),
rawValue,
unit: scanner.current(),
start,
end: scanner.pos
};
}
}
/**
* Consumes quoted string value from given scanner
*/
function stringValue(scanner) {
const ch = scanner.peek();
const start = scanner.pos;
let finished = false;
if (Scanner.isQuote(ch)) {
scanner.pos++;
while (!scanner.eof()) {
// Do not throw error on malformed string
if (scanner.eat(ch)) {
finished = true;
break;
}
else {
scanner.pos++;
}
}
scanner.start = start;
return {
type: 'StringValue',
value: scanner.substring(start + 1, scanner.pos - (finished ? 1 : 0)),
quote: ch === Chars.SingleQuote ? 'single' : 'double',
start,
end: scanner.pos
};
}
}
/**
* Consumes a color token from given string
*/
function colorValue(scanner) {
// supported color variations:
// #abc → #aabbccc
// #0 → #000000
// #fff.5 → rgba(255, 255, 255, 0.5)
// #t → transparent
const start = scanner.pos;
if (scanner.eat(Chars.Hash)) {
const valueStart = scanner.pos;
let color = '';
let alpha = '';
if (scanner.eatWhile(isHex)) {
color = scanner.substring(valueStart, scanner.pos);
alpha = colorAlpha(scanner);
}
else if (scanner.eat(Chars.Transparent)) {
color = '0';
alpha = colorAlpha(scanner) || '0';
}
else {
alpha = colorAlpha(scanner);
}
if (color || alpha || scanner.eof()) {
const { r, g, b, a } = parseColor(color, alpha);
return {
type: 'ColorValue',
r, g, b, a,
raw: scanner.substring(start + 1, scanner.pos),
start,
end: scanner.pos
};
}
else {
// Consumed # but no actual value: invalid color value, treat it as literal
return createLiteral(scanner, start);
}
}
scanner.pos = start;
}
/**
* Consumes alpha value of color: `.1`
*/
function colorAlpha(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars.Dot)) {
scanner.start = start;
if (scanner.eatWhile(Scanner.isNumber)) {
return scanner.current();
}
return '1';
}
return '';
}
/**
* Consumes white space characters as string literal from given scanner
*/
function whiteSpace(scanner) {
const start = scanner.pos;
if (scanner.eatWhile(Scanner.isSpace)) {
return {
type: 'WhiteSpace',
start,
end: scanner.pos
};
}
}
/**
* Consumes custom CSS property: --foo-bar
*/
function customProperty(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars.Dash) && scanner.eat(Chars.Dash)) {
scanner.start = start;
scanner.eatWhile(isKeyword);
return {
type: 'CustomProperty',
value: scanner.current(),
start,
end: scanner.pos
};
}
scanner.pos = start;
}
/**
* Consumes bracket from given scanner
*/
function bracket(scanner) {
const ch = scanner.peek();
if (isBracket$1(ch)) {
return {
type: 'Bracket',
open: ch === Chars.RoundBracketOpen,
start: scanner.pos++,
end: scanner.pos
};
}
}
/**
* Consumes operator from given scanner
*/
function operator(scanner) {
const op = operatorType(scanner.peek());
if (op) {
return {
type: 'Operator',
operator: op,
start: scanner.pos++,
end: scanner.pos
};
}
}
/**
* Eats number value from given stream
* @return Returns `true` if number was consumed
*/
function consumeNumber(stream) {
const start = stream.pos;
stream.eat(Chars.Dash);
const afterNegative = stream.pos;
const hasDecimal = stream.eatWhile(Scanner.isNumber);
const prevPos = stream.pos;
if (stream.eat(Chars.Dot)) {
// Its perfectly valid to have numbers like `1.`, which enforces
// value to float unit type
const hasFloat = stream.eatWhile(Scanner.isNumber);
if (!hasDecimal && !hasFloat) {
// Lone dot
stream.pos = prevPos;
}
}
// Edge case: consumed dash only: not a number, bail-out
if (stream.pos === afterNegative) {
stream.pos = start;
}
return stream.pos !== start;
}
function isIdentPrefix(code) {
return code === Chars.At || code === Chars.Dollar;
}
/**
* If given character is an operator, returns its type
*/
function operatorType(ch) {
return (ch === Chars.Sibling && exports.OperatorType.Sibling)
|| (ch === Chars.Excl && exports.OperatorType.Important)
|| (ch === Chars.Comma && exports.OperatorType.ArgumentDelimiter)
|| (ch === Chars.Colon && exports.OperatorType.PropertyDelimiter)
|| (ch === Chars.Dash && exports.OperatorType.ValueDelimiter)
|| void 0;
}
/**
* Check if given code is a hex value (/0-9a-f/)
*/
function isHex(code) {
return Scanner.isNumber(code) || Scanner.isAlpha(code, 65, 70); // A-F
}
function isKeyword(code) {
return Scanner.isAlphaNumericWord(code) || code === Chars.Dash;
}
function isBracket$1(code) {
return code === Chars.RoundBracketOpen || code === Chars.RoundBracketClose;
}
function isLiteral$1(code) {
return Scanner.isAlphaWord(code) || code === Chars.Percent || code === Chars.Slash;
}
/**
* Parses given color value from abbreviation into RGBA format
*/
function parseColor(value, alpha) {
let r = '0';
let g = '0';
let b = '0';
let a = Number(alpha != null && alpha !== '' ? alpha : 1);
if (value === 't') {
a = 0;
}
else {
switch (value.length) {
case 0:
break;
case 1:
r = g = b = value + value;
break;
case 2:
r = g = b = value;
break;
case 3:
r = value[0] + value[0];
g = value[1] + value[1];
b = value[2] + value[2];
break;
default:
value += value;
r = value.slice(0, 2);
g = value.slice(2, 4);
b = value.slice(4, 6);
}
}
return {
r: parseInt(r, 16),
g: parseInt(g, 16),
b: parseInt(b, 16),
a
};
}
/**
* Check if scanner reader must consume dash after given token.
* Used in cases where user must explicitly separate numeric values
*/
function shouldConsumeDashAfter(token) {
return token.type === 'ColorValue' || (token.type === 'NumberValue' && !token.unit);
}
/**
* Merges last adjacent tokens into a single literal.
* This function is used to overcome edge case when function name was parsed
* as a list of separate tokens. For example, a `scale3d()` value will be
* parsed as literal and number tokens (`scale` and `3d`) which is a perfectly
* valid abbreviation but undesired result. This function will detect last adjacent
* literal and number values and combine them into single literal
*/
function mergeTokens(scanner, tokens) {
let start = 0;
let end = 0;
while (tokens.length) {
const token = last(tokens);
if (token.type === 'Literal' || token.type === 'NumberValue') {
start = token.start;
if (!end) {
end = token.end;
}
tokens.pop();
}
else {
break;
}
}
if (start !== end) {
tokens.push(createLiteral(scanner, start, end));
}
}
function last(arr) {
return arr[arr.length - 1];
}
function tokenScanner(tokens) {
return {
tokens,
start: 0,
pos: 0,
size: tokens.length
};
}
function peek(scanner) {
return scanner.tokens[scanner.pos];
}
function readable(scanner) {
return scanner.pos < scanner.size;
}
function consume(scanner, test) {
if (test(peek(scanner))) {
scanner.pos++;
return true;
}
return false;
}
function error(scanner, message, token = peek(scanner)) {
if (token && token.start != null) {
message += ` at ${token.start}`;
}
const err = new Error(message);
err['pos'] = token && token.start;
return err;
}
function parser(tokens, options = {}) {
const scanner = tokenScanner(tokens);
const result = [];
let property;
while (readable(scanner)) {
if (property = consumeProperty(scanner, options)) {
result.push(property);
}
else if (!consume(scanner, isSiblingOperator)) {
throw error(scanner, 'Unexpected token');
}
}
return result;
}
/**
* Consumes single CSS property
*/
function consumeProperty(scanner, options) {
let name;
let important = false;
let valueFragment;
const value = [];
const token = peek(scanner);
const valueMode = !!options.value;
if (!valueMode && isLiteral(token) && !isFunctionStart(scanner)) {
scanner.pos++;
name = token.value;
// Consume any following value delimiter after property name
consume(scanner, isValueDelimiter);
}
// Skip whitespace right after property name, if any
if (valueMode) {
consume(scanner, isWhiteSpace);
}
while (readable(scanner)) {
if (consume(scanner, isImportant)) {
important = true;
}
else if (valueFragment = consumeValue(scanner, valueMode)) {
value.push(valueFragment);
}
else if (!consume(scanner, isFragmentDelimiter)) {
break;
}
}
if (name || value.length || important) {
return { name, value, important };
}
}
/**
* Consumes single value fragment, e.g. all value tokens before comma
*/
function consumeValue(scanner, inArgument) {
const result = [];
let token;
let args;
while (readable(scanner)) {
token = peek(scanner);
if (isValue(token)) {
scanner.pos++;
if (isLiteral(token) && (args = consumeArguments(scanner))) {
result.push({
type: 'FunctionCall',
name: token.value,
arguments: args
});
}
else {
result.push(token);
}
}
else if (isValueDelimiter(token) || (inArgument && isWhiteSpace(token))) {
scanner.pos++;
}
else {
break;
}
}
return result.length
? { type: 'CSSValue', value: result }
: void 0;
}
function consumeArguments(scanner) {
const start = scanner.pos;
if (consume(scanner, isOpenBracket)) {
const args = [];
let value;
while (readable(scanner) && !consume(scanner, isCloseBracket)) {
if (value = consumeValue(scanner, true)) {
args.push(value);
}
else if (!consume(scanner, isWhiteSpace) && !consume(scanner, isArgumentDelimiter)) {
throw error(scanner, 'Unexpected token');
}
}
scanner.start = start;
return args;
}
}
function isLiteral(token) {
return token && token.type === 'Literal';
}
function isBracket(token, open) {
return token && token.type === 'Bracket' && (open == null || token.open === open);
}
function isOpenBracket(token) {
return isBracket(token, true);
}
function isCloseBracket(token) {
return isBracket(token, false);
}
function isWhiteSpace(token) {
return token && token.type === 'WhiteSpace';
}
function isOperator(token, operator) {
return token && token.type === 'Operator' && (!operator || token.operator === operator);
}
function isSiblingOperator(token) {
return isOperator(token, exports.OperatorType.Sibling);
}
function isArgumentDelimiter(token) {
return isOperator(token, exports.OperatorType.ArgumentDelimiter);
}
function isFragmentDelimiter(token) {
return isArgumentDelimiter(token);
}
function isImportant(token) {
return isOperator(token, exports.OperatorType.Important);
}
function isValue(token) {
return token.type === 'StringValue'
|| token.type === 'ColorValue'
|| token.type === 'NumberValue'
|| token.type === 'Literal'
|| token.type === 'Field'
|| token.type === 'CustomProperty';
}
function isValueDelimiter(token) {
return isOperator(token, exports.OperatorType.PropertyDelimiter)
|| isOperator(token, exports.OperatorType.ValueDelimiter);
}
function isFunctionStart(scanner) {
const t1 = scanner.tokens[scanner.pos];
const t2 = scanner.tokens[scanner.pos + 1];
return t1 && t2 && isLiteral(t1) && t2.type === 'Bracket';
}
/**
* Parses given abbreviation into property set
*/
function parse(abbr, options) {
try {
const tokens = typeof abbr === 'string' ? tokenize(abbr, options && options.value) : abbr;
return parser(tokens, options);
}
catch (err) {
if (err instanceof Scanner.ScannerError && typeof abbr === 'string') {
err.message += `\n${abbr}\n${'-'.repeat(err.pos)}^`;
}
throw err;
}
}
exports.default = parse;
exports.getToken = getToken;
exports.parser = parser;
exports.tokenize = tokenize;
//# sourceMappingURL=index.cjs.map

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +0,0 @@
import tokenize, { getToken, type AllTokens } from './tokenizer/index.js';
import parser, { type CSSProperty, type ParseOptions } from './parser/index.js';
export { tokenize, getToken, parser };
export * from './tokenizer/tokens.js';
export type { CSSProperty, CSSValue, ParseOptions, FunctionCall, Value } from './parser/index.js';
export type CSSAbbreviation = CSSProperty[];
/**
* Parses given abbreviation into property set
*/
export default function parse(abbr: string | AllTokens[], options?: ParseOptions): CSSAbbreviation;

View File

@@ -1,680 +0,0 @@
import Scanner, { isNumber, isAlpha, isAlphaWord, isQuote, isSpace, isAlphaNumericWord, ScannerError } from '@emmetio/scanner';
var OperatorType;
(function (OperatorType) {
OperatorType["Sibling"] = "+";
OperatorType["Important"] = "!";
OperatorType["ArgumentDelimiter"] = ",";
OperatorType["ValueDelimiter"] = "-";
OperatorType["PropertyDelimiter"] = ":";
})(OperatorType || (OperatorType = {}));
var Chars;
(function (Chars) {
/** `#` character */
Chars[Chars["Hash"] = 35] = "Hash";
/** `$` character */
Chars[Chars["Dollar"] = 36] = "Dollar";
/** `-` character */
Chars[Chars["Dash"] = 45] = "Dash";
/** `.` character */
Chars[Chars["Dot"] = 46] = "Dot";
/** `:` character */
Chars[Chars["Colon"] = 58] = "Colon";
/** `,` character */
Chars[Chars["Comma"] = 44] = "Comma";
/** `!` character */
Chars[Chars["Excl"] = 33] = "Excl";
/** `@` character */
Chars[Chars["At"] = 64] = "At";
/** `%` character */
Chars[Chars["Percent"] = 37] = "Percent";
/** `_` character */
Chars[Chars["Underscore"] = 95] = "Underscore";
/** `(` character */
Chars[Chars["RoundBracketOpen"] = 40] = "RoundBracketOpen";
/** `)` character */
Chars[Chars["RoundBracketClose"] = 41] = "RoundBracketClose";
/** `{` character */
Chars[Chars["CurlyBracketOpen"] = 123] = "CurlyBracketOpen";
/** `}` character */
Chars[Chars["CurlyBracketClose"] = 125] = "CurlyBracketClose";
/** `+` character */
Chars[Chars["Sibling"] = 43] = "Sibling";
/** `'` character */
Chars[Chars["SingleQuote"] = 39] = "SingleQuote";
/** `"` character */
Chars[Chars["DoubleQuote"] = 34] = "DoubleQuote";
/** `t` character */
Chars[Chars["Transparent"] = 116] = "Transparent";
/** `/` character */
Chars[Chars["Slash"] = 47] = "Slash";
})(Chars || (Chars = {}));
function tokenize(abbr, isValue) {
let brackets = 0;
let token;
const scanner = new Scanner(abbr);
const tokens = [];
while (!scanner.eof()) {
token = getToken(scanner, brackets === 0 && !isValue);
if (!token) {
throw scanner.error('Unexpected character');
}
if (token.type === 'Bracket') {
if (!brackets && token.open) {
mergeTokens(scanner, tokens);
}
brackets += token.open ? 1 : -1;
if (brackets < 0) {
throw scanner.error('Unexpected bracket', token.start);
}
}
tokens.push(token);
// Forcibly consume next operator after unit-less numeric value or color:
// next dash `-` must be used as value delimiter
if (shouldConsumeDashAfter(token) && (token = operator(scanner))) {
tokens.push(token);
}
}
return tokens;
}
/**
* Returns next token from given scanner, if possible
*/
function getToken(scanner, short) {
return field(scanner)
|| customProperty(scanner)
|| numberValue(scanner)
|| colorValue(scanner)
|| stringValue(scanner)
|| bracket(scanner)
|| operator(scanner)
|| whiteSpace(scanner)
|| literal(scanner, short);
}
function field(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars.Dollar) && scanner.eat(Chars.CurlyBracketOpen)) {
scanner.start = scanner.pos;
let index;
let name = '';
if (scanner.eatWhile(isNumber)) {
// Its a field
index = Number(scanner.current());
name = scanner.eat(Chars.Colon) ? consumePlaceholder(scanner) : '';
}
else if (isAlpha(scanner.peek())) {
// Its a variable
name = consumePlaceholder(scanner);
}
if (scanner.eat(Chars.CurlyBracketClose)) {
return {
type: 'Field',
index, name,
start,
end: scanner.pos
};
}
throw scanner.error('Expecting }');
}
// If we reached here then theres no valid field here, revert
// back to starting position
scanner.pos = start;
}
/**
* Consumes a placeholder: value right after `:` in field. Could be empty
*/
function consumePlaceholder(stream) {
const stack = [];
stream.start = stream.pos;
while (!stream.eof()) {
if (stream.eat(Chars.CurlyBracketOpen)) {
stack.push(stream.pos);
}
else if (stream.eat(Chars.CurlyBracketClose)) {
if (!stack.length) {
stream.pos--;
break;
}
stack.pop();
}
else {
stream.pos++;
}
}
if (stack.length) {
stream.pos = stack.pop();
throw stream.error(`Expecting }`);
}
return stream.current();
}
/**
* Consumes literal from given scanner
* @param short Use short notation for consuming value.
* The difference between “short” and “full” notation is that first one uses
* alpha characters only and used for extracting keywords from abbreviation,
* while “full” notation also supports numbers and dashes
*/
function literal(scanner, short) {
const start = scanner.pos;
if (scanner.eat(isIdentPrefix)) {
// SCSS or LESS variable
// NB a bit dirty hack: if abbreviation starts with identifier prefix,
// consume alpha characters only to allow embedded variables
scanner.eatWhile(start ? isKeyword : isLiteral$1);
}
else if (scanner.eat(isAlphaWord)) {
scanner.eatWhile(short ? isLiteral$1 : isKeyword);
}
else {
// Allow dots only at the beginning of literal
scanner.eat(Chars.Dot);
scanner.eatWhile(isLiteral$1);
}
if (start !== scanner.pos) {
scanner.start = start;
return createLiteral(scanner, scanner.start = start);
}
}
function createLiteral(scanner, start = scanner.start, end = scanner.pos) {
return {
type: 'Literal',
value: scanner.substring(start, end),
start,
end
};
}
/**
* Consumes numeric CSS value (number with optional unit) from current stream,
* if possible
*/
function numberValue(scanner) {
const start = scanner.pos;
if (consumeNumber(scanner)) {
scanner.start = start;
const rawValue = scanner.current();
// eat unit, which can be a % or alpha word
scanner.start = scanner.pos;
scanner.eat(Chars.Percent) || scanner.eatWhile(isAlphaWord);
return {
type: 'NumberValue',
value: Number(rawValue),
rawValue,
unit: scanner.current(),
start,
end: scanner.pos
};
}
}
/**
* Consumes quoted string value from given scanner
*/
function stringValue(scanner) {
const ch = scanner.peek();
const start = scanner.pos;
let finished = false;
if (isQuote(ch)) {
scanner.pos++;
while (!scanner.eof()) {
// Do not throw error on malformed string
if (scanner.eat(ch)) {
finished = true;
break;
}
else {
scanner.pos++;
}
}
scanner.start = start;
return {
type: 'StringValue',
value: scanner.substring(start + 1, scanner.pos - (finished ? 1 : 0)),
quote: ch === Chars.SingleQuote ? 'single' : 'double',
start,
end: scanner.pos
};
}
}
/**
* Consumes a color token from given string
*/
function colorValue(scanner) {
// supported color variations:
// #abc → #aabbccc
// #0 → #000000
// #fff.5 → rgba(255, 255, 255, 0.5)
// #t → transparent
const start = scanner.pos;
if (scanner.eat(Chars.Hash)) {
const valueStart = scanner.pos;
let color = '';
let alpha = '';
if (scanner.eatWhile(isHex)) {
color = scanner.substring(valueStart, scanner.pos);
alpha = colorAlpha(scanner);
}
else if (scanner.eat(Chars.Transparent)) {
color = '0';
alpha = colorAlpha(scanner) || '0';
}
else {
alpha = colorAlpha(scanner);
}
if (color || alpha || scanner.eof()) {
const { r, g, b, a } = parseColor(color, alpha);
return {
type: 'ColorValue',
r, g, b, a,
raw: scanner.substring(start + 1, scanner.pos),
start,
end: scanner.pos
};
}
else {
// Consumed # but no actual value: invalid color value, treat it as literal
return createLiteral(scanner, start);
}
}
scanner.pos = start;
}
/**
* Consumes alpha value of color: `.1`
*/
function colorAlpha(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars.Dot)) {
scanner.start = start;
if (scanner.eatWhile(isNumber)) {
return scanner.current();
}
return '1';
}
return '';
}
/**
* Consumes white space characters as string literal from given scanner
*/
function whiteSpace(scanner) {
const start = scanner.pos;
if (scanner.eatWhile(isSpace)) {
return {
type: 'WhiteSpace',
start,
end: scanner.pos
};
}
}
/**
* Consumes custom CSS property: --foo-bar
*/
function customProperty(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars.Dash) && scanner.eat(Chars.Dash)) {
scanner.start = start;
scanner.eatWhile(isKeyword);
return {
type: 'CustomProperty',
value: scanner.current(),
start,
end: scanner.pos
};
}
scanner.pos = start;
}
/**
* Consumes bracket from given scanner
*/
function bracket(scanner) {
const ch = scanner.peek();
if (isBracket$1(ch)) {
return {
type: 'Bracket',
open: ch === Chars.RoundBracketOpen,
start: scanner.pos++,
end: scanner.pos
};
}
}
/**
* Consumes operator from given scanner
*/
function operator(scanner) {
const op = operatorType(scanner.peek());
if (op) {
return {
type: 'Operator',
operator: op,
start: scanner.pos++,
end: scanner.pos
};
}
}
/**
* Eats number value from given stream
* @return Returns `true` if number was consumed
*/
function consumeNumber(stream) {
const start = stream.pos;
stream.eat(Chars.Dash);
const afterNegative = stream.pos;
const hasDecimal = stream.eatWhile(isNumber);
const prevPos = stream.pos;
if (stream.eat(Chars.Dot)) {
// Its perfectly valid to have numbers like `1.`, which enforces
// value to float unit type
const hasFloat = stream.eatWhile(isNumber);
if (!hasDecimal && !hasFloat) {
// Lone dot
stream.pos = prevPos;
}
}
// Edge case: consumed dash only: not a number, bail-out
if (stream.pos === afterNegative) {
stream.pos = start;
}
return stream.pos !== start;
}
function isIdentPrefix(code) {
return code === Chars.At || code === Chars.Dollar;
}
/**
* If given character is an operator, returns its type
*/
function operatorType(ch) {
return (ch === Chars.Sibling && OperatorType.Sibling)
|| (ch === Chars.Excl && OperatorType.Important)
|| (ch === Chars.Comma && OperatorType.ArgumentDelimiter)
|| (ch === Chars.Colon && OperatorType.PropertyDelimiter)
|| (ch === Chars.Dash && OperatorType.ValueDelimiter)
|| void 0;
}
/**
* Check if given code is a hex value (/0-9a-f/)
*/
function isHex(code) {
return isNumber(code) || isAlpha(code, 65, 70); // A-F
}
function isKeyword(code) {
return isAlphaNumericWord(code) || code === Chars.Dash;
}
function isBracket$1(code) {
return code === Chars.RoundBracketOpen || code === Chars.RoundBracketClose;
}
function isLiteral$1(code) {
return isAlphaWord(code) || code === Chars.Percent || code === Chars.Slash;
}
/**
* Parses given color value from abbreviation into RGBA format
*/
function parseColor(value, alpha) {
let r = '0';
let g = '0';
let b = '0';
let a = Number(alpha != null && alpha !== '' ? alpha : 1);
if (value === 't') {
a = 0;
}
else {
switch (value.length) {
case 0:
break;
case 1:
r = g = b = value + value;
break;
case 2:
r = g = b = value;
break;
case 3:
r = value[0] + value[0];
g = value[1] + value[1];
b = value[2] + value[2];
break;
default:
value += value;
r = value.slice(0, 2);
g = value.slice(2, 4);
b = value.slice(4, 6);
}
}
return {
r: parseInt(r, 16),
g: parseInt(g, 16),
b: parseInt(b, 16),
a
};
}
/**
* Check if scanner reader must consume dash after given token.
* Used in cases where user must explicitly separate numeric values
*/
function shouldConsumeDashAfter(token) {
return token.type === 'ColorValue' || (token.type === 'NumberValue' && !token.unit);
}
/**
* Merges last adjacent tokens into a single literal.
* This function is used to overcome edge case when function name was parsed
* as a list of separate tokens. For example, a `scale3d()` value will be
* parsed as literal and number tokens (`scale` and `3d`) which is a perfectly
* valid abbreviation but undesired result. This function will detect last adjacent
* literal and number values and combine them into single literal
*/
function mergeTokens(scanner, tokens) {
let start = 0;
let end = 0;
while (tokens.length) {
const token = last(tokens);
if (token.type === 'Literal' || token.type === 'NumberValue') {
start = token.start;
if (!end) {
end = token.end;
}
tokens.pop();
}
else {
break;
}
}
if (start !== end) {
tokens.push(createLiteral(scanner, start, end));
}
}
function last(arr) {
return arr[arr.length - 1];
}
function tokenScanner(tokens) {
return {
tokens,
start: 0,
pos: 0,
size: tokens.length
};
}
function peek(scanner) {
return scanner.tokens[scanner.pos];
}
function readable(scanner) {
return scanner.pos < scanner.size;
}
function consume(scanner, test) {
if (test(peek(scanner))) {
scanner.pos++;
return true;
}
return false;
}
function error(scanner, message, token = peek(scanner)) {
if (token && token.start != null) {
message += ` at ${token.start}`;
}
const err = new Error(message);
err['pos'] = token && token.start;
return err;
}
function parser(tokens, options = {}) {
const scanner = tokenScanner(tokens);
const result = [];
let property;
while (readable(scanner)) {
if (property = consumeProperty(scanner, options)) {
result.push(property);
}
else if (!consume(scanner, isSiblingOperator)) {
throw error(scanner, 'Unexpected token');
}
}
return result;
}
/**
* Consumes single CSS property
*/
function consumeProperty(scanner, options) {
let name;
let important = false;
let valueFragment;
const value = [];
const token = peek(scanner);
const valueMode = !!options.value;
if (!valueMode && isLiteral(token) && !isFunctionStart(scanner)) {
scanner.pos++;
name = token.value;
// Consume any following value delimiter after property name
consume(scanner, isValueDelimiter);
}
// Skip whitespace right after property name, if any
if (valueMode) {
consume(scanner, isWhiteSpace);
}
while (readable(scanner)) {
if (consume(scanner, isImportant)) {
important = true;
}
else if (valueFragment = consumeValue(scanner, valueMode)) {
value.push(valueFragment);
}
else if (!consume(scanner, isFragmentDelimiter)) {
break;
}
}
if (name || value.length || important) {
return { name, value, important };
}
}
/**
* Consumes single value fragment, e.g. all value tokens before comma
*/
function consumeValue(scanner, inArgument) {
const result = [];
let token;
let args;
while (readable(scanner)) {
token = peek(scanner);
if (isValue(token)) {
scanner.pos++;
if (isLiteral(token) && (args = consumeArguments(scanner))) {
result.push({
type: 'FunctionCall',
name: token.value,
arguments: args
});
}
else {
result.push(token);
}
}
else if (isValueDelimiter(token) || (inArgument && isWhiteSpace(token))) {
scanner.pos++;
}
else {
break;
}
}
return result.length
? { type: 'CSSValue', value: result }
: void 0;
}
function consumeArguments(scanner) {
const start = scanner.pos;
if (consume(scanner, isOpenBracket)) {
const args = [];
let value;
while (readable(scanner) && !consume(scanner, isCloseBracket)) {
if (value = consumeValue(scanner, true)) {
args.push(value);
}
else if (!consume(scanner, isWhiteSpace) && !consume(scanner, isArgumentDelimiter)) {
throw error(scanner, 'Unexpected token');
}
}
scanner.start = start;
return args;
}
}
function isLiteral(token) {
return token && token.type === 'Literal';
}
function isBracket(token, open) {
return token && token.type === 'Bracket' && (open == null || token.open === open);
}
function isOpenBracket(token) {
return isBracket(token, true);
}
function isCloseBracket(token) {
return isBracket(token, false);
}
function isWhiteSpace(token) {
return token && token.type === 'WhiteSpace';
}
function isOperator(token, operator) {
return token && token.type === 'Operator' && (!operator || token.operator === operator);
}
function isSiblingOperator(token) {
return isOperator(token, OperatorType.Sibling);
}
function isArgumentDelimiter(token) {
return isOperator(token, OperatorType.ArgumentDelimiter);
}
function isFragmentDelimiter(token) {
return isArgumentDelimiter(token);
}
function isImportant(token) {
return isOperator(token, OperatorType.Important);
}
function isValue(token) {
return token.type === 'StringValue'
|| token.type === 'ColorValue'
|| token.type === 'NumberValue'
|| token.type === 'Literal'
|| token.type === 'Field'
|| token.type === 'CustomProperty';
}
function isValueDelimiter(token) {
return isOperator(token, OperatorType.PropertyDelimiter)
|| isOperator(token, OperatorType.ValueDelimiter);
}
function isFunctionStart(scanner) {
const t1 = scanner.tokens[scanner.pos];
const t2 = scanner.tokens[scanner.pos + 1];
return t1 && t2 && isLiteral(t1) && t2.type === 'Bracket';
}
/**
* Parses given abbreviation into property set
*/
function parse(abbr, options) {
try {
const tokens = typeof abbr === 'string' ? tokenize(abbr, options && options.value) : abbr;
return parser(tokens, options);
}
catch (err) {
if (err instanceof ScannerError && typeof abbr === 'string') {
err.message += `\n${abbr}\n${'-'.repeat(err.pos)}^`;
}
throw err;
}
}
export { OperatorType, parse as default, getToken, parser, tokenize };
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,17 +0,0 @@
import type { AllTokens } from '../tokenizer/index.js';
export interface TokenScanner {
tokens: AllTokens[];
start: number;
pos: number;
size: number;
}
type TestFn = (token?: AllTokens) => boolean;
export default function tokenScanner(tokens: AllTokens[]): TokenScanner;
export declare function peek(scanner: TokenScanner): AllTokens | undefined;
export declare function next(scanner: TokenScanner): AllTokens | undefined;
export declare function slice(scanner: TokenScanner, from?: number, to?: number): AllTokens[];
export declare function readable(scanner: TokenScanner): boolean;
export declare function consume(scanner: TokenScanner, test: TestFn): boolean;
export declare function error(scanner: TokenScanner, message: string, token?: AllTokens | undefined): Error;
export declare function consumeWhile(scanner: TokenScanner, test: TestFn): boolean;
export {};

View File

@@ -1,23 +0,0 @@
import type { StringValue, NumberValue, ColorValue, Literal, AllTokens, Field, CustomProperty } from '../tokenizer/tokens.js';
export type Value = StringValue | NumberValue | ColorValue | Literal | FunctionCall | Field | CustomProperty;
export interface FunctionCall {
type: 'FunctionCall';
name: string;
arguments: CSSValue[];
}
export interface CSSValue {
type: 'CSSValue';
value: Value[];
}
export interface CSSProperty {
name?: string;
value: CSSValue[];
important: boolean;
/** Snippet matched with current property */
snippet?: any;
}
export interface ParseOptions {
/** Consumes given abbreviation tokens as value */
value?: boolean;
}
export default function parser(tokens: AllTokens[], options?: ParseOptions): CSSProperty[];

View File

@@ -1,8 +0,0 @@
import { default as Scanner } from '@emmetio/scanner';
import type { AllTokens, Literal, NumberValue, ColorValue, WhiteSpace, Operator, Bracket, StringValue, Field, CustomProperty } from './tokens.js';
export * from './tokens.js';
export default function tokenize(abbr: string, isValue?: boolean): AllTokens[];
/**
* Returns next token from given scanner, if possible
*/
export declare function getToken(scanner: Scanner, short?: boolean): Bracket | Literal | Operator | WhiteSpace | ColorValue | NumberValue | StringValue | CustomProperty | Field | undefined;

View File

@@ -1,58 +0,0 @@
export type AllTokens = Bracket | Literal | Operator | WhiteSpace | ColorValue | NumberValue | StringValue | CustomProperty | Field;
export declare const enum OperatorType {
Sibling = "+",
Important = "!",
ArgumentDelimiter = ",",
ValueDelimiter = "-",
PropertyDelimiter = ":"
}
export interface Token {
type: string;
/** Location of token start in source */
start?: number;
/** Location of token end in source */
end?: number;
}
export interface Operator extends Token {
type: 'Operator';
operator: OperatorType;
}
export interface Bracket extends Token {
type: 'Bracket';
open: boolean;
}
export interface Literal extends Token {
type: 'Literal';
value: string;
}
export interface CustomProperty extends Token {
type: 'CustomProperty';
value: string;
}
export interface NumberValue extends Token {
type: 'NumberValue';
value: number;
unit: string;
rawValue: string;
}
export interface ColorValue extends Token {
type: 'ColorValue';
r: number;
g: number;
b: number;
a: number;
raw: string;
}
export interface StringValue extends Token {
type: 'StringValue';
value: string;
quote: 'single' | 'double';
}
export interface WhiteSpace extends Token {
type: 'WhiteSpace';
}
export interface Field extends Token {
type: 'Field';
index?: number;
name: string;
}

View File

@@ -1,40 +0,0 @@
export declare const enum Chars {
/** `#` character */
Hash = 35,
/** `$` character */
Dollar = 36,
/** `-` character */
Dash = 45,
/** `.` character */
Dot = 46,
/** `:` character */
Colon = 58,
/** `,` character */
Comma = 44,
/** `!` character */
Excl = 33,
/** `@` character */
At = 64,
/** `%` character */
Percent = 37,
/** `_` character */
Underscore = 95,
/** `(` character */
RoundBracketOpen = 40,
/** `)` character */
RoundBracketClose = 41,
/** `{` character */
CurlyBracketOpen = 123,
/** `}` character */
CurlyBracketClose = 125,
/** `+` character */
Sibling = 43,
/** `'` character */
SingleQuote = 39,
/** `"` character */
DoubleQuote = 34,
/** `t` character */
Transparent = 116,
/** `/` character */
Slash = 47
}

View File

@@ -1,52 +0,0 @@
{
"name": "@emmetio/css-abbreviation",
"version": "2.1.8",
"description": "Parses Emmet CSS abbreviation into AST tree",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"type": "module",
"exports": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"scripts": {
"test": "mocha",
"build": "rollup -c",
"watch": "rollup -wc",
"clean": "rimraf ./dist",
"prepublishOnly": "npm run clean && npm run build && npm test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/emmetio/emmet.git"
},
"keywords": [],
"author": "Sergey Chikuyonok <serge.che@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/emmetio/emmet/issues"
},
"homepage": "https://github.com/emmetio/emmet#readme",
"dependencies": {
"@emmetio/scanner": "^1.0.4"
},
"devDependencies": {
"@rollup/plugin-typescript": "^10.0.1",
"@types/mocha": "^10.0.1",
"@types/node": "^18.11.18",
"mocha": "^10.2.0",
"rimraf": "^5.0.0",
"rollup": "^3.9.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
"directories": {
"test": "test"
},
"mocha": {
"loader": "ts-node/esm",
"spec": "./test/*.ts"
},
"gitHead": "fce2127ece65adbb293a40aa0577e4558658c559"
}

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 Emmet.io
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,2 +0,0 @@
# css-parser
CSS/LESS/SCSS fast and minimalistic parser

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,887 +0,0 @@
(function (exports) {
'use strict';
/**
* A streaming, character code-based string reader
*/
class StreamReader {
constructor(string, start, end) {
if (end == null && typeof string === 'string') {
end = string.length;
}
this.string = string;
this.pos = this.start = start || 0;
this.end = end;
}
/**
* Returns true only if the stream is at the end of the file.
* @returns {Boolean}
*/
eof() {
return this.pos >= this.end;
}
/**
* Creates a new stream instance which is limited to given `start` and `end`
* range. E.g. its `eof()` method will look at `end` property, not actual
* stream end
* @param {Point} start
* @param {Point} end
* @return {StreamReader}
*/
limit(start, end) {
return new this.constructor(this.string, start, end);
}
/**
* Returns the next character code in the stream without advancing it.
* Will return NaN at the end of the file.
* @returns {Number}
*/
peek() {
return this.string.charCodeAt(this.pos);
}
/**
* Returns the next character in the stream and advances it.
* Also returns <code>undefined</code> when no more characters are available.
* @returns {Number}
*/
next() {
if (this.pos < this.string.length) {
return this.string.charCodeAt(this.pos++);
}
}
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
* the given argument, it is consumed and returned.
* Otherwise, `false` is returned.
* @param {Number|Function} match
* @returns {Boolean}
*/
eat(match) {
const ch = this.peek();
const ok = typeof match === 'function' ? match(ch) : ch === match;
if (ok) {
this.next();
}
return ok;
}
/**
* Repeatedly calls <code>eat</code> with the given argument, until it
* fails. Returns <code>true</code> if any characters were eaten.
* @param {Object} match
* @returns {Boolean}
*/
eatWhile(match) {
const start = this.pos;
while (!this.eof() && this.eat(match)) {}
return this.pos !== start;
}
/**
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
* @param {Number} n
*/
backUp(n) {
this.pos -= (n || 1);
}
/**
* Get the string between the start of the current token and the
* current stream position.
* @returns {String}
*/
current() {
return this.substring(this.start, this.pos);
}
/**
* Returns substring for given range
* @param {Number} start
* @param {Number} [end]
* @return {String}
*/
substring(start, end) {
return this.string.slice(start, end);
}
/**
* Creates error object with current stream state
* @param {String} message
* @return {Error}
*/
error(message) {
const err = new Error(`${message} at char ${this.pos + 1}`);
err.originalMessage = message;
err.pos = this.pos;
err.string = this.string;
return err;
}
}
class Container {
constructor() {
this.children = [];
this.parent = null;
}
get firstChild() {
return this.children[0];
}
get nextSibling() {
const ix = this.index();
return ix !== -1 ? this.parent.children[ix + 1] : null;
}
get previousSibling() {
const ix = this.index();
return ix !== -1 ? this.parent.children[ix - 1] : null;
}
/**
* Returns current elements index in parent list of child nodes
* @return {Number}
*/
index() {
return this.parent ? this.parent.children.indexOf(this) : -1;
}
/**
* Adds given node as a child
* @param {Node} node
* @return {Node} Current node
*/
add(node) {
if (node) {
node.remove();
this.children.push(node);
node.parent = this;
}
return this;
}
/**
* Removes current node from its parent
* @return {Node} Current node
*/
remove() {
if (this.parent) {
const ix = this.index();
if (ix !== -1) {
this.parent.children.splice(ix, 1);
this.parent = null;
}
}
return this;
}
}
class Stylesheet extends Container {
constructor() {
super();
this.type = 'stylesheet';
}
/**
* Returns nodes start position in stream
* @return {*}
*/
get start() {
const node = this.firstChild;
return node && node.start;
}
/**
* Returns nodes end position in stream
* @return {*}
*/
get end() {
const node = this.children[this.children.length - 1];
return node && node.end;
}
}
class Token {
/**
* @param {StreamReader} stream
* @param {String} type Token type
* @param {Object} [start] Tokens start position in `stream`
* @param {Object} [end] Tokens end position in `stream`
*/
constructor(stream, type, start, end) {
this.stream = stream;
this.start = start != null ? start : stream.start;
this.end = end != null ? end : stream.pos;
this.type = type;
this._props = null;
this._value = null;
this._items = null;
}
get size() {
return this._items ? this._items.length : 0;
}
add(item) {
if (item) {
if (!this._items) {
this._items = [item];
} else {
this._items.push(item);
}
}
return this;
}
remove(item) {
if (this._items) {
const ix = this._items.indexOf(item);
if (ix !== -1 ) {
this._items.splice(ix, 1);
}
}
return this;
}
item(i) {
return this._items && this._items[i];
}
property(name, value) {
if (typeof value !== 'undefined') {
// set property value
if (!this._props) {
this._props = {};
}
this._props[name] = value;
}
return this._props && this._props[name];
}
/**
* Returns token textual representation
* @return {String}
*/
toString() {
return `${this.valueOf()} [${this.start}, ${this.end}]`;
}
valueOf() {
if (this._value === null) {
this._value = this.stream.substring(this.start, this.end);
}
return this._value;
}
}
/**
* Removes tokens that matches given criteria from start and end of given list
* @param {Token[]} tokens
* @param {Function} test
* @return {Token[]}
*/
/**
* Trims formatting tokens (whitespace and comments) from the beginning and end
* of given token list
* @param {Token[]} tokens
* @return {Token[]}
*/
/**
* Check if given token is a formatting one (whitespace or comment)
* @param {Token} token
* @return {Boolean}
*/
/**
* Consumes string char-by-char from given stream
* @param {StreamReader} stream
* @param {String} string
* @return {Boolean} Returns `true` if string was completely consumed
*/
function eatString(stream, string) {
const start = stream.pos;
for (let i = 0, il = string.length; i < il; i++) {
if (!stream.eat(string.charCodeAt(i))) {
stream.pos = start;
return false;
}
}
return true;
}
function consumeWhile(stream, match) {
const start = stream.pos;
if (stream.eatWhile(match)) {
stream.start = start;
return true;
}
return false;
}
/**
* Returns type of given token
* @param {Token} token
* @return {String}
*/
function last(arr) {
return arr[arr.length - 1];
}
function createRule(stream, tokens, content) {
if (!tokens.length) {
return null;
}
let ix = 0;
const name = tokens[ix++];
if (name.type === 'at-keyword') {
let expression;
if (ix < tokens.length) {
expression = tokens[ix++];
expression.type = 'expression';
expression.end = last(tokens).end;
} else {
expression = new Token(stream, 'expression', name.end, name.end);
}
return new AtRule(stream, name, expression, content);
} else {
name.end = last(tokens).end;
}
return new Rule(stream, name, content);
}
/**
* Represents CSS rule
* @type {Node}
*/
class Rule extends Container {
/**
* @param {StreamReader} stream
* @param {Token} name Rules name token
* @param {Token} content Rules content token
*/
constructor(stream, name, content) {
super();
this.type = 'rule';
this.stream = stream;
this.nameToken = name;
this.contentToken = content;
}
/**
* Returns node name
* @return {String}
*/
get name() {
return valueOf(this.nameToken);
}
/**
* Returns nodes start position in stream
* @return {*}
*/
get start() {
return this.nameToken && this.nameToken.start;
}
/**
* Returns nodes end position in stream
* @return {*}
*/
get end() {
const token = this.contentToken || this.nameToken;
return token && token.end;
}
}
class AtRule extends Rule {
constructor(stream, name, expression, content) {
super(stream, name, content);
this.type = 'at-rule';
this.expressionToken = expression;
}
get expressions() {
return valueOf(this.expressionToken);
}
}
function valueOf(token) {
return token && token.valueOf();
}
function createProperty(stream, tokens, terminator) {
// NB in LESS, fragmented properties without value like `.foo.bar;` must be
// treated like mixin call
if (!tokens.length) {
return null;
}
let separator, value, ix = 0;
const name = tokens[ix++];
if (ix < tokens.length) {
value = tokens[ix++];
value.type = 'value';
value.end = last(tokens).end;
}
if (name && value) {
separator = new Token(stream, 'separator', name.end, value.start);
}
return new Property(
stream,
name,
value,
separator,
terminator
);
}
class Property {
constructor(stream, name, value, separator, terminator) {
this.type = 'property';
this.stream = stream;
this.nameToken = name;
this.valueToken = value;
this.separatorToken = separator;
this.terminatorToken = terminator;
}
get name() {
return valueOf$1(this.nameToken);
}
get value() {
return valueOf$1(this.valueToken);
}
get separator() {
return valueOf$1(this.separatorToken);
}
get terminator() {
return valueOf$1(this.terminatorToken);
}
get start() {
const token = this.nameToken || this.separatorToken || this.valueToken
|| this.terminatorToken;
return token && token.start;
}
get end() {
const token = this.terminatorToken || this.valueToken
|| this.separatorToken || this.nameToken;
return token && token.end;
}
}
function valueOf$1(token) {
return token && token.valueOf();
}
/**
* Methods for consuming quoted values
*/
const SINGLE_QUOTE = 39; // '
const DOUBLE_QUOTE = 34; // "
function isQuote(code) {
return code === SINGLE_QUOTE || code === DOUBLE_QUOTE;
}
/**
* Check if given code is a number
* @param {Number} code
* @return {Boolean}
*/
function isNumber(code) {
return code > 47 && code < 58;
}
/**
* Check if given character code is alpha code (letter through A to Z)
* @param {Number} code
* @param {Number} [from]
* @param {Number} [to]
* @return {Boolean}
*/
function isAlpha(code, from, to) {
from = from || 65; // A
to = to || 90; // Z
code &= ~32; // quick hack to convert any char code to uppercase char code
return code >= from && code <= to;
}
/**
* Check if given character code is alpha-numeric (letter through A to Z or number)
* @param {Number} code
* @return {Boolean}
*/
function isWhiteSpace(code) {
return code === 32 /* space */
|| code === 9 /* tab */
|| code === 160; /* non-breaking space */
}
/**
* Check if given character code is a space
* @param {Number} code
* @return {Boolean}
*/
function isSpace(code) {
return isWhiteSpace(code)
|| code === 10 /* LF */
|| code === 13; /* CR */
}
const HYPHEN = 45;
const UNDERSCORE = 95;
function ident(stream) {
return eatIdent(stream) && new Token(stream, 'ident');
}
function eatIdent(stream) {
const start = stream.pos;
stream.eat(HYPHEN);
if (stream.eat(isIdentStart)) {
stream.eatWhile(isIdent);
stream.start = start;
return true;
}
stream.pos = start;
return false;
}
function isIdentStart(code) {
return code === UNDERSCORE || code === HYPHEN || isAlpha(code) || code >= 128;
}
function isIdent(code) {
return isNumber(code) || isIdentStart(code);
}
function prefixed(stream, tokenType, prefix, body, allowEmptyBody) {
const start = stream.pos;
if (stream.eat(prefix)) {
const bodyToken = body(stream, start);
if (bodyToken || allowEmptyBody) {
stream.start = start;
return new Token(stream, tokenType, start).add(bodyToken);
}
}
stream.pos = start;
}
const AT = 64; // @
/**
* Consumes at-keyword from given stream
*/
function atKeyword(stream) {
return prefixed(stream, 'at-keyword', AT, ident);
}
function eatString$1(stream, asToken) {
let ch = stream.peek(), pos;
if (isQuote(ch)) {
stream.start = stream.pos;
stream.next();
const quote = ch;
const valueStart = stream.pos;
while (!stream.eof()) {
pos = stream.pos;
if (stream.eat(quote) || stream.eat(isNewline)) {
// found end of string or newline without preceding '\',
// which is not allowed (dont throw error, for now)
break;
} else if (stream.eat(92 /* \ */)) {
// backslash allows newline in string
stream.eat(isNewline);
}
stream.next();
}
// Either reached EOF or explicitly stopped at string end
// NB use extra `asToken` param to return boolean instead of token to reduce
// memory allocations and improve performance
if (asToken) {
const token = new Token(stream, 'string');
token.add(new Token(stream, 'unquoted', valueStart, pos));
token.property('quote', quote);
return token;
}
return true;
}
return false;
}
function isNewline(code) {
return code === 10 /* LF */ || code === 13 /* CR */;
}
const ASTERISK = 42;
const SLASH = 47;
/**
* Consumes comment from given stream: either multi-line or single-line
* @param {StreamReader} stream
* @return {CommentToken}
*/
function eatComment(stream) {
return eatSingleLineComment(stream) || eatMultiLineComment(stream);
}
function eatSingleLineComment(stream) {
const start = stream.pos;
if (stream.eat(SLASH) && stream.eat(SLASH)) {
// single-line comment, consume till the end of line
stream.start = start;
while (!stream.eof()) {
if (isLineBreak(stream.next())) {
break;
}
}
return true;
}
stream.pos = start;
return false;
}
function eatMultiLineComment(stream) {
const start = stream.pos;
if (stream.eat(SLASH) && stream.eat(ASTERISK)) {
while (!stream.eof()) {
if (stream.next() === ASTERISK && stream.eat(SLASH)) {
break;
}
}
stream.start = start;
return true;
}
stream.pos = start;
return false;
}
function isLineBreak(code) {
return code === 10 /* LF */ || code === 13 /* CR */;
}
function eatWhitespace(stream) {
return consumeWhile(stream, isSpace);
}
function eatUnquoted(stream) {
return consumeWhile(stream, isUnquoted);
}
function isUnquoted(code) {
return !isNaN(code) && !isQuote(code) && !isSpace(code)
&& code !== 40 /* ( */ && code !== 41 /* ) */ && code !== 92 /* \ */
&& !isNonPrintable(code);
}
function isNonPrintable(code) {
return (code >= 0 && code <= 8) || code === 11
|| (code >= 14 && code <= 31) || code === 127;
}
function eatUrl(stream) {
const start = stream.pos;
if (eatString(stream, 'url(')) {
eatWhitespace(stream);
eatString$1(stream) || eatUnquoted(stream);
eatWhitespace(stream);
stream.eat(41); // )
stream.start = start;
return true;
}
stream.pos = start;
return false;
}
const LBRACE = 40; // (
const RBRACE = 41; // )
const PROP_DELIMITER = 58; // :
const PROP_TERMINATOR = 59; // ;
const RULE_START = 123; // {
const RULE_END = 125; // }
function parseStylesheet(source) {
const stream = typeof source === 'string' ? new StreamReader(source) : source;
const root = new Stylesheet();
let ctx = root, child, accum, token;
let tokens = [];
const flush = () => {
if (accum) {
tokens.push(accum);
accum = null;
}
};
while (!stream.eof()) {
if (eatWhitespace(stream) || eatComment(stream)) {
continue;
}
stream.start = stream.pos;
if (stream.eatWhile(PROP_DELIMITER)) {
// Property delimiter can be either a real property delimiter or a
// part of pseudo-selector.
if (!tokens.length) {
if (accum) {
// No consumed tokens yet but pending token: most likely its
// a CSS property
flush();
} else {
// No consumend or accumulated token, seems like a start of
// pseudo-selector, e.g. `::slotted`
accum = new Token(stream, 'preparse');
}
}
// Skip delimiter if there are already consumend tokens: most likely
// its a part of pseudo-selector
} else if (stream.eat(RULE_END)) {
flush();
// Finalize context section
ctx.add(createProperty(stream, tokens));
if (ctx.type !== 'stylesheet') {
// In case of invalid stylesheet with redundant `}`,
// dont modify root section.
ctx.contentToken.end = stream.pos;
ctx = ctx.parent;
}
tokens.length = 0;
} else if (stream.eat(PROP_TERMINATOR)) {
flush();
ctx.add(createProperty(stream, tokens, new Token(stream, 'termintator')));
tokens.length = 0;
} else if (stream.eat(RULE_START)) {
flush();
child = createRule(stream, tokens, new Token(stream, 'body'));
ctx.add(child);
ctx = child;
tokens.length = 0;
} else if (token = atKeyword(stream)) {
// Explictly consume @-tokens since it defines how rule or property
// should be pre-parsed
flush();
tokens.push(token);
} else if (eatUrl(stream) || eatBraces(stream) || eatString$1(stream) || stream.next()) {
// NB explicitly consume `url()` token since it may contain
// an unquoted url like `http://example.com` which interferes
// with single-line comment
accum = accum || new Token(stream, 'preparse');
accum.end = stream.pos;
} else {
throw new Error(`Unexpected end-of-stream at ${stream.pos}`);
}
}
if (accum) {
tokens.push(accum);
}
// Finalize all the rest properties
ctx.add(createProperty(stream, tokens));
return root;
}
/**
* Consumes content inside round braces. Mostly used to skip `;` token inside
* expressions since in LESS it is also used to separate function arguments
* @param {StringReader} stream
* @return {Boolean}
*/
function eatBraces(stream) {
if (stream.eat(LBRACE)) {
let stack = 1;
while (!stream.eof()) {
if (stream.eat(RBRACE)) {
stack--;
if (!stack) {
break;
}
} else if (stream.eat(LBRACE)) {
stack++;
} else {
eatUrl(stream) || eatString$1(stream) || eatComment(stream) || stream.next();
}
}
return true;
}
return false;
}
exports['default'] = parseStylesheet;
}((this.cssParser = this.cssParser || {})));

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,36 +0,0 @@
{
"name": "@emmetio/css-parser",
"version": "0.4.0",
"description": "CSS/LESS/SCSS fast and minimalistic parser",
"main": "./dist/css-parser.cjs.js",
"module": "./dist/css-parser.es.js",
"dependencies": {
"@emmetio/stream-reader": "^2.2.0",
"@emmetio/stream-reader-utils": "^0.1.0"
},
"devDependencies": {
"babel-plugin-transform-es2015-modules-commonjs": "^6.24.1",
"babel-register": "^6.24.1",
"mocha": "^3.2.0",
"rollup": "^0.41.6",
"rollup-plugin-node-resolve": "^3.0.0",
"rollup-watch": "^3.2.2"
},
"scripts": {
"test": "mocha",
"build": "rollup -c",
"watch": "rollup -wc",
"prepublish": "npm run test && npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/emmetio/css-parser.git"
},
"keywords": [],
"author": "Sergey Chikuyonok <serge.che@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/emmetio/css-parser/issues"
},
"homepage": "https://github.com/emmetio/css-parser#readme"
}

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 Emmet.io
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,50 +0,0 @@
# Small and fast HTML matcher
Finds matching opening and closing tag pair for given location in HTML/XML source:
```js
import match from '@emmetio/html-matcher';
const content = '<div><a href="http://emmet.io">Example</a></div>';
// Find tag pair at character 35
const tag = match(content, 35);
console.log(tag.name); // Name of matched tag: "a"
console.log(tag.open); // Range of opening tag: [5, 31]
console.log(tag.end); // Range of closing tag: [38, 42]
// List of attributes found in opening tag
console.log(tag.attributes);
```
By default, matcher works in HTML, which means if it finds tag name which is known to be empty (for example, `<img>`) it will not search for its closing part. However, such behavior might be unexpected for XML syntaxes where all tags should be either self-closed or provide closing part. In this case, you should pass `xml: true` option to properly handle XML mode:
```js
import match from '@emmetio/html-matcher';
const content = '<div><img>Caption</img></div>';
const html = match(content, 8);
const xml = match(content, 8, { xml: true });
console.log(html.name); // "img"
console.log(html.open); // [5, 10]
console.log(html.close); // undefined
console.log(xml.name); // "img"
console.log(xml.open); // [5, 10]
console.log(xml.close); // [17, 23]
```
## Special tags
In HTML, some tags has special meaning. For example, a `<script>` tag: its contents should be completely ignored until we find closing `</script>` tag. But, if `<script>` tag contains unknown `type` attribute value, we should consider it as a regular tag. By default, matcher understands `script` and `style` tags as “special” but you can override them with `special` option:
```js
import match from '@emmetio/html-matcher';
// Treat `<foo-bar>` tag as ”special”: skip its content until `</foo-bar>`. Note that this option overwrites default value with `['script', 'style']` value
match('...', 10, { special: { 'foo-bar': null } });
```
The `special` option is an object where key is a tag name and value is an array of `type` attribute values which, if present in tag, will make it special. If array is not provided, all instances of tag with given name will be considered as special.

View File

@@ -1,29 +0,0 @@
import Scanner from '@emmetio/scanner';
export interface AttributeToken {
name: string;
value?: string;
nameStart: number;
nameEnd: number;
valueStart?: number;
valueEnd?: number;
}
/**
* Parses given string as list of HTML attributes.
* @param src A fragment to parse. If `name` argument is provided, it must be an
* opening tag (`<a foo="bar">`), otherwise it should be a fragment between element
* name and tag closing angle (`foo="bar"`)
* @param name Tag name
*/
export default function attributes(src: string, name?: string): AttributeToken[];
/**
* Consumes attribute name from given scanner context
*/
export declare function attributeName(scanner: Scanner): boolean;
/**
* Consumes attribute value
*/
export declare function attributeValue(scanner: Scanner): true | undefined;
/**
* Returns clean (unquoted) value of `name` attribute
*/
export declare function getAttributeValue(attrs: AttributeToken[], name: string): string | undefined;

View File

@@ -1,641 +0,0 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; }
var Scanner = require('@emmetio/scanner');
var Scanner__default = _interopDefault(Scanner);
const defaultOptions = {
xml: false,
allTokens: false,
special: {
style: null,
script: ['', 'text/javascript', 'application/x-javascript', 'javascript', 'typescript', 'ts', 'coffee', 'coffeescript']
},
empty: ['img', 'meta', 'link', 'br', 'base', 'hr', 'area', 'wbr', 'col', 'embed', 'input', 'param', 'source', 'track']
};
/** Options for `Scanner` utils */
const opt = { throws: false };
function createOptions(options = {}) {
return Object.assign(Object.assign({}, defaultOptions), options);
}
/**
* Converts given string into array of character codes
*/
function toCharCodes(str) {
return str.split('').map(ch => ch.charCodeAt(0));
}
/**
* Consumes array of character codes from given scanner
*/
function consumeArray(scanner, codes) {
const start = scanner.pos;
for (let i = 0; i < codes.length; i++) {
if (!scanner.eat(codes[i])) {
scanner.pos = start;
return false;
}
}
scanner.start = start;
return true;
}
/**
* Consumes section from given string which starts with `open` character codes
* and ends with `close` character codes
* @return Returns `true` if section was consumed
*/
function consumeSection(scanner, open, close, allowUnclosed) {
const start = scanner.pos;
if (consumeArray(scanner, open)) {
// consumed `<!--`, read next until we find ending part or reach the end of input
while (!scanner.eof()) {
if (consumeArray(scanner, close)) {
scanner.start = start;
return true;
}
scanner.pos++;
}
// unclosed section is allowed
if (allowUnclosed) {
scanner.start = start;
return true;
}
scanner.pos = start;
return false;
}
// unable to find section, revert to initial position
scanner.pos = start;
return false;
}
/**
* Check if given character can be used as a start of tag name or attribute
*/
function nameStartChar(ch) {
// Limited XML spec: https://www.w3.org/TR/xml/#NT-NameStartChar
return Scanner.isAlpha(ch) || ch === 58 /* Colon */ || ch === 95 /* Underscore */
|| (ch >= 0xC0 && ch <= 0xD6)
|| (ch >= 0xD8 && ch <= 0xF6)
|| (ch >= 0xF8 && ch <= 0x2FF)
|| (ch >= 0x370 && ch <= 0x37D)
|| (ch >= 0x37F && ch <= 0x1FFF);
}
/**
* Check if given character can be used in a tag or attribute name
*/
function nameChar(ch) {
// Limited XML spec: https://www.w3.org/TR/xml/#NT-NameChar
return nameStartChar(ch) || ch === 45 /* Dash */ || ch === 46 /* Dot */ || Scanner.isNumber(ch)
|| ch === 0xB7
|| (ch >= 0x0300 && ch <= 0x036F);
}
/**
* Consumes identifier from given scanner
*/
function ident(scanner) {
const start = scanner.pos;
if (scanner.eat(nameStartChar)) {
scanner.eatWhile(nameChar);
scanner.start = start;
return true;
}
return false;
}
/**
* Check if given code is tag terminator
*/
function isTerminator(code) {
return code === 62 /* RightAngle */ || code === 47 /* Slash */;
}
/**
* Check if given character code is valid unquoted value
*/
function isUnquoted(code) {
return !isNaN(code) && !Scanner.isQuote(code) && !Scanner.isSpace(code) && !isTerminator(code);
}
/**
* Consumes paired tokens (like `[` and `]`) with respect of nesting and embedded
* quoted values
* @return `true` if paired token was consumed
*/
function consumePaired(scanner) {
return Scanner.eatPair(scanner, 60 /* LeftAngle */, 62 /* RightAngle */, opt)
|| Scanner.eatPair(scanner, 40 /* LeftRound */, 41 /* RightRound */, opt)
|| Scanner.eatPair(scanner, 91 /* LeftSquare */, 93 /* RightSquare */, opt)
|| Scanner.eatPair(scanner, 123 /* LeftCurly */, 125 /* RightCurly */, opt);
}
/**
* Returns unquoted value of given string
*/
function getUnquotedValue(value) {
// Trim quotes
if (Scanner.isQuote(value.charCodeAt(0))) {
value = value.slice(1);
}
if (Scanner.isQuote(value.charCodeAt(value.length - 1))) {
value = value.slice(0, -1);
}
return value;
}
/**
* Parses given string as list of HTML attributes.
* @param src A fragment to parse. If `name` argument is provided, it must be an
* opening tag (`<a foo="bar">`), otherwise it should be a fragment between element
* name and tag closing angle (`foo="bar"`)
* @param name Tag name
*/
function attributes(src, name) {
const result = [];
let start = 0;
let end = src.length;
if (name) {
start = name.length + 1;
end -= src.slice(-2) === '/>' ? 2 : 1;
}
const scanner = new Scanner__default(src, start, end);
while (!scanner.eof()) {
scanner.eatWhile(Scanner.isSpace);
if (attributeName(scanner)) {
const token = {
name: scanner.current(),
nameStart: scanner.start,
nameEnd: scanner.pos
};
if (scanner.eat(61 /* Equals */) && attributeValue(scanner)) {
token.value = scanner.current();
token.valueStart = scanner.start;
token.valueEnd = scanner.pos;
}
result.push(token);
}
else {
// Do not break on invalid attributes: we are not validating parser
scanner.pos++;
}
}
return result;
}
/**
* Consumes attribute name from given scanner context
*/
function attributeName(scanner) {
const start = scanner.pos;
if (scanner.eat(42 /* Asterisk */) || scanner.eat(35 /* Hash */)) {
// Angular-style directives: `<section *ngIf="showSection">`, `<video #movieplayer ...>`
ident(scanner);
scanner.start = start;
return true;
}
// Attribute name could be a regular name or expression:
// React-style `<div {...props}>`
// Angular-style `<div [ng-for]>` or `<div *ng-for>`
return consumePaired(scanner) || ident(scanner);
}
/**
* Consumes attribute value
*/
function attributeValue(scanner) {
// Supported attribute values are quoted, React-like expressions (`{foo}`)
// or unquoted literals
return Scanner.eatQuoted(scanner, opt) || consumePaired(scanner) || unquoted(scanner);
}
/**
* Returns clean (unquoted) value of `name` attribute
*/
function getAttributeValue(attrs, name) {
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
if (attr.name === name) {
return attr.value && getUnquotedValue(attr.value);
}
}
}
/**
* Consumes unquoted value
*/
function unquoted(scanner) {
const start = scanner.pos;
if (scanner.eatWhile(isUnquoted)) {
scanner.start = start;
return true;
}
}
const cdataOpen = toCharCodes('<![CDATA[');
const cdataClose = toCharCodes(']]>');
const commentOpen = toCharCodes('<!--');
const commentClose = toCharCodes('-->');
const piStart = toCharCodes('<?');
const piEnd = toCharCodes('?>');
const erbStart = toCharCodes('<%');
const erbEnd = toCharCodes('%>');
/**
* Performs fast scan of given source code: for each tag found it invokes callback
* with tag name, its type (open, close, self-close) and range in original source.
* Unlike regular scanner, fast scanner doesnt provide info about attributes to
* reduce object allocations hence increase performance.
* If `callback` returns `false`, scanner stops parsing.
* @param special List of “special” HTML tags which should be ignored. Most likely
* its a "script" and "style" tags.
*/
function scan(source, callback, options) {
const scanner = new Scanner__default(source);
const special = options ? options.special : null;
const allTokens = options ? options.allTokens : false;
let type;
let name;
let nameStart;
let nameEnd;
let nameCodes;
let found = false;
let piName = null;
while (!scanner.eof()) {
const start = scanner.pos;
if (cdata(scanner)) {
if (allTokens && callback('#cdata', 4 /* CData */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (comment(scanner)) {
if (allTokens && callback('#comment', 6 /* Comment */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (erb(scanner)) {
if (allTokens && callback('#erb', 7 /* ERB */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (piName = processingInstruction(scanner)) {
if (allTokens && callback(piName, 5 /* ProcessingInstruction */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (scanner.eat(60 /* LeftAngle */)) {
// Maybe a tag name?
type = scanner.eat(47 /* Slash */) ? 2 /* Close */ : 1 /* Open */;
nameStart = scanner.pos;
if (ident(scanner)) {
// Consumed tag name
nameEnd = scanner.pos;
if (type !== 2 /* Close */) {
skipAttributes(scanner);
scanner.eatWhile(Scanner.isSpace);
if (scanner.eat(47 /* Slash */)) {
type = 3 /* SelfClose */;
}
}
if (scanner.eat(62 /* RightAngle */)) {
// Tag properly closed
name = scanner.substring(nameStart, nameEnd);
if (callback(name, type, start, scanner.pos) === false) {
break;
}
if (type === 1 /* Open */ && special && isSpecial(special, name, source, start, scanner.pos)) {
// Found opening tag of special element: we should skip
// scanner contents until we find closing tag
nameCodes = toCharCodes(name);
found = false;
while (!scanner.eof()) {
if (consumeClosing(scanner, nameCodes)) {
found = true;
break;
}
scanner.pos++;
}
if (found && callback(name, 2 /* Close */, scanner.start, scanner.pos) === false) {
break;
}
}
}
}
}
else {
scanner.pos++;
}
}
}
/**
* Skips attributes in current tag context
*/
function skipAttributes(scanner) {
while (!scanner.eof()) {
scanner.eatWhile(Scanner.isSpace);
if (attributeName(scanner)) {
if (scanner.eat(61 /* Equals */)) {
attributeValue(scanner);
}
}
else if (isTerminator(scanner.peek())) {
break;
}
else {
scanner.pos++;
}
}
}
/**
* Consumes closing tag with given name from scanner
*/
function consumeClosing(scanner, name) {
const start = scanner.pos;
if (scanner.eat(60 /* LeftAngle */) && scanner.eat(47 /* Slash */) && consumeArray(scanner, name) && scanner.eat(62 /* RightAngle */)) {
scanner.start = start;
return true;
}
scanner.pos = start;
return false;
}
/**
* Consumes CDATA from given scanner
*/
function cdata(scanner) {
return consumeSection(scanner, cdataOpen, cdataClose, true);
}
/**
* Consumes comments from given scanner
*/
function comment(scanner) {
return consumeSection(scanner, commentOpen, commentClose, true);
}
/**
* Consumes processing instruction from given scanner. If consumed, returns
* processing instruction name
*/
function processingInstruction(scanner) {
const start = scanner.pos;
if (consumeArray(scanner, piStart) && ident(scanner)) {
const name = scanner.current();
while (!scanner.eof()) {
if (consumeArray(scanner, piEnd)) {
break;
}
Scanner.eatQuoted(scanner) || scanner.pos++;
}
scanner.start = start;
return name;
}
scanner.pos = start;
return null;
}
/**
* Consumes ERB-style entity: `<% ... %>` or `<%= ... %>`
*/
function erb(scanner) {
const start = scanner.pos;
if (consumeArray(scanner, erbStart)) {
while (!scanner.eof()) {
if (consumeArray(scanner, erbEnd)) {
break;
}
Scanner.eatQuoted(scanner) || scanner.pos++;
}
scanner.start = start;
return true;
}
scanner.pos = start;
return false;
}
/**
* Check if given tag name should be considered as special
*/
function isSpecial(special, name, source, start, end) {
if (name in special) {
const typeValues = special[name];
if (!Array.isArray(typeValues)) {
return true;
}
const attrs = attributes(source.substring(start + name.length + 1, end - 1));
return typeValues.includes(getAttributeValue(attrs, 'type') || '');
}
return false;
}
/**
* Finds matched tag for given `pos` location in XML/HTML `source`
*/
function match(source, pos, opt) {
// Since we expect large input document, well use pooling technique
// for storing tag data to reduce memory pressure and improve performance
const pool = [];
const stack = [];
const options = createOptions(opt);
let result = null;
scan(source, (name, type, start, end) => {
if (type === 1 /* Open */ && isSelfClose(name, options)) {
// Found empty element in HTML mode, mark is as self-closing
type = 3 /* SelfClose */;
}
if (type === 1 /* Open */) {
// Allocate tag object from pool
stack.push(allocTag(pool, name, start, end));
}
else if (type === 3 /* SelfClose */) {
if (start < pos && pos < end) {
// Matched given self-closing tag
result = {
name,
attributes: getAttributes(source, start, end, name),
open: [start, end]
};
return false;
}
}
else {
const tag = last(stack);
if (tag && tag.name === name) {
// Matching closing tag found
if (tag.start < pos && pos < end) {
result = {
name,
attributes: getAttributes(source, tag.start, tag.end, name),
open: [tag.start, tag.end],
close: [start, end]
};
return false;
}
else if (stack.length) {
// Release tag object for further re-use
releaseTag(pool, stack.pop());
}
}
}
}, options);
stack.length = pool.length = 0;
return result;
}
/**
* Returns balanced tag model: a list of all XML/HTML tags that could possibly match
* given location when moving in outward direction
*/
function balancedOutward(source, pos, opt) {
const pool = [];
const stack = [];
const options = createOptions(opt);
const result = [];
scan(source, (name, type, start, end) => {
if (type === 2 /* Close */) {
const tag = last(stack);
if (tag && tag.name === name) { // XXX check for invalid tag names?
// Matching closing tag found, check if matched pair is a candidate
// for outward balancing
if (tag.start < pos && pos < end) {
result.push({
name,
open: [tag.start, tag.end],
close: [start, end]
});
}
// Release tag object for further re-use
releaseTag(pool, stack.pop());
}
}
else if (type === 3 /* SelfClose */ || isSelfClose(name, options)) {
if (start < pos && pos < end) {
// Matched self-closed tag
result.push({ name, open: [start, end] });
}
}
else {
stack.push(allocTag(pool, name, start, end));
}
}, options);
stack.length = pool.length = 0;
return result;
}
/**
* Returns balanced tag model: a list of all XML/HTML tags that could possibly match
* given location when moving in inward direction
*/
function balancedInward(source, pos, opt) {
// Collecting tags for inward balancing is a bit trickier: we have to store
// first child of every matched tag until we find the one that matches given
// location
const pool = [];
const stack = [];
const options = createOptions(opt);
const result = [];
const alloc = (name, start, end) => {
if (pool.length) {
const tag = pool.pop();
tag.name = name;
tag.ranges.push(start, end);
return tag;
}
return { name, ranges: [start, end] };
};
const release = (tag) => {
tag.ranges.length = 0;
tag.firstChild = void 0;
pool.push(tag);
};
scan(source, (name, type, start, end) => {
if (type === 2 /* Close */) {
if (!stack.length) {
// Some sort of lone closing tag, ignore it
return;
}
let tag = last(stack);
if (tag.name === name) { // XXX check for invalid tag names?
// Matching closing tag found, check if matched pair is a candidate
// for outward balancing
if (tag.ranges[0] <= pos && pos <= end) {
result.push({
name,
open: tag.ranges.slice(0, 2),
close: [start, end]
});
while (tag.firstChild) {
const child = tag.firstChild;
const res = {
name: child.name,
open: child.ranges.slice(0, 2)
};
if (child.ranges.length > 2) {
res.close = child.ranges.slice(2, 4);
}
result.push(res);
release(tag);
tag = child;
}
return false;
}
else {
stack.pop();
const parent = last(stack);
if (parent && !parent.firstChild) {
// No first child in parent node: store current tag
tag.ranges.push(start, end);
parent.firstChild = tag;
}
else {
release(tag);
}
}
}
}
else if (type === 3 /* SelfClose */ || isSelfClose(name, options)) {
if (start < pos && pos < end) {
// Matched self-closed tag, no need to look further
result.push({ name, open: [start, end] });
return false;
}
const parent = last(stack);
if (parent && !parent.firstChild) {
parent.firstChild = alloc(name, start, end);
}
}
else {
stack.push(alloc(name, start, end));
}
}, options);
stack.length = pool.length = 0;
return result;
}
function allocTag(pool, name, start, end) {
if (pool.length) {
const tag = pool.pop();
tag.name = name;
tag.start = start;
tag.end = end;
return tag;
}
return { name, start, end };
}
function releaseTag(pool, tag) {
pool.push(tag);
}
/**
* Returns parsed attributes from given source
*/
function getAttributes(source, start, end, name) {
const tokens = attributes(source.slice(start, end), name);
tokens.forEach(attr => {
attr.nameStart += start;
attr.nameEnd += start;
if (attr.value != null) {
attr.valueStart += start;
attr.valueEnd += start;
}
});
return tokens;
}
/**
* Check if given tag is self-close for current parsing context
*/
function isSelfClose(name, options) {
return !options.xml && options.empty.includes(name);
}
function last(arr) {
return arr.length ? arr[arr.length - 1] : null;
}
exports.attributes = attributes;
exports.balancedInward = balancedInward;
exports.balancedOutward = balancedOutward;
exports.createOptions = createOptions;
exports.default = match;
exports.scan = scan;
//# sourceMappingURL=html-matcher.cjs.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,630 +0,0 @@
import Scanner, { eatPair, isAlpha, isNumber, isQuote, isSpace, eatQuoted } from '@emmetio/scanner';
const defaultOptions = {
xml: false,
allTokens: false,
special: {
style: null,
script: ['', 'text/javascript', 'application/x-javascript', 'javascript', 'typescript', 'ts', 'coffee', 'coffeescript']
},
empty: ['img', 'meta', 'link', 'br', 'base', 'hr', 'area', 'wbr', 'col', 'embed', 'input', 'param', 'source', 'track']
};
/** Options for `Scanner` utils */
const opt = { throws: false };
function createOptions(options = {}) {
return Object.assign(Object.assign({}, defaultOptions), options);
}
/**
* Converts given string into array of character codes
*/
function toCharCodes(str) {
return str.split('').map(ch => ch.charCodeAt(0));
}
/**
* Consumes array of character codes from given scanner
*/
function consumeArray(scanner, codes) {
const start = scanner.pos;
for (let i = 0; i < codes.length; i++) {
if (!scanner.eat(codes[i])) {
scanner.pos = start;
return false;
}
}
scanner.start = start;
return true;
}
/**
* Consumes section from given string which starts with `open` character codes
* and ends with `close` character codes
* @return Returns `true` if section was consumed
*/
function consumeSection(scanner, open, close, allowUnclosed) {
const start = scanner.pos;
if (consumeArray(scanner, open)) {
// consumed `<!--`, read next until we find ending part or reach the end of input
while (!scanner.eof()) {
if (consumeArray(scanner, close)) {
scanner.start = start;
return true;
}
scanner.pos++;
}
// unclosed section is allowed
if (allowUnclosed) {
scanner.start = start;
return true;
}
scanner.pos = start;
return false;
}
// unable to find section, revert to initial position
scanner.pos = start;
return false;
}
/**
* Check if given character can be used as a start of tag name or attribute
*/
function nameStartChar(ch) {
// Limited XML spec: https://www.w3.org/TR/xml/#NT-NameStartChar
return isAlpha(ch) || ch === 58 /* Colon */ || ch === 95 /* Underscore */
|| (ch >= 0xC0 && ch <= 0xD6)
|| (ch >= 0xD8 && ch <= 0xF6)
|| (ch >= 0xF8 && ch <= 0x2FF)
|| (ch >= 0x370 && ch <= 0x37D)
|| (ch >= 0x37F && ch <= 0x1FFF);
}
/**
* Check if given character can be used in a tag or attribute name
*/
function nameChar(ch) {
// Limited XML spec: https://www.w3.org/TR/xml/#NT-NameChar
return nameStartChar(ch) || ch === 45 /* Dash */ || ch === 46 /* Dot */ || isNumber(ch)
|| ch === 0xB7
|| (ch >= 0x0300 && ch <= 0x036F);
}
/**
* Consumes identifier from given scanner
*/
function ident(scanner) {
const start = scanner.pos;
if (scanner.eat(nameStartChar)) {
scanner.eatWhile(nameChar);
scanner.start = start;
return true;
}
return false;
}
/**
* Check if given code is tag terminator
*/
function isTerminator(code) {
return code === 62 /* RightAngle */ || code === 47 /* Slash */;
}
/**
* Check if given character code is valid unquoted value
*/
function isUnquoted(code) {
return !isNaN(code) && !isQuote(code) && !isSpace(code) && !isTerminator(code);
}
/**
* Consumes paired tokens (like `[` and `]`) with respect of nesting and embedded
* quoted values
* @return `true` if paired token was consumed
*/
function consumePaired(scanner) {
return eatPair(scanner, 60 /* LeftAngle */, 62 /* RightAngle */, opt)
|| eatPair(scanner, 40 /* LeftRound */, 41 /* RightRound */, opt)
|| eatPair(scanner, 91 /* LeftSquare */, 93 /* RightSquare */, opt)
|| eatPair(scanner, 123 /* LeftCurly */, 125 /* RightCurly */, opt);
}
/**
* Returns unquoted value of given string
*/
function getUnquotedValue(value) {
// Trim quotes
if (isQuote(value.charCodeAt(0))) {
value = value.slice(1);
}
if (isQuote(value.charCodeAt(value.length - 1))) {
value = value.slice(0, -1);
}
return value;
}
/**
* Parses given string as list of HTML attributes.
* @param src A fragment to parse. If `name` argument is provided, it must be an
* opening tag (`<a foo="bar">`), otherwise it should be a fragment between element
* name and tag closing angle (`foo="bar"`)
* @param name Tag name
*/
function attributes(src, name) {
const result = [];
let start = 0;
let end = src.length;
if (name) {
start = name.length + 1;
end -= src.slice(-2) === '/>' ? 2 : 1;
}
const scanner = new Scanner(src, start, end);
while (!scanner.eof()) {
scanner.eatWhile(isSpace);
if (attributeName(scanner)) {
const token = {
name: scanner.current(),
nameStart: scanner.start,
nameEnd: scanner.pos
};
if (scanner.eat(61 /* Equals */) && attributeValue(scanner)) {
token.value = scanner.current();
token.valueStart = scanner.start;
token.valueEnd = scanner.pos;
}
result.push(token);
}
else {
// Do not break on invalid attributes: we are not validating parser
scanner.pos++;
}
}
return result;
}
/**
* Consumes attribute name from given scanner context
*/
function attributeName(scanner) {
const start = scanner.pos;
if (scanner.eat(42 /* Asterisk */) || scanner.eat(35 /* Hash */)) {
// Angular-style directives: `<section *ngIf="showSection">`, `<video #movieplayer ...>`
ident(scanner);
scanner.start = start;
return true;
}
// Attribute name could be a regular name or expression:
// React-style `<div {...props}>`
// Angular-style `<div [ng-for]>` or `<div *ng-for>`
return consumePaired(scanner) || ident(scanner);
}
/**
* Consumes attribute value
*/
function attributeValue(scanner) {
// Supported attribute values are quoted, React-like expressions (`{foo}`)
// or unquoted literals
return eatQuoted(scanner, opt) || consumePaired(scanner) || unquoted(scanner);
}
/**
* Returns clean (unquoted) value of `name` attribute
*/
function getAttributeValue(attrs, name) {
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
if (attr.name === name) {
return attr.value && getUnquotedValue(attr.value);
}
}
}
/**
* Consumes unquoted value
*/
function unquoted(scanner) {
const start = scanner.pos;
if (scanner.eatWhile(isUnquoted)) {
scanner.start = start;
return true;
}
}
const cdataOpen = toCharCodes('<![CDATA[');
const cdataClose = toCharCodes(']]>');
const commentOpen = toCharCodes('<!--');
const commentClose = toCharCodes('-->');
const piStart = toCharCodes('<?');
const piEnd = toCharCodes('?>');
const erbStart = toCharCodes('<%');
const erbEnd = toCharCodes('%>');
/**
* Performs fast scan of given source code: for each tag found it invokes callback
* with tag name, its type (open, close, self-close) and range in original source.
* Unlike regular scanner, fast scanner doesnt provide info about attributes to
* reduce object allocations hence increase performance.
* If `callback` returns `false`, scanner stops parsing.
* @param special List of “special” HTML tags which should be ignored. Most likely
* its a "script" and "style" tags.
*/
function scan(source, callback, options) {
const scanner = new Scanner(source);
const special = options ? options.special : null;
const allTokens = options ? options.allTokens : false;
let type;
let name;
let nameStart;
let nameEnd;
let nameCodes;
let found = false;
let piName = null;
while (!scanner.eof()) {
const start = scanner.pos;
if (cdata(scanner)) {
if (allTokens && callback('#cdata', 4 /* CData */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (comment(scanner)) {
if (allTokens && callback('#comment', 6 /* Comment */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (erb(scanner)) {
if (allTokens && callback('#erb', 7 /* ERB */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (piName = processingInstruction(scanner)) {
if (allTokens && callback(piName, 5 /* ProcessingInstruction */, scanner.start, scanner.pos) === false) {
break;
}
}
else if (scanner.eat(60 /* LeftAngle */)) {
// Maybe a tag name?
type = scanner.eat(47 /* Slash */) ? 2 /* Close */ : 1 /* Open */;
nameStart = scanner.pos;
if (ident(scanner)) {
// Consumed tag name
nameEnd = scanner.pos;
if (type !== 2 /* Close */) {
skipAttributes(scanner);
scanner.eatWhile(isSpace);
if (scanner.eat(47 /* Slash */)) {
type = 3 /* SelfClose */;
}
}
if (scanner.eat(62 /* RightAngle */)) {
// Tag properly closed
name = scanner.substring(nameStart, nameEnd);
if (callback(name, type, start, scanner.pos) === false) {
break;
}
if (type === 1 /* Open */ && special && isSpecial(special, name, source, start, scanner.pos)) {
// Found opening tag of special element: we should skip
// scanner contents until we find closing tag
nameCodes = toCharCodes(name);
found = false;
while (!scanner.eof()) {
if (consumeClosing(scanner, nameCodes)) {
found = true;
break;
}
scanner.pos++;
}
if (found && callback(name, 2 /* Close */, scanner.start, scanner.pos) === false) {
break;
}
}
}
}
}
else {
scanner.pos++;
}
}
}
/**
* Skips attributes in current tag context
*/
function skipAttributes(scanner) {
while (!scanner.eof()) {
scanner.eatWhile(isSpace);
if (attributeName(scanner)) {
if (scanner.eat(61 /* Equals */)) {
attributeValue(scanner);
}
}
else if (isTerminator(scanner.peek())) {
break;
}
else {
scanner.pos++;
}
}
}
/**
* Consumes closing tag with given name from scanner
*/
function consumeClosing(scanner, name) {
const start = scanner.pos;
if (scanner.eat(60 /* LeftAngle */) && scanner.eat(47 /* Slash */) && consumeArray(scanner, name) && scanner.eat(62 /* RightAngle */)) {
scanner.start = start;
return true;
}
scanner.pos = start;
return false;
}
/**
* Consumes CDATA from given scanner
*/
function cdata(scanner) {
return consumeSection(scanner, cdataOpen, cdataClose, true);
}
/**
* Consumes comments from given scanner
*/
function comment(scanner) {
return consumeSection(scanner, commentOpen, commentClose, true);
}
/**
* Consumes processing instruction from given scanner. If consumed, returns
* processing instruction name
*/
function processingInstruction(scanner) {
const start = scanner.pos;
if (consumeArray(scanner, piStart) && ident(scanner)) {
const name = scanner.current();
while (!scanner.eof()) {
if (consumeArray(scanner, piEnd)) {
break;
}
eatQuoted(scanner) || scanner.pos++;
}
scanner.start = start;
return name;
}
scanner.pos = start;
return null;
}
/**
* Consumes ERB-style entity: `<% ... %>` or `<%= ... %>`
*/
function erb(scanner) {
const start = scanner.pos;
if (consumeArray(scanner, erbStart)) {
while (!scanner.eof()) {
if (consumeArray(scanner, erbEnd)) {
break;
}
eatQuoted(scanner) || scanner.pos++;
}
scanner.start = start;
return true;
}
scanner.pos = start;
return false;
}
/**
* Check if given tag name should be considered as special
*/
function isSpecial(special, name, source, start, end) {
if (name in special) {
const typeValues = special[name];
if (!Array.isArray(typeValues)) {
return true;
}
const attrs = attributes(source.substring(start + name.length + 1, end - 1));
return typeValues.includes(getAttributeValue(attrs, 'type') || '');
}
return false;
}
/**
* Finds matched tag for given `pos` location in XML/HTML `source`
*/
function match(source, pos, opt) {
// Since we expect large input document, well use pooling technique
// for storing tag data to reduce memory pressure and improve performance
const pool = [];
const stack = [];
const options = createOptions(opt);
let result = null;
scan(source, (name, type, start, end) => {
if (type === 1 /* Open */ && isSelfClose(name, options)) {
// Found empty element in HTML mode, mark is as self-closing
type = 3 /* SelfClose */;
}
if (type === 1 /* Open */) {
// Allocate tag object from pool
stack.push(allocTag(pool, name, start, end));
}
else if (type === 3 /* SelfClose */) {
if (start < pos && pos < end) {
// Matched given self-closing tag
result = {
name,
attributes: getAttributes(source, start, end, name),
open: [start, end]
};
return false;
}
}
else {
const tag = last(stack);
if (tag && tag.name === name) {
// Matching closing tag found
if (tag.start < pos && pos < end) {
result = {
name,
attributes: getAttributes(source, tag.start, tag.end, name),
open: [tag.start, tag.end],
close: [start, end]
};
return false;
}
else if (stack.length) {
// Release tag object for further re-use
releaseTag(pool, stack.pop());
}
}
}
}, options);
stack.length = pool.length = 0;
return result;
}
/**
* Returns balanced tag model: a list of all XML/HTML tags that could possibly match
* given location when moving in outward direction
*/
function balancedOutward(source, pos, opt) {
const pool = [];
const stack = [];
const options = createOptions(opt);
const result = [];
scan(source, (name, type, start, end) => {
if (type === 2 /* Close */) {
const tag = last(stack);
if (tag && tag.name === name) { // XXX check for invalid tag names?
// Matching closing tag found, check if matched pair is a candidate
// for outward balancing
if (tag.start < pos && pos < end) {
result.push({
name,
open: [tag.start, tag.end],
close: [start, end]
});
}
// Release tag object for further re-use
releaseTag(pool, stack.pop());
}
}
else if (type === 3 /* SelfClose */ || isSelfClose(name, options)) {
if (start < pos && pos < end) {
// Matched self-closed tag
result.push({ name, open: [start, end] });
}
}
else {
stack.push(allocTag(pool, name, start, end));
}
}, options);
stack.length = pool.length = 0;
return result;
}
/**
* Returns balanced tag model: a list of all XML/HTML tags that could possibly match
* given location when moving in inward direction
*/
function balancedInward(source, pos, opt) {
// Collecting tags for inward balancing is a bit trickier: we have to store
// first child of every matched tag until we find the one that matches given
// location
const pool = [];
const stack = [];
const options = createOptions(opt);
const result = [];
const alloc = (name, start, end) => {
if (pool.length) {
const tag = pool.pop();
tag.name = name;
tag.ranges.push(start, end);
return tag;
}
return { name, ranges: [start, end] };
};
const release = (tag) => {
tag.ranges.length = 0;
tag.firstChild = void 0;
pool.push(tag);
};
scan(source, (name, type, start, end) => {
if (type === 2 /* Close */) {
if (!stack.length) {
// Some sort of lone closing tag, ignore it
return;
}
let tag = last(stack);
if (tag.name === name) { // XXX check for invalid tag names?
// Matching closing tag found, check if matched pair is a candidate
// for outward balancing
if (tag.ranges[0] <= pos && pos <= end) {
result.push({
name,
open: tag.ranges.slice(0, 2),
close: [start, end]
});
while (tag.firstChild) {
const child = tag.firstChild;
const res = {
name: child.name,
open: child.ranges.slice(0, 2)
};
if (child.ranges.length > 2) {
res.close = child.ranges.slice(2, 4);
}
result.push(res);
release(tag);
tag = child;
}
return false;
}
else {
stack.pop();
const parent = last(stack);
if (parent && !parent.firstChild) {
// No first child in parent node: store current tag
tag.ranges.push(start, end);
parent.firstChild = tag;
}
else {
release(tag);
}
}
}
}
else if (type === 3 /* SelfClose */ || isSelfClose(name, options)) {
if (start < pos && pos < end) {
// Matched self-closed tag, no need to look further
result.push({ name, open: [start, end] });
return false;
}
const parent = last(stack);
if (parent && !parent.firstChild) {
parent.firstChild = alloc(name, start, end);
}
}
else {
stack.push(alloc(name, start, end));
}
}, options);
stack.length = pool.length = 0;
return result;
}
function allocTag(pool, name, start, end) {
if (pool.length) {
const tag = pool.pop();
tag.name = name;
tag.start = start;
tag.end = end;
return tag;
}
return { name, start, end };
}
function releaseTag(pool, tag) {
pool.push(tag);
}
/**
* Returns parsed attributes from given source
*/
function getAttributes(source, start, end, name) {
const tokens = attributes(source.slice(start, end), name);
tokens.forEach(attr => {
attr.nameStart += start;
attr.nameEnd += start;
if (attr.value != null) {
attr.valueStart += start;
attr.valueEnd += start;
}
});
return tokens;
}
/**
* Check if given tag is self-close for current parsing context
*/
function isSelfClose(name, options) {
return !options.xml && options.empty.includes(name);
}
function last(arr) {
return arr.length ? arr[arr.length - 1] : null;
}
export default match;
export { attributes, balancedInward, balancedOutward, createOptions, scan };
//# sourceMappingURL=html-matcher.es.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,38 +0,0 @@
import { ScannerOptions } from './utils';
import scan from './scan';
import attributes, { AttributeToken } from './attributes';
export { scan, attributes, AttributeToken };
export { createOptions, ScannerOptions, ElementType, FastScanCallback } from './utils';
declare type TagRange = [number, number];
export interface MatchedTag {
/** Name of matched tag */
name: string;
/** List of tag attributes */
attributes: AttributeToken[];
/** Range of opening tag */
open: TagRange;
/** Range of closing tag. If absent, tag is self-closing */
close?: TagRange;
}
export interface BalancedTag {
/** Name of balanced tag */
name: string;
/** Range of opening tag */
open: TagRange;
/** Range of closing tag. If absent, tag is self-closing */
close?: TagRange;
}
/**
* Finds matched tag for given `pos` location in XML/HTML `source`
*/
export default function match(source: string, pos: number, opt?: Partial<ScannerOptions>): MatchedTag | null;
/**
* Returns balanced tag model: a list of all XML/HTML tags that could possibly match
* given location when moving in outward direction
*/
export declare function balancedOutward(source: string, pos: number, opt?: Partial<ScannerOptions>): BalancedTag[];
/**
* Returns balanced tag model: a list of all XML/HTML tags that could possibly match
* given location when moving in inward direction
*/
export declare function balancedInward(source: string, pos: number, opt?: Partial<ScannerOptions>): BalancedTag[];

View File

@@ -1,11 +0,0 @@
import { FastScanCallback, ScannerOptions } from './utils';
/**
* Performs fast scan of given source code: for each tag found it invokes callback
* with tag name, its type (open, close, self-close) and range in original source.
* Unlike regular scanner, fast scanner doesnt provide info about attributes to
* reduce object allocations hence increase performance.
* If `callback` returns `false`, scanner stops parsing.
* @param special List of “special” HTML tags which should be ignored. Most likely
* its a "script" and "style" tags.
*/
export default function scan(source: string, callback: FastScanCallback, options?: ScannerOptions): void;

View File

@@ -1,123 +0,0 @@
import Scanner from '@emmetio/scanner';
export declare type FastScanCallback = (name: string, type: ElementType, start: number, end: number) => false | any;
export declare const enum ElementType {
Open = 1,
Close = 2,
SelfClose = 3,
CData = 4,
ProcessingInstruction = 5,
Comment = 6,
ERB = 7
}
export interface SpecialType {
[tagName: string]: string[] | null;
}
export declare const enum Chars {
/** `-` character */
Dash = 45,
/** `.` character */
Dot = 46,
/** `/` character */
Slash = 47,
/** `:` character */
Colon = 58,
/** `<` character */
LeftAngle = 60,
/** `>` character */
RightAngle = 62,
/** `(` character */
LeftRound = 40,
/** `)` character */
RightRound = 41,
/** `[` character */
LeftSquare = 91,
/** `]` character */
RightSquare = 93,
/** `{` character */
LeftCurly = 123,
/** `}` character */
RightCurly = 125,
/** `_` character */
Underscore = 95,
/** `=` character */
Equals = 61,
/** `*` character */
Asterisk = 42,
/** `#` character */
Hash = 35
}
export interface ScannerOptions {
/**
* Parses given source as XML document. It alters how should-be-empty
* elements are treated: for example, in XML mode parser will try to locate
* closing pair for `<br>` tag
*/
xml: boolean;
/**
* List of tags that should have special parsing rules, e.g. should not parse
* inner content and skip to closing tag. Key is a tag name that should be
* considered special and value is either empty (always mark element as special)
* or list of `type` attribute values, which, if present with one of this value,
* make element special
*/
special: SpecialType;
/**
* List of elements that should be treated as empty (e.g. without closing tag)
* in non-XML syntax
*/
empty: string[];
/**
* If enabled, scanner callback will receive XML tokes, including comment, cdata
* and processing instructions. If disabled, only tags are emitted
*/
allTokens: boolean;
}
/** Options for `Scanner` utils */
export declare const opt: {
throws: boolean;
};
export declare function createOptions(options?: Partial<ScannerOptions>): ScannerOptions;
/**
* Converts given string into array of character codes
*/
export declare function toCharCodes(str: string): number[];
/**
* Consumes array of character codes from given scanner
*/
export declare function consumeArray(scanner: Scanner, codes: number[]): boolean;
/**
* Consumes section from given string which starts with `open` character codes
* and ends with `close` character codes
* @return Returns `true` if section was consumed
*/
export declare function consumeSection(scanner: Scanner, open: number[], close: number[], allowUnclosed?: boolean): boolean;
/**
* Check if given character can be used as a start of tag name or attribute
*/
export declare function nameStartChar(ch: number): boolean;
/**
* Check if given character can be used in a tag or attribute name
*/
export declare function nameChar(ch: number): boolean;
/**
* Consumes identifier from given scanner
*/
export declare function ident(scanner: Scanner): boolean;
/**
* Check if given code is tag terminator
*/
export declare function isTerminator(code: number): boolean;
/**
* Check if given character code is valid unquoted value
*/
export declare function isUnquoted(code: number): boolean;
/**
* Consumes paired tokens (like `[` and `]`) with respect of nesting and embedded
* quoted values
* @return `true` if paired token was consumed
*/
export declare function consumePaired(scanner: Scanner): boolean;
/**
* Returns unquoted value of given string
*/
export declare function getUnquotedValue(value: string): string;

View File

@@ -1,47 +0,0 @@
{
"name": "@emmetio/html-matcher",
"version": "1.3.0",
"description": "Minimalistic and ultra-fast HTML parser & matcher",
"main": "./dist/html-matcher.cjs.js",
"module": "./dist/html-matcher.es.js",
"types": "./dist/index.d.ts",
"dependencies": {
"@emmetio/scanner": "^1.0.0"
},
"devDependencies": {
"@types/mocha": "^7.0.2",
"@types/node": "^12.7.5",
"mocha": "^7.1.1",
"rollup": "^2.6.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-typescript2": "^0.27.0",
"ts-node": "^8.8.2",
"tslint": "^6.1.1",
"typescript": "^3.8.3"
},
"scripts": {
"test": "mocha",
"lint": "tslint ./src/*.ts",
"build": "rollup -c",
"clean": "rm -rf ./dist",
"prepare": "npm run lint && npm test && npm run clean && npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/emmetio/html-matcher.git"
},
"keywords": [],
"author": "Sergey Chikuyonok <serge.che@gmail.com>",
"license": "ISC",
"bugs": {
"url": "https://github.com/emmetio/html-matcher/issues"
},
"homepage": "https://github.com/emmetio/html-matcher#readme",
"directories": {
"test": "test"
},
"mocha": {
"require": "ts-node/register",
"spec": "./test/*.ts"
}
}

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020 Sergey Chikuyonok <serge.che@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,49 +0,0 @@
{
"name": "@emmetio/scanner",
"version": "1.0.4",
"description": "Scans given text character-by-character",
"main": "./scanner.cjs",
"module": "./scanner.js",
"types": "./scanner.d.ts",
"type": "module",
"exports": {
"import": "./scanner.js",
"require": "./scanner.cjs"
},
"scripts": {
"test": "mocha",
"build": "rollup -c",
"clean": "rimraf ./scanner.* ./*.d.ts",
"prepublishOnly": "npm run clean && npm run build && npm test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/emmetio/stream-reader.git"
},
"keywords": [
"emmet",
"stream",
"scanner"
],
"author": "Sergey Chikuyonok <serge.che@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/emmetio/emmet/issues"
},
"homepage": "https://github.com/emmetio/emmet#readme",
"devDependencies": {
"@rollup/plugin-typescript": "^10.0.1",
"@types/mocha": "^10.0.1",
"@types/node": "^18.11.18",
"mocha": "^10.2.0",
"rimraf": "^5.0.0",
"rollup": "^3.9.0",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
"mocha": {
"loader": "ts-node/esm",
"spec": "./test/*.ts"
},
"gitHead": "fce2127ece65adbb293a40aa0577e4558658c559"
}

View File

@@ -1,253 +0,0 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
const defaultQuotedOptions = {
escape: 92,
throws: false
};
/**
* Check if given code is a number
*/
function isNumber(code) {
return code > 47 && code < 58;
}
/**
* Check if given character code is alpha code (letter through A to Z)
*/
function isAlpha(code, from, to) {
from = from || 65; // A
to = to || 90; // Z
code &= ~32; // quick hack to convert any char code to uppercase char code
return code >= from && code <= to;
}
/**
* Check if given character code is alpha-numeric (letter through A to Z or number)
*/
function isAlphaNumeric(code) {
return isNumber(code) || isAlpha(code);
}
function isAlphaNumericWord(code) {
return isNumber(code) || isAlphaWord(code);
}
function isAlphaWord(code) {
return code === 95 /* _ */ || isAlpha(code);
}
/**
* Check for Umlauts i.e. ä, Ä, ö, Ö, ü and Ü
*/
function isUmlaut(code) {
return code === 196
|| code == 214
|| code === 220
|| code === 228
|| code === 246
|| code === 252;
}
/**
* Check if given character code is a white-space character: a space character
* or line breaks
*/
function isWhiteSpace(code) {
return code === 32 /* space */
|| code === 9 /* tab */
|| code === 160; /* non-breaking space */
}
/**
* Check if given character code is a space character
*/
function isSpace(code) {
return isWhiteSpace(code)
|| code === 10 /* LF */
|| code === 13; /* CR */
}
/**
* Consumes 'single' or "double"-quoted string from given string, if possible
* @return `true` if quoted string was consumed. The contents of quoted string
* will be available as `stream.current()`
*/
function eatQuoted(stream, options) {
options = Object.assign(Object.assign({}, defaultQuotedOptions), options);
const start = stream.pos;
const quote = stream.peek();
if (stream.eat(isQuote)) {
while (!stream.eof()) {
switch (stream.next()) {
case quote:
stream.start = start;
return true;
case options.escape:
stream.next();
break;
}
}
// If were here then stream wasnt properly consumed.
// Revert stream and decide what to do
stream.pos = start;
if (options.throws) {
throw stream.error('Unable to consume quoted string');
}
}
return false;
}
/**
* Check if given character code is a quote character
*/
function isQuote(code) {
return code === 39 /* ' */ || code === 34 /* " */;
}
/**
* Eats paired characters substring, for example `(foo)` or `[bar]`
* @param open Character code of pair opening
* @param close Character code of pair closing
* @return Returns `true` if character pair was successfully consumed, its
* content will be available as `stream.current()`
*/
function eatPair(stream, open, close, options) {
options = Object.assign(Object.assign({}, defaultQuotedOptions), options);
const start = stream.pos;
if (stream.eat(open)) {
let stack = 1;
let ch;
while (!stream.eof()) {
if (eatQuoted(stream, options)) {
continue;
}
ch = stream.next();
if (ch === open) {
stack++;
}
else if (ch === close) {
stack--;
if (!stack) {
stream.start = start;
return true;
}
}
else if (ch === options.escape) {
stream.next();
}
}
// If were here then paired character cant be consumed
stream.pos = start;
if (options.throws) {
throw stream.error(`Unable to find matching pair for ${String.fromCharCode(open)}`);
}
}
return false;
}
/**
* A streaming, character code-based string reader
*/
class Scanner {
constructor(str, start, end) {
if (end == null && typeof str === 'string') {
end = str.length;
}
this.string = str;
this.pos = this.start = start || 0;
this.end = end || 0;
}
/**
* Returns true only if the stream is at the end of the file.
*/
eof() {
return this.pos >= this.end;
}
/**
* Creates a new stream instance which is limited to given `start` and `end`
* range. E.g. its `eof()` method will look at `end` property, not actual
* stream end
*/
limit(start, end) {
return new Scanner(this.string, start, end);
}
/**
* Returns the next character code in the stream without advancing it.
* Will return NaN at the end of the file.
*/
peek() {
return this.string.charCodeAt(this.pos);
}
/**
* Returns the next character in the stream and advances it.
* Also returns <code>undefined</code> when no more characters are available.
*/
next() {
if (this.pos < this.string.length) {
return this.string.charCodeAt(this.pos++);
}
}
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
* the given argument, it is consumed and returned.
* Otherwise, `false` is returned.
*/
eat(match) {
const ch = this.peek();
const ok = typeof match === 'function' ? match(ch) : ch === match;
if (ok) {
this.next();
}
return ok;
}
/**
* Repeatedly calls <code>eat</code> with the given argument, until it
* fails. Returns <code>true</code> if any characters were eaten.
*/
eatWhile(match) {
const start = this.pos;
while (!this.eof() && this.eat(match)) { /* */ }
return this.pos !== start;
}
/**
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
*/
backUp(n) {
this.pos -= (n || 1);
}
/**
* Get the string between the start of the current token and the
* current stream position.
*/
current() {
return this.substring(this.start, this.pos);
}
/**
* Returns substring for given range
*/
substring(start, end) {
return this.string.slice(start, end);
}
/**
* Creates error object with current stream state
*/
error(message, pos = this.pos) {
return new ScannerError(`${message} at ${pos + 1}`, pos, this.string);
}
}
class ScannerError extends Error {
constructor(message, pos, str) {
super(message);
this.pos = pos;
this.string = str;
}
}
exports.ScannerError = ScannerError;
exports.default = Scanner;
exports.eatPair = eatPair;
exports.eatQuoted = eatQuoted;
exports.isAlpha = isAlpha;
exports.isAlphaNumeric = isAlphaNumeric;
exports.isAlphaNumericWord = isAlphaNumericWord;
exports.isAlphaWord = isAlphaWord;
exports.isNumber = isNumber;
exports.isQuote = isQuote;
exports.isSpace = isSpace;
exports.isUmlaut = isUmlaut;
exports.isWhiteSpace = isWhiteSpace;
//# sourceMappingURL=scanner.cjs.map

File diff suppressed because one or more lines are too long

View File

@@ -1,71 +0,0 @@
export * from './utils.js';
type MatchFn = (ch: number) => boolean;
/**
* A streaming, character code-based string reader
*/
export default class Scanner {
/** Current string */
string: string;
/** Current scanner position */
pos: number;
/** Lower range limit where string reader is available */
start: number;
/** Upper range limit where string reader is available */
end: number;
constructor(str: string, start?: number, end?: number);
/**
* Returns true only if the stream is at the end of the file.
*/
eof(): boolean;
/**
* Creates a new stream instance which is limited to given `start` and `end`
* range. E.g. its `eof()` method will look at `end` property, not actual
* stream end
*/
limit(start?: number, end?: number): Scanner;
/**
* Returns the next character code in the stream without advancing it.
* Will return NaN at the end of the file.
*/
peek(): number;
/**
* Returns the next character in the stream and advances it.
* Also returns <code>undefined</code> when no more characters are available.
*/
next(): number | undefined;
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
* the given argument, it is consumed and returned.
* Otherwise, `false` is returned.
*/
eat(match: number | MatchFn): boolean;
/**
* Repeatedly calls <code>eat</code> with the given argument, until it
* fails. Returns <code>true</code> if any characters were eaten.
*/
eatWhile(match: number | MatchFn): boolean;
/**
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
*/
backUp(n: number): void;
/**
* Get the string between the start of the current token and the
* current stream position.
*/
current(): string;
/**
* Returns substring for given range
*/
substring(start: number, end?: number): string;
/**
* Creates error object with current stream state
*/
error(message: string, pos?: number): ScannerError;
}
export declare class ScannerError extends Error {
pos: number;
string: string;
constructor(message: string, pos: number, str: string);
}

View File

@@ -1,237 +0,0 @@
const defaultQuotedOptions = {
escape: 92,
throws: false
};
/**
* Check if given code is a number
*/
function isNumber(code) {
return code > 47 && code < 58;
}
/**
* Check if given character code is alpha code (letter through A to Z)
*/
function isAlpha(code, from, to) {
from = from || 65; // A
to = to || 90; // Z
code &= ~32; // quick hack to convert any char code to uppercase char code
return code >= from && code <= to;
}
/**
* Check if given character code is alpha-numeric (letter through A to Z or number)
*/
function isAlphaNumeric(code) {
return isNumber(code) || isAlpha(code);
}
function isAlphaNumericWord(code) {
return isNumber(code) || isAlphaWord(code);
}
function isAlphaWord(code) {
return code === 95 /* _ */ || isAlpha(code);
}
/**
* Check for Umlauts i.e. ä, Ä, ö, Ö, ü and Ü
*/
function isUmlaut(code) {
return code === 196
|| code == 214
|| code === 220
|| code === 228
|| code === 246
|| code === 252;
}
/**
* Check if given character code is a white-space character: a space character
* or line breaks
*/
function isWhiteSpace(code) {
return code === 32 /* space */
|| code === 9 /* tab */
|| code === 160; /* non-breaking space */
}
/**
* Check if given character code is a space character
*/
function isSpace(code) {
return isWhiteSpace(code)
|| code === 10 /* LF */
|| code === 13; /* CR */
}
/**
* Consumes 'single' or "double"-quoted string from given string, if possible
* @return `true` if quoted string was consumed. The contents of quoted string
* will be available as `stream.current()`
*/
function eatQuoted(stream, options) {
options = Object.assign(Object.assign({}, defaultQuotedOptions), options);
const start = stream.pos;
const quote = stream.peek();
if (stream.eat(isQuote)) {
while (!stream.eof()) {
switch (stream.next()) {
case quote:
stream.start = start;
return true;
case options.escape:
stream.next();
break;
}
}
// If were here then stream wasnt properly consumed.
// Revert stream and decide what to do
stream.pos = start;
if (options.throws) {
throw stream.error('Unable to consume quoted string');
}
}
return false;
}
/**
* Check if given character code is a quote character
*/
function isQuote(code) {
return code === 39 /* ' */ || code === 34 /* " */;
}
/**
* Eats paired characters substring, for example `(foo)` or `[bar]`
* @param open Character code of pair opening
* @param close Character code of pair closing
* @return Returns `true` if character pair was successfully consumed, its
* content will be available as `stream.current()`
*/
function eatPair(stream, open, close, options) {
options = Object.assign(Object.assign({}, defaultQuotedOptions), options);
const start = stream.pos;
if (stream.eat(open)) {
let stack = 1;
let ch;
while (!stream.eof()) {
if (eatQuoted(stream, options)) {
continue;
}
ch = stream.next();
if (ch === open) {
stack++;
}
else if (ch === close) {
stack--;
if (!stack) {
stream.start = start;
return true;
}
}
else if (ch === options.escape) {
stream.next();
}
}
// If were here then paired character cant be consumed
stream.pos = start;
if (options.throws) {
throw stream.error(`Unable to find matching pair for ${String.fromCharCode(open)}`);
}
}
return false;
}
/**
* A streaming, character code-based string reader
*/
class Scanner {
constructor(str, start, end) {
if (end == null && typeof str === 'string') {
end = str.length;
}
this.string = str;
this.pos = this.start = start || 0;
this.end = end || 0;
}
/**
* Returns true only if the stream is at the end of the file.
*/
eof() {
return this.pos >= this.end;
}
/**
* Creates a new stream instance which is limited to given `start` and `end`
* range. E.g. its `eof()` method will look at `end` property, not actual
* stream end
*/
limit(start, end) {
return new Scanner(this.string, start, end);
}
/**
* Returns the next character code in the stream without advancing it.
* Will return NaN at the end of the file.
*/
peek() {
return this.string.charCodeAt(this.pos);
}
/**
* Returns the next character in the stream and advances it.
* Also returns <code>undefined</code> when no more characters are available.
*/
next() {
if (this.pos < this.string.length) {
return this.string.charCodeAt(this.pos++);
}
}
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
* the given argument, it is consumed and returned.
* Otherwise, `false` is returned.
*/
eat(match) {
const ch = this.peek();
const ok = typeof match === 'function' ? match(ch) : ch === match;
if (ok) {
this.next();
}
return ok;
}
/**
* Repeatedly calls <code>eat</code> with the given argument, until it
* fails. Returns <code>true</code> if any characters were eaten.
*/
eatWhile(match) {
const start = this.pos;
while (!this.eof() && this.eat(match)) { /* */ }
return this.pos !== start;
}
/**
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
*/
backUp(n) {
this.pos -= (n || 1);
}
/**
* Get the string between the start of the current token and the
* current stream position.
*/
current() {
return this.substring(this.start, this.pos);
}
/**
* Returns substring for given range
*/
substring(start, end) {
return this.string.slice(start, end);
}
/**
* Creates error object with current stream state
*/
error(message, pos = this.pos) {
return new ScannerError(`${message} at ${pos + 1}`, pos, this.string);
}
}
class ScannerError extends Error {
constructor(message, pos, str) {
super(message);
this.pos = pos;
this.string = str;
}
}
export { ScannerError, Scanner as default, eatPair, eatQuoted, isAlpha, isAlphaNumeric, isAlphaNumericWord, isAlphaWord, isNumber, isQuote, isSpace, isUmlaut, isWhiteSpace };
//# sourceMappingURL=scanner.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,53 +0,0 @@
import type Scanner from './scanner.js';
interface QuotedOptions {
/** A character code of quote-escape symbol */
escape?: number;
/** Throw error if quotes string cant be properly consumed */
throws?: boolean;
}
/**
* Check if given code is a number
*/
export declare function isNumber(code: number): boolean;
/**
* Check if given character code is alpha code (letter through A to Z)
*/
export declare function isAlpha(code: number, from?: number, to?: number): boolean;
/**
* Check if given character code is alpha-numeric (letter through A to Z or number)
*/
export declare function isAlphaNumeric(code: number): boolean;
export declare function isAlphaNumericWord(code: number): boolean;
export declare function isAlphaWord(code: number): boolean;
/**
* Check for Umlauts i.e. ä, Ä, ö, Ö, ü and Ü
*/
export declare function isUmlaut(code: number): boolean;
/**
* Check if given character code is a white-space character: a space character
* or line breaks
*/
export declare function isWhiteSpace(code: number): boolean;
/**
* Check if given character code is a space character
*/
export declare function isSpace(code: number): boolean;
/**
* Consumes 'single' or "double"-quoted string from given string, if possible
* @return `true` if quoted string was consumed. The contents of quoted string
* will be available as `stream.current()`
*/
export declare function eatQuoted(stream: Scanner, options?: QuotedOptions): boolean;
/**
* Check if given character code is a quote character
*/
export declare function isQuote(code: number): boolean;
/**
* Eats paired characters substring, for example `(foo)` or `[bar]`
* @param open Character code of pair opening
* @param close Character code of pair closing
* @return Returns `true` if character pair was successfully consumed, its
* content will be available as `stream.current()`
*/
export declare function eatPair(stream: Scanner, open: number, close: number, options?: QuotedOptions): boolean;
export {};

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 Emmet.io
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,167 +0,0 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
/**
* Methods for consuming quoted values
*/
const SINGLE_QUOTE = 39; // '
const DOUBLE_QUOTE = 34; // "
const defaultOptions = {
escape: 92, // \ character
throws: false
};
/**
* Consumes 'single' or "double"-quoted string from given string, if possible
* @param {StreamReader} stream
* @param {Number} options.escape A character code of quote-escape symbol
* @param {Boolean} options.throws Throw error if quotes string cant be properly consumed
* @return {Boolean} `true` if quoted string was consumed. The contents
* of quoted string will be availabe as `stream.current()`
*/
var eatQuoted = function(stream, options) {
options = options ? Object.assign({}, defaultOptions, options) : defaultOptions;
const start = stream.pos;
const quote = stream.peek();
if (stream.eat(isQuote)) {
while (!stream.eof()) {
switch (stream.next()) {
case quote:
stream.start = start;
return true;
case options.escape:
stream.next();
break;
}
}
// If were here then stream wasnt properly consumed.
// Revert stream and decide what to do
stream.pos = start;
if (options.throws) {
throw stream.error('Unable to consume quoted string');
}
}
return false;
};
function isQuote(code) {
return code === SINGLE_QUOTE || code === DOUBLE_QUOTE;
}
/**
* Check if given code is a number
* @param {Number} code
* @return {Boolean}
*/
function isNumber(code) {
return code > 47 && code < 58;
}
/**
* Check if given character code is alpha code (letter through A to Z)
* @param {Number} code
* @param {Number} [from]
* @param {Number} [to]
* @return {Boolean}
*/
function isAlpha(code, from, to) {
from = from || 65; // A
to = to || 90; // Z
code &= ~32; // quick hack to convert any char code to uppercase char code
return code >= from && code <= to;
}
/**
* Check if given character code is alpha-numeric (letter through A to Z or number)
* @param {Number} code
* @return {Boolean}
*/
function isAlphaNumeric(code) {
return isNumber(code) || isAlpha(code);
}
function isWhiteSpace(code) {
return code === 32 /* space */
|| code === 9 /* tab */
|| code === 160; /* non-breaking space */
}
/**
* Check if given character code is a space
* @param {Number} code
* @return {Boolean}
*/
function isSpace(code) {
return isWhiteSpace(code)
|| code === 10 /* LF */
|| code === 13; /* CR */
}
const defaultOptions$1 = {
escape: 92, // \ character
throws: false
};
/**
* Eats paired characters substring, for example `(foo)` or `[bar]`
* @param {StreamReader} stream
* @param {Number} open Character code of pair openinig
* @param {Number} close Character code of pair closing
* @param {Object} [options]
* @return {Boolean} Returns `true` if chacarter pair was successfully
* consumed, its content will be available as `stream.current()`
*/
function eatPair(stream, open, close, options) {
options = options ? Object.assign({}, defaultOptions$1, options) : defaultOptions$1;
const start = stream.pos;
if (stream.eat(open)) {
let stack = 1, ch;
while (!stream.eof()) {
if (eatQuoted(stream, options)) {
continue;
}
ch = stream.next();
if (ch === open) {
stack++;
} else if (ch === close) {
stack--;
if (!stack) {
stream.start = start;
return true;
}
} else if (ch === options.escape) {
stream.next();
}
}
// If were here then paired character cant be consumed
stream.pos = start;
if (options.throws) {
throw stream.error(`Unable to find matching pair for ${String.fromCharCode(open)}`);
}
}
return false;
}
exports.eatQuoted = eatQuoted;
exports.isQuote = isQuote;
exports.isAlpha = isAlpha;
exports.isNumber = isNumber;
exports.isAlphaNumeric = isAlphaNumeric;
exports.isSpace = isSpace;
exports.isWhiteSpace = isWhiteSpace;
exports.eatPair = eatPair;

View File

@@ -1,156 +0,0 @@
/**
* Methods for consuming quoted values
*/
const SINGLE_QUOTE = 39; // '
const DOUBLE_QUOTE = 34; // "
const defaultOptions = {
escape: 92, // \ character
throws: false
};
/**
* Consumes 'single' or "double"-quoted string from given string, if possible
* @param {StreamReader} stream
* @param {Number} options.escape A character code of quote-escape symbol
* @param {Boolean} options.throws Throw error if quotes string cant be properly consumed
* @return {Boolean} `true` if quoted string was consumed. The contents
* of quoted string will be availabe as `stream.current()`
*/
var eatQuoted = function(stream, options) {
options = options ? Object.assign({}, defaultOptions, options) : defaultOptions;
const start = stream.pos;
const quote = stream.peek();
if (stream.eat(isQuote)) {
while (!stream.eof()) {
switch (stream.next()) {
case quote:
stream.start = start;
return true;
case options.escape:
stream.next();
break;
}
}
// If were here then stream wasnt properly consumed.
// Revert stream and decide what to do
stream.pos = start;
if (options.throws) {
throw stream.error('Unable to consume quoted string');
}
}
return false;
};
function isQuote(code) {
return code === SINGLE_QUOTE || code === DOUBLE_QUOTE;
}
/**
* Check if given code is a number
* @param {Number} code
* @return {Boolean}
*/
function isNumber(code) {
return code > 47 && code < 58;
}
/**
* Check if given character code is alpha code (letter through A to Z)
* @param {Number} code
* @param {Number} [from]
* @param {Number} [to]
* @return {Boolean}
*/
function isAlpha(code, from, to) {
from = from || 65; // A
to = to || 90; // Z
code &= ~32; // quick hack to convert any char code to uppercase char code
return code >= from && code <= to;
}
/**
* Check if given character code is alpha-numeric (letter through A to Z or number)
* @param {Number} code
* @return {Boolean}
*/
function isAlphaNumeric(code) {
return isNumber(code) || isAlpha(code);
}
function isWhiteSpace(code) {
return code === 32 /* space */
|| code === 9 /* tab */
|| code === 160; /* non-breaking space */
}
/**
* Check if given character code is a space
* @param {Number} code
* @return {Boolean}
*/
function isSpace(code) {
return isWhiteSpace(code)
|| code === 10 /* LF */
|| code === 13; /* CR */
}
const defaultOptions$1 = {
escape: 92, // \ character
throws: false
};
/**
* Eats paired characters substring, for example `(foo)` or `[bar]`
* @param {StreamReader} stream
* @param {Number} open Character code of pair openinig
* @param {Number} close Character code of pair closing
* @param {Object} [options]
* @return {Boolean} Returns `true` if chacarter pair was successfully
* consumed, its content will be available as `stream.current()`
*/
function eatPair(stream, open, close, options) {
options = options ? Object.assign({}, defaultOptions$1, options) : defaultOptions$1;
const start = stream.pos;
if (stream.eat(open)) {
let stack = 1, ch;
while (!stream.eof()) {
if (eatQuoted(stream, options)) {
continue;
}
ch = stream.next();
if (ch === open) {
stack++;
} else if (ch === close) {
stack--;
if (!stack) {
stream.start = start;
return true;
}
} else if (ch === options.escape) {
stream.next();
}
}
// If were here then paired character cant be consumed
stream.pos = start;
if (options.throws) {
throw stream.error(`Unable to find matching pair for ${String.fromCharCode(open)}`);
}
}
return false;
}
export { eatQuoted, isQuote, isAlpha, isNumber, isAlphaNumeric, isSpace, isWhiteSpace, eatPair };

View File

@@ -1,34 +0,0 @@
{
"name": "@emmetio/stream-reader-utils",
"version": "0.1.0",
"description": "",
"main": "dist/stream-reader-utils.cjs.js",
"module": "dist/stream-reader-utils.es.js",
"directories": {
"test": "test"
},
"devDependencies": {
"@emmetio/stream-reader": "^2.0.0",
"babel-plugin-transform-es2015-modules-commonjs": "^6.24.0",
"babel-register": "^6.24.0",
"mocha": "^3.2.0",
"rollup": "^0.41.6",
"rollup-watch": "^3.2.2"
},
"scripts": {
"test": "mocha",
"build": "rollup -c",
"watch": "rollup -wc"
},
"repository": {
"type": "git",
"url": "git+https://github.com/emmetio/stream-reader-utils.git"
},
"keywords": [],
"author": "Sergey Chikuyonok <serge.che@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/emmetio/stream-reader-utils/issues"
},
"homepage": "https://github.com/emmetio/stream-reader-utils#readme"
}

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 Emmet.io
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,130 +0,0 @@
'use strict';
/**
* A streaming, character code-based string reader
*/
class StreamReader {
constructor(string, start, end) {
if (end == null && typeof string === 'string') {
end = string.length;
}
this.string = string;
this.pos = this.start = start || 0;
this.end = end;
}
/**
* Returns true only if the stream is at the end of the file.
* @returns {Boolean}
*/
eof() {
return this.pos >= this.end;
}
/**
* Creates a new stream instance which is limited to given `start` and `end`
* range. E.g. its `eof()` method will look at `end` property, not actual
* stream end
* @param {Point} start
* @param {Point} end
* @return {StreamReader}
*/
limit(start, end) {
return new this.constructor(this.string, start, end);
}
/**
* Returns the next character code in the stream without advancing it.
* Will return NaN at the end of the file.
* @returns {Number}
*/
peek() {
return this.string.charCodeAt(this.pos);
}
/**
* Returns the next character in the stream and advances it.
* Also returns <code>undefined</code> when no more characters are available.
* @returns {Number}
*/
next() {
if (this.pos < this.string.length) {
return this.string.charCodeAt(this.pos++);
}
}
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
* the given argument, it is consumed and returned.
* Otherwise, `false` is returned.
* @param {Number|Function} match
* @returns {Boolean}
*/
eat(match) {
const ch = this.peek();
const ok = typeof match === 'function' ? match(ch) : ch === match;
if (ok) {
this.next();
}
return ok;
}
/**
* Repeatedly calls <code>eat</code> with the given argument, until it
* fails. Returns <code>true</code> if any characters were eaten.
* @param {Object} match
* @returns {Boolean}
*/
eatWhile(match) {
const start = this.pos;
while (!this.eof() && this.eat(match)) {}
return this.pos !== start;
}
/**
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
* @param {Number} n
*/
backUp(n) {
this.pos -= (n || 1);
}
/**
* Get the string between the start of the current token and the
* current stream position.
* @returns {String}
*/
current() {
return this.substring(this.start, this.pos);
}
/**
* Returns substring for given range
* @param {Number} start
* @param {Number} [end]
* @return {String}
*/
substring(start, end) {
return this.string.slice(start, end);
}
/**
* Creates error object with current stream state
* @param {String} message
* @return {Error}
*/
error(message) {
const err = new Error(`${message} at char ${this.pos + 1}`);
err.originalMessage = message;
err.pos = this.pos;
err.string = this.string;
return err;
}
}
module.exports = StreamReader;

View File

@@ -1,128 +0,0 @@
/**
* A streaming, character code-based string reader
*/
class StreamReader {
constructor(string, start, end) {
if (end == null && typeof string === 'string') {
end = string.length;
}
this.string = string;
this.pos = this.start = start || 0;
this.end = end;
}
/**
* Returns true only if the stream is at the end of the file.
* @returns {Boolean}
*/
eof() {
return this.pos >= this.end;
}
/**
* Creates a new stream instance which is limited to given `start` and `end`
* range. E.g. its `eof()` method will look at `end` property, not actual
* stream end
* @param {Point} start
* @param {Point} end
* @return {StreamReader}
*/
limit(start, end) {
return new this.constructor(this.string, start, end);
}
/**
* Returns the next character code in the stream without advancing it.
* Will return NaN at the end of the file.
* @returns {Number}
*/
peek() {
return this.string.charCodeAt(this.pos);
}
/**
* Returns the next character in the stream and advances it.
* Also returns <code>undefined</code> when no more characters are available.
* @returns {Number}
*/
next() {
if (this.pos < this.string.length) {
return this.string.charCodeAt(this.pos++);
}
}
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
* the given argument, it is consumed and returned.
* Otherwise, `false` is returned.
* @param {Number|Function} match
* @returns {Boolean}
*/
eat(match) {
const ch = this.peek();
const ok = typeof match === 'function' ? match(ch) : ch === match;
if (ok) {
this.next();
}
return ok;
}
/**
* Repeatedly calls <code>eat</code> with the given argument, until it
* fails. Returns <code>true</code> if any characters were eaten.
* @param {Object} match
* @returns {Boolean}
*/
eatWhile(match) {
const start = this.pos;
while (!this.eof() && this.eat(match)) {}
return this.pos !== start;
}
/**
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
* @param {Number} n
*/
backUp(n) {
this.pos -= (n || 1);
}
/**
* Get the string between the start of the current token and the
* current stream position.
* @returns {String}
*/
current() {
return this.substring(this.start, this.pos);
}
/**
* Returns substring for given range
* @param {Number} start
* @param {Number} [end]
* @return {String}
*/
substring(start, end) {
return this.string.slice(start, end);
}
/**
* Creates error object with current stream state
* @param {String} message
* @return {Error}
*/
error(message) {
const err = new Error(`${message} at char ${this.pos + 1}`);
err.originalMessage = message;
err.pos = this.pos;
err.string = this.string;
return err;
}
}
export default StreamReader;

View File

@@ -1,33 +0,0 @@
{
"name": "@emmetio/stream-reader",
"version": "2.2.0",
"description": "Reads text as stream",
"main": "dist/stream-reader.cjs.js",
"module": "dist/stream-reader.es.js",
"scripts": {
"test": "mocha",
"build": "rollup -c",
"prepublish": "npm run test && npm run build"
},
"repository": {
"type": "git",
"url": "git+https://github.com/emmetio/stream-reader.git"
},
"keywords": [
"emmet",
"stream",
"reader"
],
"author": "Sergey Chikuyonok <serge.che@gmail.com>",
"license": "MIT",
"bugs": {
"url": "https://github.com/emmetio/stream-reader/issues"
},
"homepage": "https://github.com/emmetio/stream-reader#readme",
"devDependencies": {
"babel-plugin-transform-es2015-modules-commonjs": "^6.18.0",
"babel-register": "^6.18.0",
"mocha": "^3.2.0",
"rollup": "^0.41.1"
}
}