css-fruit
Advanced tools
Comparing version 0.1.2 to 0.1.3
{ | ||
"name": "css-fruit", | ||
"description": "A Parser and Analysis of CSS Declaration", | ||
"version": "0.1.2", | ||
"version": "0.1.3", | ||
"author": "Rainfore <rainforest92@126.com>", | ||
@@ -13,3 +13,4 @@ "scripts": { | ||
"test:one": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\"}' mocha --require ts-node/register", | ||
"test:integration": "mocha test/integration/css-fruit.js" | ||
"test:integration": "mocha test/integration/css-fruit.js", | ||
"docs": "typedoc --out docs ./src" | ||
}, | ||
@@ -50,2 +51,3 @@ "main": "./dist/css-fruit.js", | ||
"ts-node": "^7.0.1", | ||
"typedoc": "^0.14.1", | ||
"typescript": "^3.2.1", | ||
@@ -52,0 +54,0 @@ "vusion-hooks": "^0.2.1", |
@@ -171,2 +171,18 @@ # css-fruit | ||
### 设计要点 | ||
- 每个节点类均继承`Fruit` | ||
- 构造函数要实现以下内容: | ||
- constructor() // 生成空对象,默认 invalid | ||
- constructor(value: string) // 直接使用 this.parse 去解析 | ||
- constructor(...args) // 需要单独对每个参数解析 | ||
- constructor({}) ? 这种要不要实现得打个问号了。。。 | ||
- _absorb() 单独对每种属性做解析 | ||
- analyzeInLoop 中的流程控制 | ||
- return true; // 继续下一个 token | ||
- return false; // 停留在当前 token,然后继续循环 | ||
- return undefined; // 结束当前循环,一般是遇到当前解析器不合法的字符,但可以让后续解析器继续处理 | ||
- throw Error; // 当前解析错误,直接停止解析 | ||
- 像`BackgroundPosition`这样的最长 hand 值,它的属性要么全没有,要么全都有 | ||
## 修改日志 | ||
@@ -173,0 +189,0 @@ |
@@ -1,2 +0,2 @@ | ||
import Fruit, { ValueNode, ValueNodeType, ParseDeepLevel, Stem } from '../Fruit'; | ||
import Fruit, { ValueNode, ValueNodeType, ParsedDepth, Stem } from '../Fruit'; | ||
const ValueParser = require('postcss-value-parser'); | ||
@@ -19,4 +19,2 @@ | ||
export default class Color extends Fruit { | ||
protected _type: string = 'color'; | ||
protected _parseDeepLevelBoundary = ParseDeepLevel.dataTypes; | ||
value: string; | ||
@@ -33,2 +31,6 @@ r: number; | ||
super(); | ||
this._type = 'color'; | ||
this._parseDepth = ParsedDepth.dataType; | ||
this.init(); | ||
this.r = r; | ||
@@ -62,3 +64,3 @@ this.g = g; | ||
if (this.value) | ||
throw new SyntaxError('Excessive value'); | ||
throw new SyntaxError(`Excessive value '${node.value}'`); | ||
this.value = 'currentColor'; | ||
@@ -68,3 +70,3 @@ return this.valid = true; | ||
if (this.value) | ||
throw new SyntaxError('Excessive value'); | ||
throw new SyntaxError(`Excessive value '${node.value}'`); | ||
this.value = node.value; | ||
@@ -75,3 +77,3 @@ // @TODO parse named value; | ||
if (this.value) | ||
throw new SyntaxError('Excessive value'); | ||
throw new SyntaxError(`Excessive value '${node.value}'`); | ||
this.value = node.value; | ||
@@ -83,6 +85,6 @@ // @TODO parse named value; | ||
if (node.unclosed) | ||
throw new SyntaxError('Unclosed function: ' + node.value); | ||
throw new SyntaxError(`Unclosed function '${node.value}'`); | ||
if (node.value === 'rgb' || node.value === 'rgba' || node.value === 'hsl' || node.value === 'hsla') { | ||
if (this.value) | ||
throw new SyntaxError('Excessive value'); | ||
throw new SyntaxError(`Excessive value '${node.value}'`); | ||
this.value = ValueParser.stringify(node); | ||
@@ -162,3 +164,3 @@ return this.valid = true; | ||
if (value.length !== 6 && value.length !== 3) | ||
throw new SyntaxError('Unexpected length of hex number'); | ||
throw new SyntaxError(`Unexpected length of hex number '${value}'`); | ||
else if (value.length === 3) | ||
@@ -178,3 +180,3 @@ value = `${value[0]}${value[0]}${value[1]}${value[1]}${value[2]}${value[2]}`; | ||
if (arr.length !== 4) | ||
throw new SyntaxError('Unexpected params of rgba function'); | ||
throw new SyntaxError(`Unexpected params of rgba function '${value}'`); | ||
@@ -188,3 +190,3 @@ return new Color(...arr); | ||
if (arr.length !== 4) | ||
throw new SyntaxError('Unexpected params of rgba function'); | ||
throw new SyntaxError(`Unexpected params of rgba function '${value}'`); | ||
@@ -191,0 +193,0 @@ return new Color(...arr); |
@@ -1,19 +0,32 @@ | ||
import Fruit, { ValueNode, ValueNodeType, ParseDeepLevel, Stem } from '../Fruit'; | ||
import Fruit, { ValueNode, ValueNodeType, ParsedDepth, Stem } from '../Fruit'; | ||
import URL from './URL'; | ||
import ImageSet from './ImageSet'; | ||
export default class Image extends Fruit { | ||
protected _type: string = 'image'; | ||
protected _parseDeepLevelBoundary: ParseDeepLevel = ParseDeepLevel.dataTypes; | ||
protected _state: { count: number }; | ||
value: URL | string; | ||
value: URL | ImageSet | string; | ||
// constructor(value?: string); | ||
// constructor(width: string, height?: string) { | ||
// super(width); | ||
// if (arguments.length > 1) { | ||
// this.width = width; | ||
// this.height = height; | ||
// } | ||
// } | ||
constructor(); | ||
constructor(value: string | URL | ImageSet); | ||
constructor(value?: string | URL | ImageSet) { | ||
super(); | ||
this._type = 'image'; | ||
this._parseDepth = ParsedDepth.dataType; | ||
this.init(); | ||
const args = arguments; | ||
this.tryCatch(() => { | ||
if (args.length === 0) | ||
return; | ||
else if (typeof value === 'string') | ||
this.parse(value); | ||
else if (value instanceof URL || value instanceof ImageSet) { | ||
// @矛盾: 赋值给`this.value`时,应不应该检查 URL 本身的合法性? | ||
this.value = value.toResult() as URL | ImageSet | string; | ||
this.valid = value.valid; | ||
} else | ||
throw new TypeError('Wrong type or excessive arguments'); | ||
}); | ||
} | ||
protected init() { | ||
@@ -39,13 +52,23 @@ super.init(); | ||
if (node.unclosed) | ||
throw new SyntaxError('Unclosed function: ' + node.value); | ||
throw new SyntaxError(`Unclosed function '${node.value}'`); | ||
if (node.value === 'url') { | ||
if (this.value) | ||
throw new SyntaxError('Excessive values'); | ||
throw new SyntaxError(`Excessive value '${node.value}'`); | ||
const url = new URL(); | ||
url.analyze(stem); | ||
if (!url.valid) | ||
throw new SyntaxError('Invalid url: ' + node.value); | ||
throw new SyntaxError(`Invalid <url> '${node.value}'`); | ||
this.value = url.toResult() as URL | string; | ||
this.valid = true; | ||
return false; | ||
} else if (node.value === 'image-set' || node.value === '-webkit-image-set') { | ||
if (this.value) | ||
throw new SyntaxError(`Excessive value '${node.value}'`); | ||
const imageSet = new ImageSet(); | ||
imageSet.analyze(stem); | ||
if (!imageSet.valid) | ||
throw new SyntaxError(`Invalid <image-set> '${node.value}'`); | ||
this.value = imageSet.toResult() as ImageSet | string; | ||
this.valid = true; | ||
return false; | ||
} // else | ||
@@ -52,0 +75,0 @@ // cont gradient = new Gradient(); |
@@ -1,13 +0,43 @@ | ||
import Fruit, { ValueNode, ValueNodeType, ParseDeepLevel } from '../Fruit'; | ||
import Fruit, { ValueNode, ValueNodeType, ParsedDepth } from '../Fruit'; | ||
import { numberRE } from './Number'; | ||
const experimentalRE = new RegExp(`^(${String(numberRE).slice(2, -3)})(cap|ch|em|ex|ic|lh|rem|rlh|vh|vw|vi|vb|vmin|vmax|px|cm|mm|Q|in|pc|pt)?$`, 'i'); | ||
const partialRE = new RegExp(`^(${String(numberRE).slice(2, -3)})(ch|em|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt)?$`, 'i'); | ||
const unitRE = /^ch|em|ex|rem|vh|vw|vmin|vmax|px|cm|mm|in|pc|pt$/i; | ||
const experimentalUnitRE = /^cap|ch|em|ex|ic|lh|rem|rlh|vh|vw|vi|vb|vmin|vmax|px|cm|mm|Q|in|pc|pt$/i; | ||
const partialRE = new RegExp(`^(${String(numberRE).slice(2, -3)})(${String(unitRE).slice(2, -3)})?$`, 'i'); | ||
export default class Length extends Fruit { | ||
protected _type: string = 'length'; | ||
protected _parseDeepLevelBoundary = ParseDeepLevel.dataTypes; | ||
number: number; | ||
unit: string; | ||
constructor(); | ||
constructor(value: string | number); | ||
constructor(number: number, unit: string); | ||
constructor(value?: string | number, unit?: string) { | ||
super(); | ||
this._type = 'length'; | ||
this._parseDepth = ParsedDepth.dataType; | ||
this.init(); | ||
const args = arguments; | ||
this.tryCatch(() => { | ||
if (args.length === 0) | ||
return; | ||
else if (typeof value === 'string' && args.length === 1) | ||
this.parse(value); | ||
else if (typeof value === 'number') { | ||
if (!unit && value === 0) { | ||
this.number = value; | ||
this.unit = ''; | ||
this.valid = true; | ||
} else if (unit && unitRE.test(unit)) { | ||
this.number = value; | ||
this.unit = unit; | ||
this.valid = true; | ||
} else | ||
throw new SyntaxError(`Invalid unit '${unit}'`); | ||
} else | ||
throw new TypeError('Wrong type or excessive arguments'); | ||
}); | ||
} | ||
init() { | ||
@@ -21,13 +51,14 @@ super.init(); | ||
value = value.trim(); | ||
this.init(); | ||
const found = partialRE.exec(value); | ||
if (!found) | ||
throw new SyntaxError('Invalid length'); | ||
if (+found[1] !== 0 && !found[2]) | ||
throw new SyntaxError('A unit should be after the non-zero number'); | ||
this.tryCatch(() => { | ||
const found = partialRE.exec(value); | ||
if (!found) | ||
throw new SyntaxError(`Invalid length '${value}'`); | ||
if (+found[1] !== 0 && !found[2]) | ||
throw new SyntaxError('There must be a unit after the non-zero number'); | ||
this.number = +found[1]; | ||
this.unit = found[2] || ''; | ||
this.valid = true; | ||
this.number = +found[1]; | ||
this.unit = found[2] || ''; | ||
this.valid = true; | ||
}); | ||
@@ -34,0 +65,0 @@ return this.toResult(); |
@@ -1,2 +0,2 @@ | ||
import Fruit, { ValueNode, ValueNodeType, ParseDeepLevel } from '../Fruit'; | ||
import Fruit, { ValueNode, ValueNodeType, ParsedDepth } from '../Fruit'; | ||
import { numberRE } from './Number'; | ||
@@ -7,6 +7,26 @@ | ||
export default class Percentage extends Fruit { | ||
protected _type: string = 'percentage'; | ||
protected _parseDeepLevelBoundary = ParseDeepLevel.dataTypes; | ||
number: number; | ||
constructor(); | ||
constructor(value: string | number); | ||
constructor(value?: string | number) { | ||
super(); | ||
this._type = 'percentage'; | ||
this._parseDepth = ParsedDepth.dataType; | ||
this.init(); | ||
const args = arguments; | ||
this.tryCatch(() => { | ||
if (args.length === 0) | ||
return; | ||
else if (typeof value === 'string') | ||
this.parse(value); | ||
else if (typeof value === 'number') { | ||
this.number = value; | ||
this.valid = true; | ||
} else | ||
throw new TypeError('Wrong type or excessive arguments'); | ||
}); | ||
} | ||
init() { | ||
@@ -19,12 +39,13 @@ super.init(); | ||
value = value.trim(); | ||
this.init(); | ||
const found = partialRE.exec(value); | ||
if (!found) | ||
throw new SyntaxError('Invalid percentage'); | ||
// if (+found[1] !== 0 && !found[2]) | ||
// throw new SyntaxError('"%" should be after the non-zero number'); | ||
this.tryCatch(() => { | ||
const found = partialRE.exec(value); | ||
if (!found) | ||
throw new SyntaxError(`Invalid percentage format of '${value}'`); | ||
// if (+found[1] !== 0 && !found[2]) | ||
// throw new SyntaxError('"%" must be after the non-zero number'); | ||
this.number = +found[1]; | ||
this.valid = true; | ||
this.number = +found[1]; | ||
this.valid = true; | ||
}); | ||
@@ -31,0 +52,0 @@ return this.toResult(); |
@@ -1,2 +0,2 @@ | ||
import Fruit, { ValueNode, ValueNodeType, ParseDeepLevel } from '../Fruit'; | ||
import Fruit, { ValueNode, ValueNodeType, ParsedDepth } from '../Fruit'; | ||
import { parseQuery, stringifyQuery, Query } from '../utils'; | ||
@@ -18,4 +18,2 @@ | ||
export default class URL extends Fruit { | ||
protected _type: string = 'url'; | ||
protected _parseDeepLevelBoundary: ParseDeepLevel = ParseDeepLevel.dataTypes; | ||
// quote: string; | ||
@@ -27,2 +25,21 @@ url: string; | ||
constructor(); | ||
constructor(value: string); | ||
constructor(value?: string) { | ||
super(); | ||
this._type = 'url'; | ||
this._parseDepth = ParsedDepth.dataType; | ||
this.init(); | ||
const args = arguments; | ||
this.tryCatch(() => { | ||
if (args.length === 0) | ||
return; | ||
else if (args.length === 1) | ||
this.parse(value); | ||
else | ||
throw new TypeError('Wrong type or excessive arguments'); | ||
}) | ||
} | ||
protected init() { | ||
@@ -42,14 +59,14 @@ super.init(); | ||
if (node.unclosed) | ||
throw new SyntaxError('Unclosed function: ' + node.value); | ||
throw new SyntaxError(`Unclosed function '${node.value}'`); | ||
if (node.value === 'url') { | ||
if (this.url) | ||
throw new SyntaxError('Duplicated url functions'); | ||
throw new SyntaxError(`Duplicated function 'url'`); | ||
let url = ''; | ||
if (node.nodes.length > 1) | ||
throw new SyntaxError('Invalid url'); | ||
throw new SyntaxError('Invalid url format'); | ||
else if (node.nodes.length === 1) { | ||
const subNode = node.nodes[0]; | ||
if (subNode.unclosed) | ||
throw new SyntaxError('Unclosed quote: ' + subNode.value); | ||
throw new SyntaxError(`Unclosed quote '${subNode.value}'`); | ||
else | ||
@@ -61,6 +78,8 @@ url = subNode.value; | ||
const found = urlRE.exec(url); | ||
this.path = found[1] ? decodeURIComponent(found[1]) : ''; | ||
this.path = found[1] || ''; | ||
// this.path = found[1] ? decodeURIComponent(found[1]) : ''; | ||
if (found[2]) | ||
this.query = parseQuery(found[2]); | ||
this.hash = found[3] ? decodeURIComponent(found[3].slice(1)) : ''; | ||
this.hash = found[3] ? found[3].slice(1) : ''; | ||
// this.hash = found[3] ? decodeURIComponent(found[3].slice(1)) : ''; | ||
return this.valid = true; | ||
@@ -77,4 +96,5 @@ } | ||
const queryString = this.query ? stringifyQuery(this.query) : ''; | ||
return `url(${quote}${encodeURIComponent(this.path)}${queryString}${this.hash ? '#' + encodeURIComponent(this.hash) : ''}${quote})`; | ||
// return `url(${quote}${encodeURIComponent(this.path)}${queryString}${this.hash ? '#' + encodeURIComponent(this.hash) : ''}${quote})`; | ||
return `url(${quote}${this.path}${queryString}${this.hash ? '#' + this.hash : ''}${quote})`; | ||
} | ||
} |
@@ -31,4 +31,7 @@ const ValueParser = require('postcss-value-parser'); | ||
pos: number; | ||
constructor(value: string) { | ||
this.nodes = new ValueParser(value).nodes; | ||
constructor(value: string | Array<ValueNode>) { | ||
if (Array.isArray(value)) | ||
this.nodes = value; | ||
else | ||
this.nodes = new ValueParser(value).nodes; | ||
this.pos = 0; | ||
@@ -58,3 +61,16 @@ } | ||
export const enum ParseDeepLevel { | ||
/* @example: | ||
background === '20px top'; // Keep shorthand | ||
background.position === '20px top'; // Keep primaryLonghand | completeLonghand | ||
background.position.x === '20px'; // Keep virtualLonghand | ||
background.position.x.offset === '20px'; // Keep dataType | ||
background.position.x.offset.number = 20; // Keep dataTypeProperty | ||
border = '2px solid color'; // Keep shorthand | ||
border.left = '20px solid color'; // Keep primaryLonghand | ||
border.left.width = '20px'; // Keep completeLonghand | virtualLonghand | dataType | ||
border.left.width.number = 20; // Keep dataTypeProperty | ||
*/ | ||
export const enum ParsedDepth { | ||
shorthand, | ||
@@ -64,3 +80,4 @@ primaryLonghand, | ||
virtualLonghand, | ||
dataTypes, | ||
dataType, | ||
dataTypeProperty, | ||
} | ||
@@ -84,4 +101,5 @@ | ||
irrelevantProperty: IrrelevantProperty; | ||
parseDeepLevel: ParseDeepLevel; | ||
depthParseTo: ParsedDepth; | ||
forceParsing: { [prop: string]: boolean }; | ||
throwErrors: boolean; | ||
} | ||
@@ -93,3 +111,3 @@ | ||
protected _inherited: boolean = false; | ||
protected _parseDeepLevelBoundary = ParseDeepLevel.virtualLonghand; | ||
protected _parseDepth = ParsedDepth.virtualLonghand; | ||
raw: string; | ||
@@ -99,8 +117,21 @@ valid: boolean = false; | ||
constructor(); | ||
constructor(value?: string); | ||
constructor(value?: string) { | ||
if (arguments.length === 1 && value) | ||
this.parse(value); | ||
constructor(...args: any[]); | ||
constructor(...args: any[]) { | ||
if (args.length === 0) | ||
return; | ||
if (args.length === 1) | ||
this.parse(args[0]); | ||
else | ||
this.parse(args.join(' ')); | ||
} | ||
protected tryCatch(func: Function): void { | ||
try { | ||
func(); | ||
} catch (e) { | ||
if (this.options.throwErrors) | ||
throw e; | ||
} | ||
} | ||
protected init(): void { | ||
@@ -115,7 +146,9 @@ this.valid = false; | ||
const stem = new Stem(value); | ||
this.analyze(stem); | ||
if (stem.head()) { | ||
this.valid = false; | ||
throw SyntaxError('Nodes of value cannot be fully analyzed: ' + value); | ||
} | ||
this.tryCatch(() => { | ||
this.analyze(stem); | ||
if (stem.head()) { | ||
this.valid = false; | ||
throw SyntaxError('Nodes of value cannot be fully analyzed: ' + value); | ||
} | ||
}); | ||
return this.toResult(); | ||
@@ -127,3 +160,3 @@ } | ||
return undefined; | ||
if (this.options.parseDeepLevel >= this._parseDeepLevelBoundary || this.options.forceParsing[this._type]) | ||
if (this.options.depthParseTo > this._parseDepth || this.options.forceParsing[this._type]) | ||
return this; | ||
@@ -136,3 +169,2 @@ else | ||
let node; | ||
this.init(); | ||
while (node = stem.head()) { | ||
@@ -144,3 +176,3 @@ let control: boolean; | ||
this.valid = false; | ||
throw e; | ||
throw new Error(`When analyzing <${this._type}>\n\t` + e); | ||
} | ||
@@ -201,6 +233,4 @@ | ||
static parse(value: string): Fruit | string { | ||
try { | ||
const fruit = new this(); | ||
return fruit.parse(value); | ||
} catch (e) {} | ||
const fruit = new this(); | ||
return fruit.parse(value); | ||
} | ||
@@ -211,3 +241,7 @@ | ||
static validate(value: string): boolean { | ||
return this.parse(value) !== undefined; | ||
try { | ||
return this.parse(value) !== undefined; | ||
} catch (e) { | ||
return false; | ||
} | ||
} | ||
@@ -228,4 +262,5 @@ | ||
irrelevantProperty: IrrelevantProperty.ignore, | ||
parseDeepLevel: ParseDeepLevel.virtualLonghand, | ||
depthParseTo: ParsedDepth.dataType, | ||
forceParsing: {}, | ||
throwErrors: false, | ||
}; |
@@ -1,2 +0,2 @@ | ||
import Fruit, { ValueNode, ValueNodeType, Stem } from '../Fruit'; | ||
import Fruit, { ValueNode, ValueNodeType, Stem, ParsedDepth } from '../Fruit'; | ||
import Color from '../dataTypes/Color'; | ||
@@ -26,4 +26,2 @@ import Image from '../dataTypes/Image'; | ||
export default class Background extends Fruit { | ||
protected _type: string = 'background'; | ||
protected _inherited: boolean = false; | ||
protected _state: { boxCount: number }; | ||
@@ -39,2 +37,21 @@ attachment: string; | ||
constructor(); | ||
constructor(value: string); | ||
constructor(value?: string) { | ||
super(); | ||
this._type = 'background'; | ||
this._parseDepth = ParsedDepth.shorthand; | ||
this.init(); | ||
const args = arguments; | ||
this.tryCatch(() => { | ||
if (args.length === 0) | ||
return; | ||
else if (args.length === 1) | ||
this.parse(value); | ||
else | ||
throw new TypeError('Wrong type or excessive arguments'); | ||
}) | ||
} | ||
protected init() { | ||
@@ -61,3 +78,3 @@ super.init(); | ||
if (this.attachment) | ||
throw new SyntaxError('Excessive background-attachment'); | ||
throw new SyntaxError(`Excessive <background-attachment> '${node.value}'`); | ||
this.attachment = node.value; | ||
@@ -75,3 +92,3 @@ return this.valid = true; | ||
} else | ||
throw new SyntaxError('Excessive background-clip') | ||
throw new SyntaxError(`Excessive <background-clip> '${node.value}'`); | ||
} else { | ||
@@ -104,3 +121,3 @@ let valid: boolean = false; | ||
} else | ||
throw new SyntaxError('Invalid background-size'); | ||
throw new SyntaxError(`Invalid <background-size> '${node.value}'`); | ||
} | ||
@@ -122,8 +139,8 @@ } | ||
if (node.unclosed) | ||
throw new SyntaxError('Unclosed function: ' + node.value); | ||
if (node.value === 'url') { | ||
throw new SyntaxError(`Unclosed function '${node.value}'`); | ||
if (node.value === 'url' || node.value === 'image-set' || node.value === '-webkit-image-set') { | ||
const image = new Image(); | ||
image.analyze(stem); | ||
if (!image.valid) | ||
throw new SyntaxError('Invalid image'); | ||
throw new SyntaxError(`Invalid <image> '${node.value}'`); | ||
this.setImage(image.toResult() as Image | string); | ||
@@ -145,3 +162,3 @@ this.valid = true; | ||
if (this.color) | ||
throw new SyntaxError('Excessive color'); | ||
throw new SyntaxError('Excessive <color>'); | ||
else | ||
@@ -153,3 +170,3 @@ this.color = color; | ||
if (this.image) | ||
throw new SyntaxError('Excessive image'); | ||
throw new SyntaxError('Excessive <image>'); | ||
else | ||
@@ -161,3 +178,3 @@ this.image = image; | ||
if (this.position) | ||
throw new SyntaxError('Excessive background-position'); | ||
throw new SyntaxError('Excessive <background-position>'); | ||
else | ||
@@ -169,3 +186,3 @@ this.position = position; | ||
if (this.repeat) | ||
throw new SyntaxError('Excessive background-repeat'); | ||
throw new SyntaxError('Excessive <background-repeat>'); | ||
else | ||
@@ -177,3 +194,3 @@ this.repeat = repeat; | ||
if (this.size) | ||
throw new SyntaxError('Excessive background-size'); | ||
throw new SyntaxError('Excessive <background-size>'); | ||
else | ||
@@ -224,5 +241,5 @@ this.size = size; | ||
} else | ||
throw new Error('Incompatible property: ' + prop); | ||
throw new TypeError(`Property '${prop}' is inconsistent with existing type '${this._type}'`); | ||
return this; | ||
} | ||
} |
@@ -1,2 +0,2 @@ | ||
import Fruit, { ValueNode, ValueNodeType } from '../Fruit'; | ||
import Fruit, { ValueNode, ValueNodeType, Stem } from '../Fruit'; | ||
import Length from '../dataTypes/Length'; | ||
@@ -19,3 +19,2 @@ import Percentage from '../dataTypes/Percentage'; | ||
export default class BackgroundPosition extends Fruit { | ||
protected _type: string = 'background-position'; | ||
protected _state: { count: number, lastType: string }; | ||
@@ -25,2 +24,20 @@ x: BackgroundPositionValue; | ||
constructor(); | ||
constructor(value: string); | ||
constructor(value?: string) { | ||
super(); | ||
this._type = 'background-position'; | ||
this.init(); | ||
const args = arguments; | ||
this.tryCatch(() => { | ||
if (args.length === 0) | ||
return; | ||
else if (args.length === 1) | ||
this.parse(value); | ||
else | ||
throw new TypeError('Wrong type or excessive arguments'); | ||
}); | ||
} | ||
protected init() { | ||
@@ -33,2 +50,14 @@ super.init(); | ||
analyze(stem: Stem): void { | ||
super.analyze(stem); | ||
// Make sure each property has a value. | ||
if (this.valid) { | ||
if (!this.x.offset) | ||
this.x.offset = new Length(0).toResult() as Length | Percentage | string; | ||
if (!this.y.offset) | ||
this.y.offset = new Length(0).toResult() as Length | Percentage | string; | ||
} | ||
} | ||
protected analyzeInLoop(node: ValueNode): boolean { | ||
@@ -43,3 +72,3 @@ if (node.type === ValueNodeType.space || node.type === ValueNodeType.comment) | ||
if (this._state.count >= 2) | ||
throw new SyntaxError('Excessive keyword'); | ||
throw new SyntaxError(`Excessive keyword '${node.value}'`); | ||
else if (this._state.count === 0) { | ||
@@ -64,3 +93,3 @@ this.x.origin = this.y.origin = node.value; | ||
if (this._state.count >= 3) | ||
throw new SyntaxError('Excessive keyword'); | ||
throw new SyntaxError(`Excessive keyword '${node.value}'`); | ||
else if (this._state.count === 0) { | ||
@@ -80,3 +109,3 @@ this.x.origin = node.value; | ||
if (this.x.origin !== BackgroundPositionKeyword.center) | ||
throw new SyntaxError('Duplicated keywords: ' + node.value); | ||
throw new SyntaxError(`Duplicated keyword '${node.value}'`); | ||
this.x.origin = node.value; | ||
@@ -118,7 +147,7 @@ this._state.lastType = 'x'; | ||
} else | ||
throw new SyntaxError('Excessive keyword: ' + node.value); | ||
throw new SyntaxError(`Excessive keyword '${node.value}'`); | ||
} | ||
} else if (node.value === BackgroundPositionKeyword.top || node.value === BackgroundPositionKeyword.bottom) { | ||
if (this._state.count >= 3) | ||
throw new SyntaxError('Excessive keyword'); | ||
throw new SyntaxError(`Excessive keyword '${node.value}'`); | ||
else if (this._state.count === 0) { | ||
@@ -138,3 +167,3 @@ this.y.origin = node.value; | ||
if (this.y.origin !== BackgroundPositionKeyword.center) | ||
throw new SyntaxError('Duplicated keywords: ' + node.value); | ||
throw new SyntaxError(`Duplicated keyword '${node.value}'`); | ||
this.y.origin = node.value; | ||
@@ -176,3 +205,3 @@ this._state.lastType = 'y'; | ||
} else | ||
throw new SyntaxError('Excessive keyword: ' + node.value); | ||
throw new SyntaxError(`Excessive keyword '${node.value}'`); | ||
} | ||
@@ -187,3 +216,3 @@ } else { | ||
if (this._state.count >= 4) | ||
throw new SyntaxError('Excessive <length-percentage> value: ' + lengthPercentage); | ||
throw new SyntaxError(`Excessive <length-percentage> '${lengthPercentage}'`); | ||
else if (this._state.count === 0) { | ||
@@ -245,3 +274,3 @@ this.x.offset = lengthPercentage; | ||
else | ||
throw new Error('xxx'); | ||
throw new Error('Unexpected internal error'); | ||
this._state.lastType = 'length-percentage'; | ||
@@ -251,3 +280,3 @@ this._state.count++; | ||
} else | ||
throw new SyntaxError('Excessive <length-percentage> value: ' + lengthPercentage); | ||
throw new SyntaxError(`Excessive <length-percentage> value '${lengthPercentage}'`); | ||
} else if (this._state.count === 3) { | ||
@@ -263,3 +292,3 @@ /** | ||
if (this._state.lastType === 'length-percentage') | ||
throw new Error('Excessive <length-percentage> value: ' + lengthPercentage); | ||
throw new Error(`Excessive <length-percentage> value '${lengthPercentage}'`); | ||
if (!this.x.offset) | ||
@@ -283,5 +312,5 @@ this.x.offset = lengthPercentage; | ||
if (this.x.offset) | ||
if (this.x.offset.toString() !== '0') | ||
x.push(this.x.offset.toString()); | ||
if (this.y.offset) | ||
if (this.y.offset.toString() !== '0') | ||
y.push(this.y.offset.toString()); | ||
@@ -288,0 +317,0 @@ |
@@ -13,3 +13,2 @@ import Fruit, { ValueNode, ValueNodeType } from '../Fruit'; | ||
export default class BackgroundRepeat extends Fruit { | ||
protected _type: string = 'background-repeat'; | ||
protected _state: { count: number }; | ||
@@ -19,9 +18,23 @@ x: BackgroundRepeatKeyword; | ||
constructor(value?: string); | ||
constructor(x: BackgroundRepeatKeyword, y?: BackgroundRepeatKeyword) { | ||
super(x); | ||
if (arguments.length > 1) { | ||
this.x = x; | ||
this.y = y; | ||
} | ||
constructor(); | ||
constructor(value: string); | ||
constructor(x: BackgroundRepeatKeyword, y: BackgroundRepeatKeyword); | ||
constructor(x?: BackgroundRepeatKeyword | string, y?: BackgroundRepeatKeyword) { | ||
super(); | ||
this._type = 'background-repeat'; | ||
this.init(); | ||
const args = arguments; | ||
this.tryCatch(() => { | ||
if (args.length === 0) | ||
return; | ||
else if (args.length === 1 && typeof x === 'string') | ||
this.parse(x); | ||
else if (args.length === 2) { | ||
this.x = x as BackgroundRepeatKeyword; | ||
this.y = y; | ||
this.valid = true; | ||
} else | ||
throw new TypeError('Wrong type or excessive arguments'); | ||
}) | ||
} | ||
@@ -42,3 +55,3 @@ | ||
if (this._state.count >= 1) | ||
throw new SyntaxError('Excessive keyword'); | ||
throw new SyntaxError(`Excessive keyword '${node.value}'`); | ||
else { | ||
@@ -52,3 +65,3 @@ this.x = BackgroundRepeatKeyword.repeat; | ||
if (this._state.count >= 1) | ||
throw new SyntaxError('Excessive keyword'); | ||
throw new SyntaxError(`Excessive keyword '${node.value}'`); | ||
else { | ||
@@ -62,3 +75,3 @@ this.x = BackgroundRepeatKeyword['no-repeat']; | ||
if (this._state.count >= 2) | ||
throw new SyntaxError('Excessive keyword'); | ||
throw new SyntaxError(`Excessive keyword '${node.value}'`); | ||
else if (this._state.count === 0) { | ||
@@ -73,3 +86,3 @@ this.x = this.y = node.value as BackgroundRepeatKeyword; | ||
} else | ||
throw new Error('State inside problem!'); | ||
throw new Error('Unexpected internal error about _state.count'); | ||
} | ||
@@ -76,0 +89,0 @@ } |
@@ -11,9 +11,24 @@ import Fruit, { ValueNode, ValueNodeType } from '../Fruit'; | ||
constructor(value?: string); | ||
constructor(width: string, height?: string) { | ||
super(width); | ||
if (arguments.length > 1) { | ||
this.width = width; | ||
this.height = height; | ||
} | ||
constructor(); | ||
constructor(value: string); | ||
constructor(width: string, height: string); | ||
constructor(width: Length | Percentage, height: Length | Percentage); | ||
constructor(width?: Length | Percentage | string, height?: Length | Percentage | string) { | ||
super(); | ||
this._type = 'background-size'; | ||
this.init(); | ||
const args = arguments; | ||
this.tryCatch(() => { | ||
if (args.length === 0) | ||
return; | ||
else if (args.length === 1 && typeof width === 'string') | ||
this.parse(width); | ||
else if (args.length === 2) { | ||
this.width = width; | ||
this.height = height; | ||
this.valid = true; | ||
} else | ||
throw new TypeError('Wrong type or excessive arguments'); | ||
}); | ||
} | ||
@@ -43,3 +58,3 @@ | ||
if (this._state.count >= 1) | ||
throw new SyntaxError('Excessive keyword'); | ||
throw new SyntaxError(`Excessive keyword '${node.value}'`); | ||
else { | ||
@@ -60,3 +75,3 @@ this.width = this.height = node.value; | ||
if (this._state.count >= 2) | ||
throw new SyntaxError('Excessive <size> value: ' + size); | ||
throw new SyntaxError(`Excessive value '${size}'`); | ||
else if (this._state.count === 0) { | ||
@@ -72,3 +87,3 @@ this.width = size; | ||
} else | ||
throw new Error('State Problem!'); | ||
throw new Error('Unexpected internal error about _state.count'); | ||
} | ||
@@ -75,0 +90,0 @@ } |
@@ -1,2 +0,2 @@ | ||
import Fruit, { ValueNode, ValueNodeType } from '../Fruit'; | ||
import Fruit, { ValueNode, ValueNodeType, ParsedDepth } from '../Fruit'; | ||
import Length from '../dataTypes/Length'; | ||
@@ -13,3 +13,2 @@ import Percentage from '../dataTypes/Percentage'; | ||
export default class Margin extends Fruit { | ||
protected _type: string = 'background-repeat'; | ||
protected _state: { count: number }; | ||
@@ -22,2 +21,21 @@ | ||
constructor(); | ||
constructor(value: string); | ||
constructor(value?: string) { | ||
super(); | ||
this._type = 'margin'; | ||
this._parseDepth = ParsedDepth.shorthand; | ||
this.init(); | ||
const args = arguments; | ||
this.tryCatch(() => { | ||
if (args.length === 0) | ||
return; | ||
else if (args.length === 1) | ||
this.parse(value); | ||
else | ||
throw new TypeError('Wrong type or excessive arguments'); | ||
}) | ||
} | ||
protected init() { | ||
@@ -46,3 +64,3 @@ super.init(); | ||
if (this._state.count >= 4) | ||
throw new SyntaxError('Excessive <margin> value: ' + value); | ||
throw new SyntaxError(`Excessive value '${value}'`); | ||
else if (this._state.count === 0) | ||
@@ -49,0 +67,0 @@ this.top = this.right = this.bottom = this.left = value; |
@@ -1,2 +0,2 @@ | ||
import Fruit, { ValueNode, ValueNodeType } from '../Fruit'; | ||
import Fruit, { ValueNode, ValueNodeType, ParsedDepth } from '../Fruit'; | ||
import Length from '../dataTypes/Length'; | ||
@@ -13,3 +13,2 @@ import Percentage from '../dataTypes/Percentage'; | ||
export default class Padding extends Fruit { | ||
protected _type: string = 'background-repeat'; | ||
protected _state: { count: number }; | ||
@@ -22,2 +21,21 @@ | ||
constructor(); | ||
constructor(value: string); | ||
constructor(value?: string) { | ||
super(); | ||
this._type = 'padding'; | ||
this._parseDepth = ParsedDepth.shorthand; | ||
this.init(); | ||
const args = arguments; | ||
this.tryCatch(() => { | ||
if (args.length === 0) | ||
return; | ||
else if (args.length === 1) | ||
this.parse(value); | ||
else | ||
throw new TypeError('Wrong type or excessive arguments'); | ||
}) | ||
} | ||
protected init() { | ||
@@ -46,6 +64,6 @@ super.init(); | ||
if (String(value)[0] === '-') | ||
throw new Error('Negative values are invalid'); | ||
throw new RangeError(`Negative value '${value}' is invalid`); | ||
if (this._state.count >= 4) | ||
throw new SyntaxError('Excessive <padding> value: ' + value); | ||
throw new SyntaxError(`Excessive value '${value}'`); | ||
else if (this._state.count === 0) | ||
@@ -52,0 +70,0 @@ this.top = this.right = this.bottom = this.left = value; |
@@ -13,3 +13,3 @@ import * as JSON5 from 'json5'; | ||
if (query[0] !== '?') | ||
throw new Error("A valid query string passed to parseQuery should begin with '?'"); | ||
throw new SyntaxError("A valid query string passed to parseQuery should begin with '?'"); | ||
@@ -67,10 +67,18 @@ query = query.substr(1); | ||
if (Array.isArray(value)) | ||
return value.map((subValue) => `${encodeURIComponent(key)}[]=${encodeURIComponent(subValue)}`).join('&'); | ||
return value.map((subValue) => `${key}[]=${subValue}`).join('&'); | ||
else if (value === true) | ||
return `${encodeURIComponent(key)}`; | ||
return `${key}`; | ||
else if (value === false || value === null) | ||
return `${encodeURIComponent(key)}=${value}`; | ||
return `${key}=${value}`; | ||
else | ||
return `${encodeURIComponent(key)}=${encodeURIComponent(query[key] as string)}`; | ||
return `${key}=${query[key] as string}`; | ||
// if (Array.isArray(value)) | ||
// return value.map((subValue) => `${encodeURIComponent(key)}[]=${encodeURIComponent(subValue)}`).join('&'); | ||
// else if (value === true) | ||
// return `${encodeURIComponent(key)}`; | ||
// else if (value === false || value === null) | ||
// return `${encodeURIComponent(key)}=${value}`; | ||
// else | ||
// return `${encodeURIComponent(key)}=${encodeURIComponent(query[key] as string)}`; | ||
}).join('&'); | ||
} |
@@ -35,2 +35,6 @@ import { expect } from 'chai'; | ||
it('parse(image-set) -> image-set', () => { | ||
expect(Image.parse('image-set(url(bird.png) 1x, url(bird@2x.png) 2x)').toString()).to.equal("image-set(url('bird.png') 1x, url('bird@2x.png') 2x)"); | ||
}); | ||
it('parse(value) -> invalid', () => { | ||
@@ -37,0 +41,0 @@ expect(Image.parse('abc')).to.be.undefined; |
@@ -5,9 +5,29 @@ import { expect } from 'chai'; | ||
describe('Length', () => { | ||
it('#constructor(value)', () => { | ||
it('#constructor(value: string)', () => { | ||
const length = new Length('4.01em'); | ||
expect(length.number).to.equal(4.01); | ||
expect(length.unit).to.equal('em'); | ||
expect(length.valid).to.be.true; | ||
}); | ||
it('#constructor(number: number)', () => { | ||
const length = new Length(0); | ||
expect(length.number).to.equal(0); | ||
expect(length.unit).to.equal(''); | ||
expect(length.valid).to.be.true; | ||
const length2 = new Length(3); | ||
expect(length2.valid).to.be.false; | ||
}); | ||
it('#constructor(number: number, unit: string)', () => { | ||
const length = new Length(3, 'px'); | ||
expect(length.number).to.equal(3); | ||
expect(length.unit).to.equal('px'); | ||
expect(length.valid).to.be.true; | ||
const length2 = new Length(2.1, 'ab'); | ||
expect(length2.valid).to.be.false; | ||
}); | ||
it('.validate(value) => true', () => { | ||
@@ -14,0 +34,0 @@ expect(Length.validate('12px')).to.be.true; |
@@ -5,3 +5,3 @@ import { expect } from 'chai'; | ||
describe('Percentage', () => { | ||
it('#constructor(value)', () => { | ||
it('#constructor(value: string)', () => { | ||
const percentage = new Percentage('4.01%'); | ||
@@ -12,2 +12,8 @@ | ||
it('#constructor(value: number)', () => { | ||
const percentage = new Percentage(12); | ||
expect(percentage.number).to.equal(12); | ||
}); | ||
it('.validate(value) => true', () => { | ||
@@ -14,0 +20,0 @@ expect(Percentage.validate('12%')).to.be.true; |
@@ -30,3 +30,3 @@ import { expect } from 'chai'; | ||
expect(new URL(`url('abc.png?xyz[]=a&xyz[]=b')`).toString()).to.equal(`url('abc.png?xyz[]=a&xyz[]=b')`); | ||
expect(new URL(`url('abc.png?a%2C%26b=c%2C%26d')`).toString()).to.equal(`url('abc.png?a%2C%26b=c%2C%26d')`); | ||
// expect(new URL(`url('abc.png?a%2C%26b=c%2C%26d')`).toString()).to.equal(`url('abc.png?a%2C%26b=c%2C%26d')`); | ||
expect(new URL(`url('abc.png?{data:{a:1},isJSON5:true}')`).toString()).to.equal(`url('abc.png?{data:{a:1},isJSON5:true}')`); | ||
@@ -46,3 +46,3 @@ }) | ||
expect(url.toString()).to.equal(`url('%E4%B8%AD%E6%96%87.png?%E5%8F%82%E6%95%B0=%E5%80%BC#%E5%93%88%E5%B8%8C')`); | ||
expect(url.toString()).to.equal(`url('中文.png?参数=值#哈希')`); | ||
}); | ||
@@ -49,0 +49,0 @@ |
@@ -14,2 +14,11 @@ import { expect } from 'chai'; | ||
it('#constructor(~image-set~)', () => { | ||
const background = new Background(`red center top -webkit-image-set(url('./angry-birds.png') 1x, url('./angry-birds@2x.png') 2x) border-box no-repeat`); | ||
expect(background.color.toString()).to.equal(`red`); | ||
expect(background.image.toString()).to.equal(`-webkit-image-set(url('./angry-birds.png') 1x, url('./angry-birds@2x.png') 2x)`); | ||
expect(background.position.toString()).to.equal('center top'); | ||
expect(background.repeat.toString()).to.equal('no-repeat'); | ||
}); | ||
it('(value) -> valid', () => { | ||
@@ -16,0 +25,0 @@ expect(Background.validate('red url(abc.png) center top / auto 20px border-box no-repeat')).to.be.true; |
@@ -9,3 +9,3 @@ import { expect } from 'chai'; | ||
expect(backgroundPosition.x.origin).to.equal('left'); | ||
expect(backgroundPosition.x.offset).to.be.undefined; | ||
expect(backgroundPosition.x.offset.toString()).to.equal('0'); | ||
expect(backgroundPosition.y.origin).to.equal('top'); | ||
@@ -12,0 +12,0 @@ expect(backgroundPosition.y.offset.toString()).to.equal('40%'); |
@@ -6,2 +6,4 @@ import './dataTypes/Number'; | ||
import './dataTypes/Image'; | ||
import './dataTypes/ImageSet'; | ||
import './dataTypes/Resolution'; | ||
import './properties/Background'; | ||
@@ -11,1 +13,3 @@ import './properties/BackgroundPosition'; | ||
import './properties/BackgroundSize'; | ||
import './properties/Margin'; | ||
import './properties/Padding'; |
Sorry, the diff of this file is too big to display
225732
47
4908
15