Skip to content

Commit

Permalink
Added error messages for dynamic selectors (#43)
Browse files Browse the repository at this point in the history
Co-authored-by: Luca Schneider <[email protected]>
  • Loading branch information
Mad-Kat and Luca Schneider authored Dec 18, 2023
1 parent 95201e8 commit 71220f4
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 32 deletions.
43 changes: 43 additions & 0 deletions packages/next-yak/loaders/__tests__/tsloader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -407,4 +407,47 @@ const Icon = styled.div\`
line 11: found Expression inside \\"@media (min-width: 640px) { .bar {\\""
`);
});
it("should show error when a dynamic selector is used", async () => {
await expect(() =>
tsloader.call(
loaderContext,
`
import { styled, css } from "next-yak";
const test = "bar";
const Icon = styled.div\`
\${test} {
font-weight: bold;
}
\`
`
)
).rejects.toThrowErrorMatchingInlineSnapshot(`
"/some/special/path/page.tsx: Expressions are not allowed as selectors:
line 7: found \${test}"
`);
});

it("should show error when a dynamic selector is used after a comma", async () => {
await expect(() =>
tsloader.call(
loaderContext,
`
import { styled, css } from "next-yak";
const test = "bar";
const Icon = styled.div\`
\${test}, baz {
font-weight: bold;
}
\`
`
)
).rejects.toThrowErrorMatchingInlineSnapshot(`
"/some/special/path/page.tsx: Expressions are not allowed as selectors:
line 7: found \${test}"
`);
});
});
83 changes: 55 additions & 28 deletions packages/next-yak/loaders/babel-yak-plugin.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,9 @@ module.exports = function (babel, options) {
t.importDeclaration(
[t.importDefaultSpecifier(t.identifier("__styleYak"))],
t.stringLiteral(
`./${fileName}.yak.module.css!=!./${fileName}?./${fileName}.yak.module.css`,
),
),
`./${fileName}.yak.module.css!=!./${fileName}?./${fileName}.yak.module.css`
)
)
);

// Process import specifiers
Expand Down Expand Up @@ -115,15 +115,15 @@ module.exports = function (babel, options) {
const isStyledLiteral =
t.isMemberExpression(tag) &&
t.isIdentifier(
/** @type {babel.types.MemberExpression} */ (tag).object,
/** @type {babel.types.MemberExpression} */ (tag).object
) &&
/** @type {babel.types.Identifier} */ (
/** @type {babel.types.MemberExpression} */ (tag).object
).name === this.localVarNames.styled;
const isStyledCall =
t.isCallExpression(tag) &&
t.isIdentifier(
/** @type {babel.types.CallExpression} */ (tag).callee,
/** @type {babel.types.CallExpression} */ (tag).callee
) &&
/** @type {babel.types.Identifier} */ (
/** @type {babel.types.CallExpression} */ (tag).callee
Expand Down Expand Up @@ -169,15 +169,15 @@ module.exports = function (babel, options) {
astNode.arguments.push(
t.memberExpression(
t.identifier("__styleYak"),
t.identifier(className),
),
t.identifier(className)
)
);
}
return className;
}
return false;
},
t,
t
);

let literalSelectorWasUsed = false;
Expand All @@ -189,9 +189,9 @@ module.exports = function (babel, options) {
localIdent(
variableName,
literalSelectorIndex,
isKeyframesLiteral ? "animation" : "className",
),
),
isKeyframesLiteral ? "animation" : "className"
)
)
);

// Replace the tagged template expression with a call to the 'styled' function
Expand All @@ -200,7 +200,10 @@ module.exports = function (babel, options) {
/** @type {string[]} */
let currentNestingScopes = [];
const quasiTypes = quasis.map((quasi) => {
const classification = quasiClassifier(quasi.value.raw, currentNestingScopes);
const classification = quasiClassifier(
quasi.value.raw,
currentNestingScopes
);
currentNestingScopes = classification.currentNestingScopes;
return classification;
});
Expand All @@ -209,7 +212,26 @@ module.exports = function (babel, options) {
let cssVariablesInlineStyle;

for (let i = 0; i < quasis.length; i++) {
if (quasiTypes[i].empty) {
const type = quasiTypes[i];
if (type.unknownSelector) {
const expression = expressions[i - 1];
if (!expression) {
throw new Error(`Invalid css "${quasis[i].value.raw}"`);
}
let errorText = "Expressions are not allowed as selectors";
const line = expression.loc?.start.line || -1;
if (expression.start && expression.end) {
errorText += `:\n${
line !== -1 ? `line ${line}:` : ""
} found \${${this.file.code.slice(
expression.start,
expression.end
)}}`;
}
throw new InvalidPositionError(errorText);
}

if (type.empty) {
const expression = expressions[i];
if (expression) {
newArguments.add(expression);
Expand All @@ -229,11 +251,7 @@ module.exports = function (babel, options) {
while (i < quasis.length - 1) {
const type = quasiTypes[i];
// expressions after a partial css are converted into css variables
if (
type.unknownSelector ||
type.insideCssValue ||
(isMerging && type.empty)
) {
if (type.insideCssValue || (isMerging && type.empty)) {
isMerging = true;
// expression: `x`
// { style: { --v0: x}}
Expand All @@ -253,16 +271,18 @@ module.exports = function (babel, options) {
}
const relativePath = relative(
rootContext,
resolve(rootContext, resourcePath),
resolve(rootContext, resourcePath)
);
hashedFile = murmurhash2_32_gc(relativePath);
}

// expression: `x`
// { style: { --v0: x}}
cssVariablesInlineStyle.properties.push(
t.objectProperty(
t.stringLiteral(`--🦬${hashedFile}${this.varIndex++}`),
/** @type {babel.types.Expression} */ (expression),
),
/** @type {babel.types.Expression} */ (expression)
)
);
} else if (type.empty) {
// empty quasis can be ignored in typescript
Expand All @@ -272,10 +292,17 @@ module.exports = function (babel, options) {
if (expressions[i]) {
if (quasiTypes[i].currentNestingScopes.length > 0) {
const errorExpression = expressions[i];
const name = errorExpression.type === "Identifier" ? `"${errorExpression.name}"` : "Expression";
const line = errorExpression.loc?.start.line || -1
const name =
errorExpression.type === "Identifier"
? `"${errorExpression.name}"`
: "Expression";
const line = errorExpression.loc?.start.line || -1;
throw new InvalidPositionError(
`Expressions are not allowed inside nested selectors:\n${line !== -1 ? `line ${line}: ` : ""}found ${name} inside "${quasiTypes[i].currentNestingScopes.join(" { ")} {"`,
`Expressions are not allowed inside nested selectors:\n${
line !== -1 ? `line ${line}: ` : ""
}found ${name} inside "${quasiTypes[
i
].currentNestingScopes.join(" { ")} {"`
);
}
newArguments.add(expressions[i]);
Expand All @@ -290,9 +317,9 @@ module.exports = function (babel, options) {
t.objectExpression([
t.objectProperty(
t.stringLiteral(`style`),
cssVariablesInlineStyle,
cssVariablesInlineStyle
),
]),
])
);
}

Expand All @@ -307,7 +334,7 @@ module.exports = function (babel, options) {
className: localIdent(
variableName,
literalSelectorIndex,
"className",
"className"
),
astNode: styledCall,
});
Expand All @@ -326,4 +353,4 @@ class InvalidPositionError extends Error {
}
}

module.exports.InvalidPositionError = InvalidPositionError;
module.exports.InvalidPositionError = InvalidPositionError;
9 changes: 6 additions & 3 deletions packages/next-yak/loaders/lib/quasiClassifier.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const stripCssComments = require("./stripCssComments.cjs");
* Checks a quasiValue and returns its type
*
* - empty: no expressions, no text
* - unknownSelector: starts with a `{` e.g. `{ opacity: 0.5; }`
* - unknownSelector: starts with a `{` e.g. `{ opacity: 0.5; }` or `,` e.g. `, bar { ... }`
* - insideCssValue: does not end with a `{` or `}` or `;` e.g. `color: `
*
* @param {string} quasiValue
Expand Down Expand Up @@ -80,8 +80,11 @@ module.exports = function quasiClassifier(quasiValue, currentNestingScopes) {

return {
empty: false,
unknownSelector: trimmedCssString[0] === "{",
insideCssValue: currentCharacter !== "{" && currentCharacter !== "}" && currentCharacter !== ";",
unknownSelector: trimmedCssString[0] === "{" || trimmedCssString[0] === ",",
insideCssValue:
currentCharacter !== "{" &&
currentCharacter !== "}" &&
currentCharacter !== ";",
currentNestingScopes: newNestingLevel,
};
};
2 changes: 1 addition & 1 deletion packages/next-yak/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "next-yak",
"version": "0.0.19",
"version": "0.0.20",
"type": "module",
"types": "./dist/",
"exports": {
Expand Down

0 comments on commit 71220f4

Please sign in to comment.