@tbela99/css-parser 0.0.1-rc2 → 0.0.1-rc3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,7 +15,7 @@ $ npm install @tbela99/css-parser
15
15
  - fault tolerant parser, will try to fix invalid tokens according to the CSS syntax module 3 recommendations.
16
16
  - efficient minification, see [benchmark](https://tbela99.github.io/css-parser/benchmark/index.html)
17
17
  - replace @import at-rules with actual css content of the imported rule
18
- - automatically create nested css rules
18
+ - automatically generate nested css rules
19
19
  - works the same way in node and web browser
20
20
 
21
21
  ### Performance
@@ -79,7 +79,7 @@ parse(css, parseOptions = {})
79
79
 
80
80
  ````javascript
81
81
 
82
- const {ast, errors} = await parse(css);
82
+ const {ast, errors, stats} = await parse(css);
83
83
  ````
84
84
 
85
85
  ## Rendering
@@ -96,7 +96,7 @@ render(ast, RenderOptions = {});
96
96
  import {render} from '@tbela99/css-parser';
97
97
 
98
98
  // minified
99
- const {code} = render(ast, {minify: true});
99
+ const {code, stats} = render(ast, {minify: true});
100
100
 
101
101
  console.log(code);
102
102
  ```
@@ -160,6 +160,62 @@ Single JavaScript file
160
160
  <script src="dist/index-umd-web.js"></script>
161
161
  ```
162
162
 
163
+ ## Example
164
+
165
+ ### Automatic CSS Nesting
166
+
167
+ CSS
168
+
169
+ ```css
170
+
171
+ table.colortable td {
172
+ text-align:center;
173
+ }
174
+ table.colortable td.c {
175
+ text-transform:uppercase;
176
+ }
177
+ table.colortable td:first-child, table.colortable td:first-child+td {
178
+ border:1px solid black;
179
+ }
180
+ table.colortable th {
181
+ text-align:center;
182
+ background:black;
183
+ color:white;
184
+ }
185
+ ```
186
+
187
+ Javascript
188
+ ```javascript
189
+ import {parse, render} from '@tbela99/css-parser';
190
+
191
+
192
+ const options = {minify: true, nestingRules: true};
193
+
194
+ const {code} = await parse(css, options).then(result => render(result.ast, {minify: false}));
195
+ //
196
+ console.debug(code);
197
+ ```
198
+
199
+ Result
200
+ ```css
201
+ table.colortable {
202
+ & td {
203
+ text-align: center;
204
+ &.c {
205
+ text-transform: uppercase
206
+ }
207
+ &:first-child,&:first-child+td {
208
+ border: 1px solid #000
209
+ }
210
+ }
211
+ & th {
212
+ text-align: center;
213
+ background: #000;
214
+ color: #fff
215
+ }
216
+ }
217
+ ```
218
+
163
219
  ## AST
164
220
 
165
221
  ### Comment
@@ -1069,6 +1069,21 @@
1069
1069
 
1070
1070
  const getConfig = () => config$1;
1071
1071
 
1072
+ const funcList = ['clamp', 'calc'];
1073
+ function matchType(val, properties) {
1074
+ if (val.typ == 'Iden' && properties.keywords.includes(val.val) ||
1075
+ (properties.types.includes(val.typ))) {
1076
+ return true;
1077
+ }
1078
+ if (val.typ == 'Number' && val.val == '0') {
1079
+ return properties.types.some(type => type == 'Length' || type == 'Angle');
1080
+ }
1081
+ if (val.typ == 'Func' && funcList.includes(val.val)) {
1082
+ return val.chi.every((t => ['Literal', 'Comma', 'Whitespace', 'Start-parens', 'End-parens'].includes(t.typ) || matchType(t, properties)));
1083
+ }
1084
+ return false;
1085
+ }
1086
+
1072
1087
  // name to color
1073
1088
  const COLORS_NAMES = Object.seal({
1074
1089
  'aliceblue': '#f0f8ff',
@@ -1597,6 +1612,8 @@
1597
1612
  const indent = indents[level];
1598
1613
  const indentSub = indents[level + 1];
1599
1614
  switch (data.typ) {
1615
+ case 'Declaration':
1616
+ return `${data.nam}:${options.indent}${data.val.reduce((acc, curr) => acc + renderToken(curr), '')}`;
1600
1617
  case 'Comment':
1601
1618
  return options.removeComments ? '' : data.val;
1602
1619
  case 'StyleSheet':
@@ -1622,6 +1639,10 @@
1622
1639
  str = options.removeComments ? '' : node.val;
1623
1640
  }
1624
1641
  else if (node.typ == 'Declaration') {
1642
+ if (node.val.length == 0) {
1643
+ console.error(`invalid declaration`, node);
1644
+ return '';
1645
+ }
1625
1646
  str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`;
1626
1647
  }
1627
1648
  else if (node.typ == 'AtRule' && !('chi' in node)) {
@@ -2000,17 +2021,6 @@
2000
2021
  }
2001
2022
  }
2002
2023
 
2003
- function matchType(val, properties) {
2004
- if (val.typ == 'Iden' && properties.keywords.includes(val.val) ||
2005
- (properties.types.includes(val.typ))) {
2006
- return true;
2007
- }
2008
- if (val.typ == 'Number' && val.val == '0') {
2009
- return properties.types.some(type => type == 'Length' || type == 'Angle');
2010
- }
2011
- return false;
2012
- }
2013
-
2014
2024
  const propertiesConfig = getConfig();
2015
2025
  class PropertyMap {
2016
2026
  config;
@@ -2025,6 +2035,9 @@
2025
2035
  this.pattern = config.pattern.split(/\s/);
2026
2036
  }
2027
2037
  add(declaration) {
2038
+ for (const val of declaration.val) {
2039
+ Object.defineProperty(val, 'propertyName', { enumerable: false, writable: true, value: declaration.nam });
2040
+ }
2028
2041
  if (declaration.nam == this.config.shorthand) {
2029
2042
  this.declarations = new Map;
2030
2043
  this.declarations.set(declaration.nam, declaration);
@@ -2058,7 +2071,7 @@
2058
2071
  i--;
2059
2072
  continue;
2060
2073
  }
2061
- if (matchType(acc[i], props)) {
2074
+ if (('propertyName' in acc[i] && acc[i].propertyName == property) || matchType(acc[i], props)) {
2062
2075
  if ('prefix' in props && props.previous != null && !(props.previous in tokens)) {
2063
2076
  return acc;
2064
2077
  }
@@ -2192,10 +2205,12 @@
2192
2205
  }
2193
2206
  else {
2194
2207
  let count = 0;
2208
+ let match;
2195
2209
  const separator = this.config.separator;
2196
2210
  const tokens = {};
2197
2211
  // @ts-ignore
2198
- /* const valid: string[] =*/ Object.entries(this.config.properties).reduce((acc, curr) => {
2212
+ /* const valid: string[] =*/
2213
+ Object.entries(this.config.properties).reduce((acc, curr) => {
2199
2214
  if (!this.declarations.has(curr[0])) {
2200
2215
  if (curr[1].required) {
2201
2216
  acc.push(curr[0]);
@@ -2204,33 +2219,39 @@
2204
2219
  }
2205
2220
  let current = 0;
2206
2221
  const props = this.config.properties[curr[0]];
2207
- const declaration = this.declarations.get(curr[0]);
2208
- // @ts-ignore
2209
- for (const val of (declaration instanceof PropertySet ? [...declaration][0] : declaration).val) {
2210
- if (separator != null && separator.typ == val.typ && eq(separator, val)) {
2211
- current++;
2212
- if (tokens[curr[0]].length == current) {
2213
- tokens[curr[0]].push([]);
2222
+ const properties = this.declarations.get(curr[0]);
2223
+ for (const declaration of [(properties instanceof PropertySet ? [...properties][0] : properties)]) {
2224
+ // @ts-ignore
2225
+ for (const val of declaration.val) {
2226
+ if (separator != null && separator.typ == val.typ && eq(separator, val)) {
2227
+ current++;
2228
+ if (tokens[curr[0]].length == current) {
2229
+ tokens[curr[0]].push([]);
2230
+ }
2231
+ continue;
2214
2232
  }
2215
- continue;
2216
- }
2217
- if (val.typ == 'Whitespace' || val.typ == 'Comment') {
2218
- continue;
2219
- }
2220
- if (props.multiple && props.separator != null && props.separator.typ == val.typ && eq(props.separator, val)) {
2221
- continue;
2222
- }
2223
- if (matchType(val, curr[1])) {
2224
- if (!(curr[0] in tokens)) {
2225
- tokens[curr[0]] = [[]];
2233
+ if (val.typ == 'Whitespace' || val.typ == 'Comment') {
2234
+ continue;
2235
+ }
2236
+ if (props.multiple && props.separator != null && props.separator.typ == val.typ && eq(props.separator, val)) {
2237
+ continue;
2238
+ }
2239
+ match = matchType(val, curr[1]);
2240
+ if (isShorthand) {
2241
+ isShorthand = match;
2242
+ }
2243
+ if (('propertyName' in val && val.propertyName == property) || match) {
2244
+ if (!(curr[0] in tokens)) {
2245
+ tokens[curr[0]] = [[]];
2246
+ }
2247
+ // is default value
2248
+ tokens[curr[0]][current].push(val);
2249
+ // continue;
2250
+ }
2251
+ else {
2252
+ acc.push(curr[0]);
2253
+ break;
2226
2254
  }
2227
- // is default value
2228
- tokens[curr[0]][current].push(val);
2229
- // continue;
2230
- }
2231
- else {
2232
- acc.push(curr[0]);
2233
- break;
2234
2255
  }
2235
2256
  }
2236
2257
  if (count == 0) {
@@ -2239,7 +2260,10 @@
2239
2260
  return acc;
2240
2261
  }, []);
2241
2262
  count++;
2242
- if (!Object.values(tokens).every(v => v.length == count)) {
2263
+ if (!isShorthand || Object.entries(this.config.properties).some(entry => {
2264
+ // missing required property
2265
+ return entry[1].required && !(entry[0] in tokens);
2266
+ }) || !Object.values(tokens).every(v => v.length == count)) {
2243
2267
  // @ts-ignore
2244
2268
  iterable = this.declarations.values();
2245
2269
  }
@@ -4106,13 +4130,13 @@
4106
4130
  };
4107
4131
  }
4108
4132
  function parseString(src, options = { location: false }) {
4109
- return [...tokenize(src)].map(t => {
4133
+ return parseTokens([...tokenize(src)].map(t => {
4110
4134
  const token = getTokenType(t.token, t.hint);
4111
4135
  if (options.location) {
4112
4136
  Object.assign(token, { loc: t.position });
4113
4137
  }
4114
4138
  return token;
4115
- });
4139
+ }));
4116
4140
  }
4117
4141
  function getTokenType(val, hint) {
4118
4142
  if (val === '' && hint == null) {
@@ -4623,6 +4647,7 @@
4623
4647
  exports.isTime = isTime;
4624
4648
  exports.isWhiteSpace = isWhiteSpace;
4625
4649
  exports.load = load;
4650
+ exports.matchType = matchType;
4626
4651
  exports.matchUrl = matchUrl;
4627
4652
  exports.minify = minify;
4628
4653
  exports.minifyRule = minifyRule;
package/dist/index.cjs CHANGED
@@ -1067,6 +1067,21 @@ var config$1 = {
1067
1067
 
1068
1068
  const getConfig = () => config$1;
1069
1069
 
1070
+ const funcList = ['clamp', 'calc'];
1071
+ function matchType(val, properties) {
1072
+ if (val.typ == 'Iden' && properties.keywords.includes(val.val) ||
1073
+ (properties.types.includes(val.typ))) {
1074
+ return true;
1075
+ }
1076
+ if (val.typ == 'Number' && val.val == '0') {
1077
+ return properties.types.some(type => type == 'Length' || type == 'Angle');
1078
+ }
1079
+ if (val.typ == 'Func' && funcList.includes(val.val)) {
1080
+ return val.chi.every((t => ['Literal', 'Comma', 'Whitespace', 'Start-parens', 'End-parens'].includes(t.typ) || matchType(t, properties)));
1081
+ }
1082
+ return false;
1083
+ }
1084
+
1070
1085
  // name to color
1071
1086
  const COLORS_NAMES = Object.seal({
1072
1087
  'aliceblue': '#f0f8ff',
@@ -1595,6 +1610,8 @@ function doRender(data, options, reducer, level = 0, indents = []) {
1595
1610
  const indent = indents[level];
1596
1611
  const indentSub = indents[level + 1];
1597
1612
  switch (data.typ) {
1613
+ case 'Declaration':
1614
+ return `${data.nam}:${options.indent}${data.val.reduce((acc, curr) => acc + renderToken(curr), '')}`;
1598
1615
  case 'Comment':
1599
1616
  return options.removeComments ? '' : data.val;
1600
1617
  case 'StyleSheet':
@@ -1620,6 +1637,10 @@ function doRender(data, options, reducer, level = 0, indents = []) {
1620
1637
  str = options.removeComments ? '' : node.val;
1621
1638
  }
1622
1639
  else if (node.typ == 'Declaration') {
1640
+ if (node.val.length == 0) {
1641
+ console.error(`invalid declaration`, node);
1642
+ return '';
1643
+ }
1623
1644
  str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`;
1624
1645
  }
1625
1646
  else if (node.typ == 'AtRule' && !('chi' in node)) {
@@ -1998,17 +2019,6 @@ class PropertySet {
1998
2019
  }
1999
2020
  }
2000
2021
 
2001
- function matchType(val, properties) {
2002
- if (val.typ == 'Iden' && properties.keywords.includes(val.val) ||
2003
- (properties.types.includes(val.typ))) {
2004
- return true;
2005
- }
2006
- if (val.typ == 'Number' && val.val == '0') {
2007
- return properties.types.some(type => type == 'Length' || type == 'Angle');
2008
- }
2009
- return false;
2010
- }
2011
-
2012
2022
  const propertiesConfig = getConfig();
2013
2023
  class PropertyMap {
2014
2024
  config;
@@ -2023,6 +2033,9 @@ class PropertyMap {
2023
2033
  this.pattern = config.pattern.split(/\s/);
2024
2034
  }
2025
2035
  add(declaration) {
2036
+ for (const val of declaration.val) {
2037
+ Object.defineProperty(val, 'propertyName', { enumerable: false, writable: true, value: declaration.nam });
2038
+ }
2026
2039
  if (declaration.nam == this.config.shorthand) {
2027
2040
  this.declarations = new Map;
2028
2041
  this.declarations.set(declaration.nam, declaration);
@@ -2056,7 +2069,7 @@ class PropertyMap {
2056
2069
  i--;
2057
2070
  continue;
2058
2071
  }
2059
- if (matchType(acc[i], props)) {
2072
+ if (('propertyName' in acc[i] && acc[i].propertyName == property) || matchType(acc[i], props)) {
2060
2073
  if ('prefix' in props && props.previous != null && !(props.previous in tokens)) {
2061
2074
  return acc;
2062
2075
  }
@@ -2190,10 +2203,12 @@ class PropertyMap {
2190
2203
  }
2191
2204
  else {
2192
2205
  let count = 0;
2206
+ let match;
2193
2207
  const separator = this.config.separator;
2194
2208
  const tokens = {};
2195
2209
  // @ts-ignore
2196
- /* const valid: string[] =*/ Object.entries(this.config.properties).reduce((acc, curr) => {
2210
+ /* const valid: string[] =*/
2211
+ Object.entries(this.config.properties).reduce((acc, curr) => {
2197
2212
  if (!this.declarations.has(curr[0])) {
2198
2213
  if (curr[1].required) {
2199
2214
  acc.push(curr[0]);
@@ -2202,33 +2217,39 @@ class PropertyMap {
2202
2217
  }
2203
2218
  let current = 0;
2204
2219
  const props = this.config.properties[curr[0]];
2205
- const declaration = this.declarations.get(curr[0]);
2206
- // @ts-ignore
2207
- for (const val of (declaration instanceof PropertySet ? [...declaration][0] : declaration).val) {
2208
- if (separator != null && separator.typ == val.typ && eq(separator, val)) {
2209
- current++;
2210
- if (tokens[curr[0]].length == current) {
2211
- tokens[curr[0]].push([]);
2220
+ const properties = this.declarations.get(curr[0]);
2221
+ for (const declaration of [(properties instanceof PropertySet ? [...properties][0] : properties)]) {
2222
+ // @ts-ignore
2223
+ for (const val of declaration.val) {
2224
+ if (separator != null && separator.typ == val.typ && eq(separator, val)) {
2225
+ current++;
2226
+ if (tokens[curr[0]].length == current) {
2227
+ tokens[curr[0]].push([]);
2228
+ }
2229
+ continue;
2212
2230
  }
2213
- continue;
2214
- }
2215
- if (val.typ == 'Whitespace' || val.typ == 'Comment') {
2216
- continue;
2217
- }
2218
- if (props.multiple && props.separator != null && props.separator.typ == val.typ && eq(props.separator, val)) {
2219
- continue;
2220
- }
2221
- if (matchType(val, curr[1])) {
2222
- if (!(curr[0] in tokens)) {
2223
- tokens[curr[0]] = [[]];
2231
+ if (val.typ == 'Whitespace' || val.typ == 'Comment') {
2232
+ continue;
2233
+ }
2234
+ if (props.multiple && props.separator != null && props.separator.typ == val.typ && eq(props.separator, val)) {
2235
+ continue;
2236
+ }
2237
+ match = matchType(val, curr[1]);
2238
+ if (isShorthand) {
2239
+ isShorthand = match;
2240
+ }
2241
+ if (('propertyName' in val && val.propertyName == property) || match) {
2242
+ if (!(curr[0] in tokens)) {
2243
+ tokens[curr[0]] = [[]];
2244
+ }
2245
+ // is default value
2246
+ tokens[curr[0]][current].push(val);
2247
+ // continue;
2248
+ }
2249
+ else {
2250
+ acc.push(curr[0]);
2251
+ break;
2224
2252
  }
2225
- // is default value
2226
- tokens[curr[0]][current].push(val);
2227
- // continue;
2228
- }
2229
- else {
2230
- acc.push(curr[0]);
2231
- break;
2232
2253
  }
2233
2254
  }
2234
2255
  if (count == 0) {
@@ -2237,7 +2258,10 @@ class PropertyMap {
2237
2258
  return acc;
2238
2259
  }, []);
2239
2260
  count++;
2240
- if (!Object.values(tokens).every(v => v.length == count)) {
2261
+ if (!isShorthand || Object.entries(this.config.properties).some(entry => {
2262
+ // missing required property
2263
+ return entry[1].required && !(entry[0] in tokens);
2264
+ }) || !Object.values(tokens).every(v => v.length == count)) {
2241
2265
  // @ts-ignore
2242
2266
  iterable = this.declarations.values();
2243
2267
  }
@@ -4104,13 +4128,13 @@ async function parse$1(iterator, opt = {}) {
4104
4128
  };
4105
4129
  }
4106
4130
  function parseString(src, options = { location: false }) {
4107
- return [...tokenize(src)].map(t => {
4131
+ return parseTokens([...tokenize(src)].map(t => {
4108
4132
  const token = getTokenType(t.token, t.hint);
4109
4133
  if (options.location) {
4110
4134
  Object.assign(token, { loc: t.position });
4111
4135
  }
4112
4136
  return token;
4113
- });
4137
+ }));
4114
4138
  }
4115
4139
  function getTokenType(val, hint) {
4116
4140
  if (val === '' && hint == null) {
@@ -4607,6 +4631,7 @@ exports.isResolution = isResolution;
4607
4631
  exports.isTime = isTime;
4608
4632
  exports.isWhiteSpace = isWhiteSpace;
4609
4633
  exports.load = load;
4634
+ exports.matchType = matchType;
4610
4635
  exports.matchUrl = matchUrl;
4611
4636
  exports.minify = minify;
4612
4637
  exports.minifyRule = minifyRule;
package/dist/index.d.ts CHANGED
@@ -173,6 +173,30 @@ interface AttrToken {
173
173
  }
174
174
  declare type Token = LiteralToken | IdentToken | CommaToken | ColonToken | SemiColonToken | NumberToken | AtRuleToken | PercentageToken | FunctionURLToken | FunctionToken | DimensionToken | LengthToken | AngleToken | StringToken | TimeToken | FrequencyToken | ResolutionToken | UnclosedStringToken | HashToken | BadStringToken | BlockStartToken | BlockEndToken | AttrStartToken | AttrEndToken | ParensStartToken | ParensEndToken | CDOCommentToken | BadCDOCommentToken | CommentToken | BadCommentToken | WhitespaceToken | IncludesToken | DashMatchToken | LessThanToken | GreaterThanToken | PseudoClassToken | PseudoClassFunctionToken | DelimToken | BadUrlToken | UrlToken | ImportantToken | ColorToken | AttrToken | EOFToken;
175
175
 
176
+ interface PropertyMapType {
177
+ default: string[];
178
+ types: string[];
179
+ keywords: string[];
180
+ required?: boolean;
181
+ multiple?: boolean;
182
+ prefix?: {
183
+ typ: 'Literal';
184
+ val: string;
185
+ };
186
+ previous?: string;
187
+ separator?: {
188
+ typ: 'Comma';
189
+ };
190
+ constraints?: {
191
+ [key: string]: {
192
+ [key: string]: any;
193
+ };
194
+ };
195
+ mapping?: {
196
+ [key: string]: any;
197
+ };
198
+ }
199
+
176
200
  interface PropertiesConfig {
177
201
  properties: PropertiesConfigProperties;
178
202
  map: Map;
@@ -629,6 +653,8 @@ declare function isWhiteSpace(codepoint: number): boolean;
629
653
 
630
654
  declare const getConfig: () => PropertiesConfig;
631
655
 
656
+ declare function matchType(val: Token, properties: PropertyMapType): boolean;
657
+
632
658
  declare function render(data: AstNode, opt?: RenderOptions): RenderResult;
633
659
  declare function renderToken(token: Token, options?: RenderOptions): string;
634
660
 
@@ -661,4 +687,4 @@ declare function resolve(url: string, currentDirectory: string, cwd?: string): {
661
687
  declare function parse(iterator: string, opt?: ParserOptions): Promise<ParseResult>;
662
688
  declare function transform(css: string, options?: TransformOptions): Promise<TransformResult>;
663
689
 
664
- export { combinators, dirname, getConfig, hasDeclaration, isAngle, isAtKeyword, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, load, matchUrl, minify, minifyRule, parse, parseDimension, parseString, reduceSelector, render, renderToken, resolve, tokenize, transform, urlTokenMatcher, walk };
690
+ export { combinators, dirname, getConfig, hasDeclaration, isAngle, isAtKeyword, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, load, matchType, matchUrl, minify, minifyRule, parse, parseDimension, parseString, reduceSelector, render, renderToken, resolve, tokenize, transform, urlTokenMatcher, walk };
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ export { parseString, urlTokenMatcher } from './lib/parser/parse.js';
3
3
  export { tokenize } from './lib/parser/tokenize.js';
4
4
  export { isAngle, isAtKeyword, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension } from './lib/parser/utils/syntax.js';
5
5
  export { getConfig } from './lib/parser/utils/config.js';
6
+ export { matchType } from './lib/parser/utils/type.js';
6
7
  export { render, renderToken } from './lib/renderer/render.js';
7
8
  export { combinators, hasDeclaration, minify, minifyRule, reduceSelector } from './lib/ast/minify.js';
8
9
  export { walk } from './lib/ast/walk.js';
@@ -1,7 +1,7 @@
1
1
  import { eq } from '../utils/eq.js';
2
2
  import { getConfig } from '../utils/config.js';
3
- import { renderToken } from '../../renderer/render.js';
4
3
  import { matchType } from '../utils/type.js';
4
+ import { renderToken } from '../../renderer/render.js';
5
5
  import { parseString } from '../parse.js';
6
6
  import { PropertySet } from './set.js';
7
7
 
@@ -19,6 +19,9 @@ class PropertyMap {
19
19
  this.pattern = config.pattern.split(/\s/);
20
20
  }
21
21
  add(declaration) {
22
+ for (const val of declaration.val) {
23
+ Object.defineProperty(val, 'propertyName', { enumerable: false, writable: true, value: declaration.nam });
24
+ }
22
25
  if (declaration.nam == this.config.shorthand) {
23
26
  this.declarations = new Map;
24
27
  this.declarations.set(declaration.nam, declaration);
@@ -52,7 +55,7 @@ class PropertyMap {
52
55
  i--;
53
56
  continue;
54
57
  }
55
- if (matchType(acc[i], props)) {
58
+ if (('propertyName' in acc[i] && acc[i].propertyName == property) || matchType(acc[i], props)) {
56
59
  if ('prefix' in props && props.previous != null && !(props.previous in tokens)) {
57
60
  return acc;
58
61
  }
@@ -186,10 +189,12 @@ class PropertyMap {
186
189
  }
187
190
  else {
188
191
  let count = 0;
192
+ let match;
189
193
  const separator = this.config.separator;
190
194
  const tokens = {};
191
195
  // @ts-ignore
192
- /* const valid: string[] =*/ Object.entries(this.config.properties).reduce((acc, curr) => {
196
+ /* const valid: string[] =*/
197
+ Object.entries(this.config.properties).reduce((acc, curr) => {
193
198
  if (!this.declarations.has(curr[0])) {
194
199
  if (curr[1].required) {
195
200
  acc.push(curr[0]);
@@ -198,33 +203,39 @@ class PropertyMap {
198
203
  }
199
204
  let current = 0;
200
205
  const props = this.config.properties[curr[0]];
201
- const declaration = this.declarations.get(curr[0]);
202
- // @ts-ignore
203
- for (const val of (declaration instanceof PropertySet ? [...declaration][0] : declaration).val) {
204
- if (separator != null && separator.typ == val.typ && eq(separator, val)) {
205
- current++;
206
- if (tokens[curr[0]].length == current) {
207
- tokens[curr[0]].push([]);
206
+ const properties = this.declarations.get(curr[0]);
207
+ for (const declaration of [(properties instanceof PropertySet ? [...properties][0] : properties)]) {
208
+ // @ts-ignore
209
+ for (const val of declaration.val) {
210
+ if (separator != null && separator.typ == val.typ && eq(separator, val)) {
211
+ current++;
212
+ if (tokens[curr[0]].length == current) {
213
+ tokens[curr[0]].push([]);
214
+ }
215
+ continue;
208
216
  }
209
- continue;
210
- }
211
- if (val.typ == 'Whitespace' || val.typ == 'Comment') {
212
- continue;
213
- }
214
- if (props.multiple && props.separator != null && props.separator.typ == val.typ && eq(props.separator, val)) {
215
- continue;
216
- }
217
- if (matchType(val, curr[1])) {
218
- if (!(curr[0] in tokens)) {
219
- tokens[curr[0]] = [[]];
217
+ if (val.typ == 'Whitespace' || val.typ == 'Comment') {
218
+ continue;
219
+ }
220
+ if (props.multiple && props.separator != null && props.separator.typ == val.typ && eq(props.separator, val)) {
221
+ continue;
222
+ }
223
+ match = matchType(val, curr[1]);
224
+ if (isShorthand) {
225
+ isShorthand = match;
226
+ }
227
+ if (('propertyName' in val && val.propertyName == property) || match) {
228
+ if (!(curr[0] in tokens)) {
229
+ tokens[curr[0]] = [[]];
230
+ }
231
+ // is default value
232
+ tokens[curr[0]][current].push(val);
233
+ // continue;
234
+ }
235
+ else {
236
+ acc.push(curr[0]);
237
+ break;
220
238
  }
221
- // is default value
222
- tokens[curr[0]][current].push(val);
223
- // continue;
224
- }
225
- else {
226
- acc.push(curr[0]);
227
- break;
228
239
  }
229
240
  }
230
241
  if (count == 0) {
@@ -233,7 +244,10 @@ class PropertyMap {
233
244
  return acc;
234
245
  }, []);
235
246
  count++;
236
- if (!Object.values(tokens).every(v => v.length == count)) {
247
+ if (!isShorthand || Object.entries(this.config.properties).some(entry => {
248
+ // missing required property
249
+ return entry[1].required && !(entry[0] in tokens);
250
+ }) || !Object.values(tokens).every(v => v.length == count)) {
237
251
  // @ts-ignore
238
252
  iterable = this.declarations.values();
239
253
  }
@@ -389,13 +389,13 @@ async function parse(iterator, opt = {}) {
389
389
  };
390
390
  }
391
391
  function parseString(src, options = { location: false }) {
392
- return [...tokenize(src)].map(t => {
392
+ return parseTokens([...tokenize(src)].map(t => {
393
393
  const token = getTokenType(t.token, t.hint);
394
394
  if (options.location) {
395
395
  Object.assign(token, { loc: t.position });
396
396
  }
397
397
  return token;
398
- });
398
+ }));
399
399
  }
400
400
  function getTokenType(val, hint) {
401
401
  if (val === '' && hint == null) {
@@ -1,3 +1,4 @@
1
+ const funcList = ['clamp', 'calc'];
1
2
  function matchType(val, properties) {
2
3
  if (val.typ == 'Iden' && properties.keywords.includes(val.val) ||
3
4
  (properties.types.includes(val.typ))) {
@@ -6,6 +7,9 @@ function matchType(val, properties) {
6
7
  if (val.typ == 'Number' && val.val == '0') {
7
8
  return properties.types.some(type => type == 'Length' || type == 'Angle');
8
9
  }
10
+ if (val.typ == 'Func' && funcList.includes(val.val)) {
11
+ return val.chi.every((t => ['Literal', 'Comma', 'Whitespace', 'Start-parens', 'End-parens'].includes(t.typ) || matchType(t, properties)));
12
+ }
9
13
  return false;
10
14
  }
11
15
 
@@ -35,6 +35,8 @@ function doRender(data, options, reducer, level = 0, indents = []) {
35
35
  const indent = indents[level];
36
36
  const indentSub = indents[level + 1];
37
37
  switch (data.typ) {
38
+ case 'Declaration':
39
+ return `${data.nam}:${options.indent}${data.val.reduce((acc, curr) => acc + renderToken(curr), '')}`;
38
40
  case 'Comment':
39
41
  return options.removeComments ? '' : data.val;
40
42
  case 'StyleSheet':
@@ -60,6 +62,10 @@ function doRender(data, options, reducer, level = 0, indents = []) {
60
62
  str = options.removeComments ? '' : node.val;
61
63
  }
62
64
  else if (node.typ == 'Declaration') {
65
+ if (node.val.length == 0) {
66
+ console.error(`invalid declaration`, node);
67
+ return '';
68
+ }
63
69
  str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`;
64
70
  }
65
71
  else if (node.typ == 'AtRule' && !('chi' in node)) {
package/dist/web/index.js CHANGED
@@ -3,6 +3,7 @@ export { parseString, urlTokenMatcher } from '../lib/parser/parse.js';
3
3
  export { tokenize } from '../lib/parser/tokenize.js';
4
4
  export { isAngle, isAtKeyword, isDigit, isDimension, isFrequency, isFunction, isHash, isHexColor, isHexDigit, isIdent, isIdentCodepoint, isIdentStart, isLength, isNewLine, isNumber, isPercentage, isPseudo, isResolution, isTime, isWhiteSpace, parseDimension } from '../lib/parser/utils/syntax.js';
5
5
  export { getConfig } from '../lib/parser/utils/config.js';
6
+ export { matchType } from '../lib/parser/utils/type.js';
6
7
  export { render, renderToken } from '../lib/renderer/render.js';
7
8
  import { transform as transform$1 } from '../lib/transform.js';
8
9
  export { combinators, hasDeclaration, minify, minifyRule, reduceSelector } from '../lib/ast/minify.js';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tbela99/css-parser",
3
3
  "description": "CSS parser for node and the browser",
4
- "version": "0.0.1-rc2",
4
+ "version": "0.0.1-rc3",
5
5
  "exports": {
6
6
  ".": "./dist/index.js",
7
7
  "./umd": "./dist/index-umd-web.js",
@@ -41,20 +41,19 @@
41
41
  "homepage": "https://github.com/tbela99/css-parser#readme",
42
42
  "devDependencies": {
43
43
  "@esm-bundle/chai": "^4.3.4-fix.0",
44
- "@rollup/plugin-commonjs": "^25.0.2",
44
+ "@rollup/plugin-commonjs": "^25.0.4",
45
45
  "@rollup/plugin-json": "^6.0.0",
46
- "@rollup/plugin-node-resolve": "^15.0.1",
46
+ "@rollup/plugin-node-resolve": "^15.1.0",
47
47
  "@rollup/plugin-terser": "^0.4.3",
48
- "@rollup/plugin-typescript": "^11.0.0",
48
+ "@rollup/plugin-typescript": "^11.1.2",
49
49
  "@types/chai": "^4.3.5",
50
50
  "@types/mocha": "^10.0.1",
51
- "@types/node": "^18.15.10",
52
- "@web/test-runner": "^0.16.1",
53
- "@webref/css": "^6.5.9",
51
+ "@types/node": "^20.4.10",
52
+ "@web/test-runner": "^0.17.0",
54
53
  "c8": "^8.0.1",
55
54
  "mocha": "^10.2.0",
56
- "rollup": "^3.20.1",
57
- "rollup-plugin-dts": "^5.3.0",
58
- "tslib": "^2.5.0"
55
+ "rollup": "^3.28.0",
56
+ "rollup-plugin-dts": "^5.3.1",
57
+ "tslib": "^2.6.1"
59
58
  }
60
59
  }
package/.gitattributes DELETED
@@ -1,22 +0,0 @@
1
- /test/ export-ignore
2
- /docs/ export-ignore
3
- /benchmark
4
- /tools/ export-ignore
5
- /package-lock.json export-ignore
6
- /.gitignore export-ignore
7
- /.gitattributes export-ignore
8
- /coverage/ export-ignore
9
- /rollup.config.mjs export-ignore
10
- /tsconfig.json export-ignore
11
- # exclude all files in test/ from stats
12
- /test/** linguist-vendored
13
- /docs/** linguist-vendored
14
- /tools/** linguist-vendored
15
- /dist/** linguist-vendored
16
- /coverage/** linguist-vendored
17
- #
18
- # do not replace lf by crlf
19
- *.css text
20
- *.json text
21
- text eol=lf
22
-