Support both keys and value as an expression
kutnickclose opened this issue · 5 comments
Also, another case that's giving me trouble is with at-rules expressions and the case above but maybe solving the above will help solve this issue:
css`
${atRuleExpr} {
${expr1}: ${expr2}
}
`
this is probably related to #6.
there's a possibility the lit analyzer currently being worked on by the lit team will be able to parse HTML and CSS templates once it is finished. if thats the case, we can use it in this plugin and will be able to handle all binding positions properly.
otherwise, we will have to write our own smarter substitution code (which really comes close to writing a mini parser...).
i'll reach out to the lit team again so we can find out what the path forward should be.
I did a little experimentation this weekend and was able to get this test to pass using "smarter substitution code"
it('should parse and stringify expressions', () => {
const { source, ast } = createTestAst(`
css\`
\${expr0}
.foo { \${expr1}: \${expr2}; }
\${expr3} {
.bar { color: black}
}
\${expr4}
\`;
`);
const output = ast.toString(syntax);
expect(output).toEqual(source);
});
"smarter" substitution code:
//utils.js
const isAtRule = (sourceAsString: string, indexAfterExpression: number) => {
return sourceAsString[indexAfterExpression + 1] === '{';
};
const isProperty = (sourceAsString: string, indexAfterExpression: number) => {
return sourceAsString[indexAfterExpression] === ':';
};
const isRuleSet = (sourceAsString: string, indexAfterExpression: number) => {
return sourceAsString.indexOf('\n', indexAfterExpression-1) === indexAfterExpression;
};
export const smartCreatePlaceholder = (
i: number,
sourceAsString: string,
indexAfterExpression: number
): string => {
if (isAtRule(sourceAsString, indexAfterExpression)) {
return `@POSTCSS_LIT${i}`;
}
if (isProperty(sourceAsString, indexAfterExpression)) {
return `POSTCSS_LIT${i}`;
}
if (isRuleSet(sourceAsString, indexAfterExpression)) {
return `/*POSTCSS_LIT:${i}*/`;
}
// assume it's a property value;
return `POSTCSS_LIT${i}`;
};
after a parse it'll look like this:
/*POSTCSS_LIT:0*/
.foo { POSTCSS_LIT1: POSTCSS_LIT2; }
@POSTCSS_LIT3 {
.bar { color: black}
}
/*POSTCSS_LIT:4*/
Then I needed to update to the stringifier for decl
and atRules
//stringify.ts
public override atrule(node: AtRule, semicolon: boolean): void {
if (node.name.includes('POSTCSS_LIT')) {
const params = node.params ? this.rawValue(node, 'params') : '';
const [, expressionIndexString] = node.name.split('POSTCSS_LIT');
const expressionIndex = Number(expressionIndexString);
const root = node.root();
const expressionStrings = root.raws.linariaTemplateExpressions;
if (expressionStrings && !Number.isNaN(expressionIndex)) {
const expression = expressionStrings[expressionIndex];
if (expression) {
if (node.nodes) {
this.block(node, expression + params);
} else {
const end = (node.raws.between || '') + (semicolon ? ';' : '');
this.builder(expression + params + end, node);
}
return;
}
}
}
super.atrule(node);
}
public override decl(node: Declaration, semicolon: boolean): void {
let earlyReturn = false;
if (node.prop.includes('POSTCSS_LIT')) {
const [, expressionIndexString] = node.prop.split('POSTCSS_LIT');
const expressionIndex = Number(expressionIndexString);
const root = node.root();
const expressionStrings = root.raws.linariaTemplateExpressions;
if (expressionStrings && !Number.isNaN(expressionIndex)) {
const expression = expressionStrings[expressionIndex];
if (expression) {
this.builder(expression + node.raws.between, node);
earlyReturn = true;
}
}
}
if (node.value.includes('POSTCSS_LIT')) {
const [, expressionIndexString] = node.value.split('POSTCSS_LIT');
const expressionIndex = Number(expressionIndexString);
const root = node.root();
const expressionStrings = root.raws.linariaTemplateExpressions;
if (expressionStrings && !Number.isNaN(expressionIndex)) {
let expression = expressionStrings[expressionIndex];
expression += semicolon ? ';' : '';
if (expression) {
this.builder(expression, node);
earlyReturn = true;
}
}
}
if (earlyReturn) return;
super.decl(node);
}
I think this would break a lot of stylelint rules so I'm not sure if it's a viable solution without turning off or creating exceptions for rules. Or maybe it would work fine if we just add postcsslit to acceptable at-rules, property and values?
thats pretty much what all the old postcss plugins used to do (before custom syntax was a thing), and is roughly what i was planning on doing if the lit team didn't beat me to it with their analyser by then.
it has a lot of edge cases though which, in those cases, lead to either ignoring those cases or an increasingly complex substitution algorithm.
e.g. color: ${c};
i imagine would end up color: ;
- easy enough to cover but its one of many, many examples
even a tiny tokeniser might work better but feels like overkill. i really need to have a think about this too but just haven't had time recently 😞