Skip to content

Commit

Permalink
Merge pull request #7 from tbela99/generators
Browse files Browse the repository at this point in the history
Incorrect shorthand parsing #6
  • Loading branch information
tbela99 authored Aug 13, 2023
2 parents f3c6e3a + 997ff5f commit 0e24780
Show file tree
Hide file tree
Showing 19 changed files with 456 additions and 167 deletions.
3 changes: 2 additions & 1 deletion .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
/package-lock.json
/node_modules
/coverage
/.github
/.github
/.gitattributes
62 changes: 59 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ $ npm install @tbela99/css-parser
- fault tolerant parser, will try to fix invalid tokens according to the CSS syntax module 3 recommendations.
- efficient minification, see [benchmark](https://tbela99.github.io/css-parser/benchmark/index.html)
- replace @import at-rules with actual css content of the imported rule
- automatically create nested css rules
- automatically generate nested css rules
- works the same way in node and web browser

### Performance
Expand Down Expand Up @@ -79,7 +79,7 @@ parse(css, parseOptions = {})

````javascript

const {ast, errors} = await parse(css);
const {ast, errors, stats} = await parse(css);
````

## Rendering
Expand All @@ -96,7 +96,7 @@ render(ast, RenderOptions = {});
import {render} from '@tbela99/css-parser';

// minified
const {code} = render(ast, {minify: true});
const {code, stats} = render(ast, {minify: true});

console.log(code);
```
Expand Down Expand Up @@ -160,6 +160,62 @@ Single JavaScript file
<script src="dist/index-umd-web.js"></script>
```

## Example

### Automatic CSS Nesting

CSS

```css

table.colortable td {
text-align:center;
}
table.colortable td.c {
text-transform:uppercase;
}
table.colortable td:first-child, table.colortable td:first-child+td {
border:1px solid black;
}
table.colortable th {
text-align:center;
background:black;
color:white;
}
```

Javascript
```javascript
import {parse, render} from '@tbela99/css-parser';


const options = {minify: true, nestingRules: true};

const {code} = await parse(css, options).then(result => render(result.ast, {minify: false}));
//
console.debug(code);
```

Result
```css
table.colortable {
& td {
text-align: center;
&.c {
text-transform: uppercase
}
&:first-child,&:first-child+td {
border: 1px solid #000
}
}
& th {
text-align: center;
background: #000;
color: #fff
}
}
```

## AST

### Comment
Expand Down
107 changes: 66 additions & 41 deletions dist/index-umd-web.js
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,21 @@

const getConfig = () => config$1;

const funcList = ['clamp', 'calc'];
function matchType(val, properties) {
if (val.typ == 'Iden' && properties.keywords.includes(val.val) ||
(properties.types.includes(val.typ))) {
return true;
}
if (val.typ == 'Number' && val.val == '0') {
return properties.types.some(type => type == 'Length' || type == 'Angle');
}
if (val.typ == 'Func' && funcList.includes(val.val)) {
return val.chi.every((t => ['Literal', 'Comma', 'Whitespace', 'Start-parens', 'End-parens'].includes(t.typ) || matchType(t, properties)));
}
return false;
}

// name to color
const COLORS_NAMES = Object.seal({
'aliceblue': '#f0f8ff',
Expand Down Expand Up @@ -1597,6 +1612,8 @@
const indent = indents[level];
const indentSub = indents[level + 1];
switch (data.typ) {
case 'Declaration':
return `${data.nam}:${options.indent}${data.val.reduce((acc, curr) => acc + renderToken(curr), '')}`;
case 'Comment':
return options.removeComments ? '' : data.val;
case 'StyleSheet':
Expand All @@ -1622,6 +1639,10 @@
str = options.removeComments ? '' : node.val;
}
else if (node.typ == 'Declaration') {
if (node.val.length == 0) {
console.error(`invalid declaration`, node);
return '';
}
str = `${node.nam}:${options.indent}${node.val.reduce(reducer, '').trimEnd()};`;
}
else if (node.typ == 'AtRule' && !('chi' in node)) {
Expand Down Expand Up @@ -2000,17 +2021,6 @@
}
}

function matchType(val, properties) {
if (val.typ == 'Iden' && properties.keywords.includes(val.val) ||
(properties.types.includes(val.typ))) {
return true;
}
if (val.typ == 'Number' && val.val == '0') {
return properties.types.some(type => type == 'Length' || type == 'Angle');
}
return false;
}

const propertiesConfig = getConfig();
class PropertyMap {
config;
Expand All @@ -2025,6 +2035,9 @@
this.pattern = config.pattern.split(/\s/);
}
add(declaration) {
for (const val of declaration.val) {
Object.defineProperty(val, 'propertyName', { enumerable: false, writable: true, value: declaration.nam });
}
if (declaration.nam == this.config.shorthand) {
this.declarations = new Map;
this.declarations.set(declaration.nam, declaration);
Expand Down Expand Up @@ -2058,7 +2071,7 @@
i--;
continue;
}
if (matchType(acc[i], props)) {
if (('propertyName' in acc[i] && acc[i].propertyName == property) || matchType(acc[i], props)) {
if ('prefix' in props && props.previous != null && !(props.previous in tokens)) {
return acc;
}
Expand Down Expand Up @@ -2192,10 +2205,12 @@
}
else {
let count = 0;
let match;
const separator = this.config.separator;
const tokens = {};
// @ts-ignore
/* const valid: string[] =*/ Object.entries(this.config.properties).reduce((acc, curr) => {
/* const valid: string[] =*/
Object.entries(this.config.properties).reduce((acc, curr) => {
if (!this.declarations.has(curr[0])) {
if (curr[1].required) {
acc.push(curr[0]);
Expand All @@ -2204,33 +2219,39 @@
}
let current = 0;
const props = this.config.properties[curr[0]];
const declaration = this.declarations.get(curr[0]);
// @ts-ignore
for (const val of (declaration instanceof PropertySet ? [...declaration][0] : declaration).val) {
if (separator != null && separator.typ == val.typ && eq(separator, val)) {
current++;
if (tokens[curr[0]].length == current) {
tokens[curr[0]].push([]);
const properties = this.declarations.get(curr[0]);
for (const declaration of [(properties instanceof PropertySet ? [...properties][0] : properties)]) {
// @ts-ignore
for (const val of declaration.val) {
if (separator != null && separator.typ == val.typ && eq(separator, val)) {
current++;
if (tokens[curr[0]].length == current) {
tokens[curr[0]].push([]);
}
continue;
}
continue;
}
if (val.typ == 'Whitespace' || val.typ == 'Comment') {
continue;
}
if (props.multiple && props.separator != null && props.separator.typ == val.typ && eq(props.separator, val)) {
continue;
}
if (matchType(val, curr[1])) {
if (!(curr[0] in tokens)) {
tokens[curr[0]] = [[]];
if (val.typ == 'Whitespace' || val.typ == 'Comment') {
continue;
}
if (props.multiple && props.separator != null && props.separator.typ == val.typ && eq(props.separator, val)) {
continue;
}
match = matchType(val, curr[1]);
if (isShorthand) {
isShorthand = match;
}
if (('propertyName' in val && val.propertyName == property) || match) {
if (!(curr[0] in tokens)) {
tokens[curr[0]] = [[]];
}
// is default value
tokens[curr[0]][current].push(val);
// continue;
}
else {
acc.push(curr[0]);
break;
}
// is default value
tokens[curr[0]][current].push(val);
// continue;
}
else {
acc.push(curr[0]);
break;
}
}
if (count == 0) {
Expand All @@ -2239,7 +2260,10 @@
return acc;
}, []);
count++;
if (!Object.values(tokens).every(v => v.length == count)) {
if (!isShorthand || Object.entries(this.config.properties).some(entry => {
// missing required property
return entry[1].required && !(entry[0] in tokens);
}) || !Object.values(tokens).every(v => v.length == count)) {
// @ts-ignore
iterable = this.declarations.values();
}
Expand Down Expand Up @@ -4106,13 +4130,13 @@
};
}
function parseString(src, options = { location: false }) {
return [...tokenize(src)].map(t => {
return parseTokens([...tokenize(src)].map(t => {
const token = getTokenType(t.token, t.hint);
if (options.location) {
Object.assign(token, { loc: t.position });
}
return token;
});
}));
}
function getTokenType(val, hint) {
if (val === '' && hint == null) {
Expand Down Expand Up @@ -4623,6 +4647,7 @@
exports.isTime = isTime;
exports.isWhiteSpace = isWhiteSpace;
exports.load = load;
exports.matchType = matchType;
exports.matchUrl = matchUrl;
exports.minify = minify;
exports.minifyRule = minifyRule;
Expand Down
Loading

0 comments on commit 0e24780

Please sign in to comment.