forked from mahirshah/css-property-parser
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathexpandShorthandProperty.js
135 lines (127 loc) · 6.27 KB
/
expandShorthandProperty.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
const nearley = require('nearley');
const properties = require('./formatted-data/properties.json');
const isShorthandProperty = require('./isShorthandProperty');
const getShorthandComputedProperties = require('./getShorthandComputedProperties');
const shorthandProperties = require('./formatted-data/shorthand-properties.json');
const CSS_CONSTANTS = require('./constants/css');
const { CLASSIFICATIONS } = require('./constants/shorthandProperties');
const LocationIndexTracker = require('./utils/LocationIndexTracker');
const { ParseError, UnsupportedPropertyError, UnknownPropertyError } = require('./errors');
const SHORTHAND_IDENT_TO_LONGHAND_PROPERTY_MAP = require('./constants/shorthandIdentToLonghandPropertyMap.json');
const {
BackgroundPropertyFormatter,
BorderRadiusPropertyFormatter,
CommaSeparatedListPropertyFormatter,
FlexPropertyFormatter,
FontPropertyFormatter,
TrblPropertyFormatter,
UnorderedOptionalListPropertyFormatter,
} = require('./formatters/shorthandPropertyTypeFormatters');
const { initialValue, initialValues } = require('./initialValueMap');
const grammars = require('./grammars/generated');
const shorthandPropertyTypeToActionDictionaryFactoryMap = {
[CLASSIFICATIONS.TRBL]: TrblPropertyFormatter,
[CLASSIFICATIONS.UNORDERED_OPTIONAL_TUPLE]: UnorderedOptionalListPropertyFormatter,
[CLASSIFICATIONS.COMMA_SEPARATED_LIST]: CommaSeparatedListPropertyFormatter,
[CLASSIFICATIONS.FLEX]: FlexPropertyFormatter,
[CLASSIFICATIONS.BORDER_RADIUS]: BorderRadiusPropertyFormatter,
[CLASSIFICATIONS.BACKGROUND]: BackgroundPropertyFormatter,
[CLASSIFICATIONS.FONT]: FontPropertyFormatter,
};
const R_BLOCK_COMMENT = /\/\*.*?\*\//g;
/**
* Given a property and value attempts to expand the value into its longhand equivalents. Returns an object
* mapping the property longhand names to the longhand values. If the property cannot be expanded (i.e. the property
* is not a shorthand property) simply returns an object mapping the original property to the original value.
*
* @param {string} propertyName - the property name for the given value
* @param {string} propertyValue - the value of the property
* @param {boolean} [recursivelyResolve=false] - recursively resolve additional longhand properties if the shorthands
* expand to additional shorthands. For example, the border property
* expands to border-width, which expands further to border-left-width,
* border-right-width, etc.
* @param {boolean} [includeInitialValues=false] - when expanding the shorthand property, fill in any missing longhand
* values with their initial value. For example, the property
* declaration "border: 1px" only explicitly sets the "border-width"
* longhand property. If this param is true, the returned object will
* fill in the initial values for "border-style" and "border-color". By
* default, the returned object will only contain the "border-width".
* @return {Object} - object mapping longhand property names to values
*
* @throws {ParseError} - if the propertyValue cannot be parsed.
* @throws {UnknownPropertyError} - if the propertyName is not defined in mdn.
* @throws {UnsupportedPropertyError} - if the propertyName is a shorthand property, but we don't support expanding it
* yet.
*
* @example
* expandShorthandProperty('margin', '0 3px 10rem')
* {
* 'margin-top': '0',
* 'margin-right': '3px',
* 'margin-bottom': '10rem',
* 'margin-left': '3px',
* }
*
* @example
* expandShorthandProperty('flex', 'initial')
* {
* 'flex-grow': 'initial',
* 'flex-shrink': 'initial',
* 'flex-basis': 'initial',
* }
*/
module.exports = function expandShorthandProperty(propertyName,
propertyValue,
recursivelyResolve = false,
includeInitialValues = false) {
if (!properties[propertyName]) {
throw new UnknownPropertyError(propertyName);
} else if (!isShorthandProperty(propertyName)) {
return { [propertyName]: propertyValue };
} else if (!SHORTHAND_IDENT_TO_LONGHAND_PROPERTY_MAP[propertyName]) {
throw new UnsupportedPropertyError(propertyName);
} else if (CSS_CONSTANTS.globalValues.includes(propertyValue)) {
return getShorthandComputedProperties(propertyName).reduce((propertyMap, computedPropertyName) => (
Object.assign({ [computedPropertyName]: propertyValue }, propertyMap)
), {});
}
// get the compiled grammar file for this property
const grammar = grammars[propertyName];
// remove any block style comments and extra whitespace
const formattedPropertyValue = propertyValue.replace(R_BLOCK_COMMENT, ' ').replace(/\s+/g, ' ').trim();
let parser;
// attempt to parse the css value, using the property specific parser
try {
parser = new nearley.Parser(nearley.Grammar.fromCompiled(grammar)).feed(formattedPropertyValue);
} catch (parseError) {
throw new ParseError(`Error parsing shorthand property ${propertyName}: ${propertyValue}. ${parseError.message}`);
}
// get the first parsing and use the formatter for the specific shorthand type for this property
const { shorthandType } = shorthandProperties[propertyName];
const [rootNode] = parser.results;
LocationIndexTracker.reset();
let propertyExpansion = shorthandPropertyTypeToActionDictionaryFactoryMap[shorthandType]
.format(propertyName, rootNode, formattedPropertyValue);
if (recursivelyResolve) {
Object.keys(propertyExpansion).forEach((prop) => {
if (shorthandProperties[prop]) {
Object.assign(
propertyExpansion,
expandShorthandProperty(prop, propertyExpansion[prop], true));
}
});
}
if (includeInitialValues) {
let initials = {};
if (recursivelyResolve) {
initials = initialValues(propertyName, true, true);
delete initials[propertyName];
} else {
getShorthandComputedProperties(propertyName).forEach((prop) => {
initials[prop] = initialValue(prop);
});
}
propertyExpansion = Object.assign(initials, propertyExpansion);
}
return propertyExpansion;
};