Skip to content

Commit

Permalink
feat: emit warnings on missing keys (#445)
Browse files Browse the repository at this point in the history
Also adds strict mode which will throw errors on missing keys.
  • Loading branch information
tivac authored Jul 11, 2018
1 parent 5facdff commit 9cc656b
Show file tree
Hide file tree
Showing 11 changed files with 224 additions and 40 deletions.
10 changes: 10 additions & 0 deletions docs/svelte.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,3 +89,13 @@ module.exports = {
]
};
```
## Options
### `strict`
If `true` whenever a missing replacement is found like `{css.doesnotexist}` an error will be throw aborting the file processing. Defaults to `false`.
### Shared Options
All options are passed to the underlying `Processor` instance, see [Options](https://github.com/tivac/modular-css/blob/master/docs/api.md#options).
6 changes: 6 additions & 0 deletions packages/svelte/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,10 @@ module.exports = {
## Options
### `strict`
If `true` whenever a missing replacement is found like `{css.doesnotexist}` an error will be throw aborting the file processing. Defaults to `false`.
### Shared Options
All options are passed to the underlying `Processor` instance, see [Options](https://github.com/tivac/modular-css/blob/master/docs/api.md#options).
69 changes: 50 additions & 19 deletions packages/svelte/src/markup.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,46 @@ const slash = require("slash");
const styleRegex = /<style[\S\s]*?>([\S\s]*?)<\/style>/im;
const scriptRegex = /<script[\S\s]*?>([\S\s]*?)<\/script>/im;
const linkRegex = /<link\b[^<>]*?\bhref=\s*(?:"([^"]+)"|'([^']+)'|([^>\s]+))[^>]*>/im;
const missedRegex = /css\.\w+/gim;

function updateCss({ content, result }) {
function updateCss({ processor, content, result, filename }) {
const exported = result.files[result.file].exports;

const code = content
// Replace simple {css.<key>} values first
.replace(
new RegExp(`{css\\.(${Object.keys(exported).join("|")})}`, "gm"),
(match, key) => exported[key].join(" ")
)
// Then any remaining bare css.<key> values
.replace(
new RegExp(`(\\b)css\\.(${Object.keys(exported).join("|")})(\\b)`, "gm"),
(match, prefix, key, suffix) => `${prefix}"${exported[key].join(" ")}"${suffix}`
);

// Check for any values in the template we couldn't convert
const missed = code.match(missedRegex);

if(missed) {
const { strict } = processor.options;

if(strict) {
throw new Error(`modular-css-svelte: Unable to find "${missed.join(", ")}" in ${filename}`);
}

missed.forEach((key) =>
// eslint-disable-next-line no-console
console.warn(`modular-css-svelte: Unable to find "${key}" in ${filename}`)
);
}

return {
code : content
// Replace simple {css.<key>} values first
.replace(
new RegExp(`{css\\.(${Object.keys(exported).join("|")})}`, "gm"),
(match, key) => exported[key].join(" ")
)
// Then any remaining bare css.<key> values
.replace(
new RegExp(`(\\b)css\\.(${Object.keys(exported).join("|")})(\\b)`, "gm"),
(match, prefix, key, suffix) => `${prefix}"${exported[key].join(" ")}"${suffix}`
),
code,
};
}

async function extractLink({ processor, content, filename, link }) {
// This looks weird, but it's to support multiple types of quotation marks
const href = link[1] || link[2] || link[3];

const external = slash(resolve(path.dirname(filename), href));
Expand All @@ -41,11 +61,13 @@ async function extractLink({ processor, content, filename, link }) {
// To get rollup to watch the CSS file we need to inject an import statement
// if a <script> block already exists hijack it otherwise inject a simple one
if(script) {
const [ tag, contents ] = script;

content = content.replace(
script[0],
script[0].replace(script[1], dedent(`
tag,
tag.replace(contents, dedent(`
import css from ${JSON.stringify(href)};
${script[1]}
${contents}
`))
);
} else {
Expand All @@ -58,7 +80,12 @@ async function extractLink({ processor, content, filename, link }) {

const result = await processor.file(external);

return updateCss({ content, result });
return updateCss({
processor,
content,
result,
filename : href,
});
}

async function extractStyle({ processor, content, filename, style }) {
Expand All @@ -67,16 +94,20 @@ async function extractStyle({ processor, content, filename, style }) {
style[1]
);

return updateCss({ content, result });
return updateCss({
processor,
content,
result,
filename : "<style>",
});
}

/* eslint-disable max-statements */
module.exports = (processor) => ({ content, filename }) => {
const link = content.match(linkRegex);
const style = content.match(styleRegex);

if(link && style) {
throw new Error("modular-css-svelte supports <style> OR <link>, but not both");
throw new Error("modular-css-svelte: use <style> OR <link>, but not both");
}

if(style) {
Expand Down
8 changes: 7 additions & 1 deletion packages/svelte/svelte.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ const markup = require("./src/markup.js");
const style = require("./src/style.js");

module.exports = function(args) {
const processor = new Processor(args);
const config = Object.assign(
Object.create(null),
{ strict : false },
args
);

const processor = new Processor(config);

return {
processor,
Expand Down
86 changes: 81 additions & 5 deletions packages/svelte/test/__snapshots__/svelte.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -205,15 +205,91 @@ exports[`/svelte.js should extract CSS from a <style> tag 2`] = `
"
`;
exports[`/svelte.js should handle invalid references in "<script>" (inline: false, strict: false) 1`] = `
Array [
Array [
"modular-css-svelte: Unable to find \\"css.nuhuh\\" in ./invalid.css",
],
]
`;
exports[`/svelte.js should handle invalid references in "<script>" (inline: false, strict: false) 2`] = `
"
<h2 class=\\"yup\\">Yup</h2>
<script>import css from \\"./invalid.css\\";
console.log(css.nuhuh);</script>
"
`;
exports[`/svelte.js should handle invalid references in "<script>" (inline: false, strict: true) 1`] = `"modular-css-svelte: Unable to find \\"css.nuhuh\\" in ./invalid.css"`;
exports[`/svelte.js should handle invalid references in "<script>" (inline: true, strict: false) 1`] = `
Array [
Array [
"modular-css-svelte: Unable to find \\"css.nuhuh\\" in <style>",
],
]
`;
exports[`/svelte.js should handle invalid references in "<script>" (inline: true, strict: false) 2`] = `
"<h2 class=\\"yup\\">Yup</h2>
<style>/* replaced by modular-css */</style>
<script>
console.log(css.nuhuh);
</script>
"
`;
exports[`/svelte.js should handle invalid references in "<script>" (inline: true, strict: true) 1`] = `"modular-css-svelte: Unable to find \\"css.nuhuh\\" in <style>"`;
exports[`/svelte.js should handle invalid references in "template" (inline: false, strict: false) 1`] = `
Array [
Array [
"modular-css-svelte: Unable to find \\"css.nope\\" in ./invalid.css",
],
]
`;
exports[`/svelte.js should handle invalid references in "template" (inline: false, strict: false) 2`] = `
"
<h1 class=\\"{css.nope}\\">Nope</h1>
<h2 class=\\"yup\\">Yup</h2>
<script>
import css from \\"./invalid.css\\";
</script>"
`;
exports[`/svelte.js should handle invalid references in "template" (inline: false, strict: true) 1`] = `"modular-css-svelte: Unable to find \\"css.nope\\" in ./invalid.css"`;
exports[`/svelte.js should handle invalid references in "template" (inline: true, strict: false) 1`] = `
Array [
Array [
"modular-css-svelte: Unable to find \\"css.nope\\" in <style>",
],
]
`;
exports[`/svelte.js should handle invalid references in "template" (inline: true, strict: false) 2`] = `
"<h1 class=\\"{css.nope}\\">Nope</h1>
<h2 class=\\"yup\\">Yup</h2>
<style>/* replaced by modular-css */</style>
"
`;
exports[`/svelte.js should handle invalid references in "template" (inline: true, strict: true) 1`] = `"modular-css-svelte: Unable to find \\"css.nope\\" in <style>"`;
exports[`/svelte.js should ignore files without <style> blocks 1`] = `
"<h1>Hello</h1>
<script>console.log(\\"output\\")</script>"
`;
exports[`/svelte.js should ignore files without <style> blocks 2`] = `""`;
exports[`/svelte.js should ignore invalid {css.<key>} 1`] = `
"<h1 class=\\"{css.nope}\\">Hello</h1>
<h2 class=\\"yup\\">World</h2>
<style>/* replaced by modular-css */</style>"
`;
exports[`/svelte.js should throw on both <style> and <link> in one file 1`] = `"modular-css-svelte: use <style> OR <link>, but not both"`;
7 changes: 7 additions & 0 deletions packages/svelte/test/specimens/invalid-external-script.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<link rel="stylesheet" href="./invalid.css" />

<h2 class="{css.yup}">Yup</h2>

<script>
console.log(css.nuhuh);
</script>
4 changes: 4 additions & 0 deletions packages/svelte/test/specimens/invalid-external-template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<link rel="stylesheet" href="./invalid.css" />

<h1 class="{css.nope}">Nope</h1>
<h2 class="{css.yup}">Yup</h2>
9 changes: 9 additions & 0 deletions packages/svelte/test/specimens/invalid-inline-script.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<h2 class="{css.yup}">Yup</h2>

<style>
.yup { color: red; }
</style>

<script>
console.log(css.nuhuh);
</script>
6 changes: 6 additions & 0 deletions packages/svelte/test/specimens/invalid-inline-template.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<h1 class="{css.nope}">Nope</h1>
<h2 class="{css.yup}">Yup</h2>

<style>
.yup { color: red; }
</style>
3 changes: 3 additions & 0 deletions packages/svelte/test/specimens/invalid.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.yup {
color: yellow;
}
56 changes: 41 additions & 15 deletions packages/svelte/test/svelte.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,37 +72,63 @@ describe("/svelte.js", () => {
expect(output.css).toMatchSnapshot();
});

it("should ignore invalid {css.<key>}", async () => {
it.each`
title | inline | strict | specimen
${"<script>"} | ${true} | ${true} | ${"invalid-inline-script.html"}
${"template"} | ${true} | ${true} | ${"invalid-inline-template.html"}
${"<script>"} | ${true} | ${false} | ${"invalid-inline-script.html"}
${"template"} | ${true} | ${false} | ${"invalid-inline-template.html"}
${"<script>"} | ${false} | ${false} | ${"invalid-external-script.html"}
${"template"} | ${false} | ${false} | ${"invalid-external-template.html"}
${"<script>"} | ${false} | ${true} | ${"invalid-external-script.html"}
${"template"} | ${false} | ${true} | ${"invalid-external-template.html"}
`("should handle invalid references in $title (inline: $inline, strict: $strict)", async ({ strict, specimen }) => {
const spy = jest.spyOn(global.console, "warn");

spy.mockImplementation(() => { /* NO-OP */ });

const filename = require.resolve(`./specimens/${specimen}`);

const { preprocess } = plugin({
namer,
strict,
});

if(strict) {
await expect(
svelte.preprocess(
fs.readFileSync(filename, "utf8"),
Object.assign({}, preprocess, { filename })
)
).rejects.toThrowErrorMatchingSnapshot();

return;
}

const processed = await svelte.preprocess(
dedent(`
<h1 class="{css.nope}">Hello</h1>
<h2 class="{css.yup}">World</h2>
<style>.yup { color: red; }</style>
`),
Object.assign({}, preprocess, { filename : require.resolve("./specimens/style.html") })
fs.readFileSync(filename, "utf8"),
Object.assign({}, preprocess, { filename })
);

expect(spy).toHaveBeenCalled();
expect(spy.mock.calls).toMatchSnapshot();

expect(processed.toString()).toMatchSnapshot();
});

it("should throw on both <style> and <link> in one file", () => {
it("should throw on both <style> and <link> in one file", async () => {
const { preprocess } = plugin({
css : "./packages/svelte/test/output/svelte.css",
namer,
});

const filename = require.resolve("./specimens/both.html");

return svelte.preprocess(
fs.readFileSync(filename, "utf8"),
Object.assign({}, preprocess, { filename })
)
.catch((error) =>
expect(error.message).toMatch("modular-css-svelte supports <style> OR <link>, but not both")
);
await expect(
svelte.preprocess(
fs.readFileSync(filename, "utf8"),
Object.assign({}, preprocess, { filename })
)
).rejects.toThrowErrorMatchingSnapshot();
});
});

0 comments on commit 9cc656b

Please sign in to comment.