Skip to content

Commit 54b5864

Browse files
committed
Fix @charset handling
Fixes #436 Closes #438
1 parent fee216e commit 54b5864

File tree

7 files changed

+74
-6
lines changed

7 files changed

+74
-6
lines changed

index.js

+23-6
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,7 @@ function AtImport(options) {
107107

108108
// Strip additional statements.
109109
bundle.forEach(stmt => {
110-
if (stmt.type === "import") {
111-
stmt.node.parent = undefined
112-
styles.append(stmt.node)
113-
} else if (stmt.type === "media") {
110+
if (["charset", "import", "media"].includes(stmt.type)) {
114111
stmt.node.parent = undefined
115112
styles.append(stmt.node)
116113
} else if (stmt.type === "nodes") {
@@ -150,15 +147,33 @@ function AtImport(options) {
150147
}, Promise.resolve())
151148
})
152149
.then(() => {
150+
let charset
153151
const imports = []
154152
const bundle = []
155153

154+
function handleCharset(stmt) {
155+
if (!charset) charset = stmt
156+
// charsets aren't case-sensitive, so convert to lower case to compare
157+
else if (
158+
stmt.node.params.toLowerCase() !==
159+
charset.node.params.toLowerCase()
160+
) {
161+
throw new Error(
162+
`Incompatable @charset statements:
163+
${stmt.node.params} specified in ${stmt.node.source.input.file}
164+
${charset.node.params} specified in ${charset.node.source.input.file}`
165+
)
166+
}
167+
}
168+
156169
// squash statements and their children
157170
statements.forEach(stmt => {
158-
if (stmt.type === "import") {
171+
if (stmt.type === "charset") handleCharset(stmt)
172+
else if (stmt.type === "import") {
159173
if (stmt.children) {
160174
stmt.children.forEach((child, index) => {
161175
if (child.type === "import") imports.push(child)
176+
else if (child.type === "charset") handleCharset(child)
162177
else bundle.push(child)
163178
// For better output
164179
if (index === 0) child.parent = stmt
@@ -169,7 +184,9 @@ function AtImport(options) {
169184
}
170185
})
171186

172-
return imports.concat(bundle)
187+
return charset
188+
? [charset, ...imports.concat(bundle)]
189+
: imports.concat(bundle)
173190
})
174191
}
175192

lib/parse-statements.js

+9
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ module.exports = function (result, styles) {
2929
if (node.type === "atrule") {
3030
if (node.name === "import") stmt = parseImport(result, node)
3131
else if (node.name === "media") stmt = parseMedia(result, node)
32+
else if (node.name === "charset") stmt = parseCharset(result, node)
3233
}
3334

3435
if (stmt) {
@@ -64,6 +65,14 @@ function parseMedia(result, atRule) {
6465
}
6566
}
6667

68+
function parseCharset(result, atRule) {
69+
return {
70+
type: "charset",
71+
node: atRule,
72+
media: [],
73+
}
74+
}
75+
6776
function parseImport(result, atRule) {
6877
let prev = getPrev(atRule)
6978
if (prev) {

test/fixtures/charset-error.css

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
@charset "foobar";
2+
@import "imports/charset.css";

test/fixtures/charset-import.css

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
@charset "UTF-8";
2+
@import "test/fixtures/imports/foo.css";
3+
@import "test/fixtures/imports/charset.css";
4+
bar{}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
@charset "UTF-8";
2+
foo{}
3+
bar{}

test/fixtures/imports/charset.css

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@charset "UTF-8";

test/import.js

+32
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,38 @@ test("should not fail with absolute and local import", t => {
4646
.then(result => t.is(result.css, "@import url('http://');\nfoo{}"))
4747
})
4848

49+
test("should keep @charset first", t => {
50+
const base = '@charset "UTF-8";\n@import url(http://);'
51+
return postcss()
52+
.use(atImport())
53+
.process(base, { from: undefined })
54+
.then(result => {
55+
t.is(result.warnings().length, 0)
56+
t.is(result.css, base)
57+
})
58+
})
59+
60+
test(
61+
"should handle multiple @charset statements",
62+
checkFixture,
63+
"charset-import"
64+
)
65+
66+
test("should error if incompatable @charset statements", t => {
67+
t.plan(2)
68+
const file = "test/fixtures/charset-error.css"
69+
return postcss()
70+
.use(atImport())
71+
.process(readFileSync(file), { from: file })
72+
.catch(err => {
73+
t.truthy(err)
74+
t.regex(
75+
err.message,
76+
/Incompatable @charset statements:.+specified in.+specified in.+/s
77+
)
78+
})
79+
})
80+
4981
test("should error when file not found", t => {
5082
t.plan(1)
5183
const file = "test/fixtures/imports/import-missing.css"

0 commit comments

Comments
 (0)