Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
989 changes: 569 additions & 420 deletions index.d.ts

Large diffs are not rendered by default.

153 changes: 110 additions & 43 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,62 @@ const { readFileSync, realpathSync, lstatSync } = require("fs")
const csstree = require("css-tree")
const { createHash } = require("./utilities/hash")

class TranslationError extends Error {
constructor(message) {
super(message)
this.name = "TranslationError"
Error.captureStackTrace(this, this.constructor)
}
}

class FileError extends Error {
constructor(message) {
super(message)
this.name = "FileError"
Error.captureStackTrace(this, this.constructor)
}
}

class RawError extends Error {
constructor(message) {
super(message)
this.name = "RawError"
Error.captureStackTrace(this, this.constructor)
}
}

class CSSError extends Error {
constructor(message) {
super(message)
this.name = "CSSError"
Error.captureStackTrace(this, this.constructor)
}
}

class ImageError extends Error {
constructor(message) {
super(message)
this.name = "ImageError"
Error.captureStackTrace(this, this.constructor)
}
}

class SVGError extends Error {
constructor(message) {
super(message)
this.name = "SVGError"
Error.captureStackTrace(this, this.constructor)
}
}

class JSONError extends Error {
constructor(message) {
super(message)
this.name = "JSONError"
Error.captureStackTrace(this, this.constructor)
}
}

function compile(path) {
const fn = require(path)
return {
Expand Down Expand Up @@ -37,7 +93,7 @@ function compile(path) {
if (
attributes.src ||
["application/json", "application/ld+json"].includes(
attributes.type
attributes.type,
)
) {
node.ignore = false
Expand Down Expand Up @@ -179,7 +235,7 @@ function validateSymlinks(path, base) {
if (!part) continue
current = resolve(current, part)
if (lstatSync(current).isSymbolicLink()) {
throw new Error(`FileError: symlinks are not allowed ("${current}")`)
throw new FileError(`symlinks are not allowed ("${current}")`)
}
}
}
Expand All @@ -191,33 +247,31 @@ function validateFile(path, base) {
const type = extension(normalizedPath)

if (!type) {
throw new Error(`FileError: path "${path}" has no extension`)
throw new FileError(`path "${path}" has no extension`)
}

if (!ALLOWED_READ_EXTENSIONS.includes(type)) {
throw new Error(
`FileError: unsupported file type "${type}" for path "${path}"`
)
throw new FileError(`unsupported file type "${type}" for path "${path}"`)
}

const stats = lstatSync(normalizedPath)
if (!stats.isFile()) {
throw new Error(`FileError: path "${path}" is not a file`)
throw new FileError(`path "${path}" is not a file`)
}

if (stats.isSymbolicLink()) {
throw new Error(`FileError: path "${path}" is a symbolic link`)
throw new FileError(`path "${path}" is a symbolic link`)
}

if (normalizedPath === normalizedBase) {
throw new Error(
`FileError: path "${path}" is the same as the current working directory "${base}"`
throw new FileError(
`path "${path}" is the same as the current working directory "${base}"`,
)
}

if (!normalizedPath.startsWith(normalizedBase + "/")) {
throw new Error(
`FileError: real path "${realPath}" is not within the current working directory "${realBase}"`
throw new FileError(
`real path "${realPath}" is not within the current working directory "${realBase}"`,
)
}
}
Expand All @@ -235,9 +289,7 @@ function readFile(path, encoding) {

return readFileSync(path, encoding)
} catch (exception) {
throw new Error(
`FileError: cannot read file "${path}": ${exception.message}`
)
throw new FileError(`cannot read file "${path}": ${exception.message}`)
}
}

Expand Down Expand Up @@ -330,8 +382,8 @@ const attributes = (options) => {
const left = result.left || "0"
styles.push(
`${decamelize(param)}:${escapeHTML(
`${top} ${right} ${bottom} ${left}`
)}`
`${top} ${right} ${bottom} ${left}`,
)}`,
)
} else if (typeof result === "string" || typeof result === "number") {
styles.push(`${decamelize(param)}:${escapeHTML(result)}`)
Expand Down Expand Up @@ -480,9 +532,7 @@ const sanitizeHTML = (content) => {
raw.load = function (path, options = {}) {
const type = extension(path)
if (!ALLOWED_RAW_EXTENSIONS.includes(type)) {
throw new Error(
`RawError: unsupported raw type "${type}" for path "${path}"`
)
throw new RawError(`unsupported raw type "${type}" for path "${path}"`)
}

let content = readFile(path, "utf8")
Expand Down Expand Up @@ -585,7 +635,7 @@ function css(inputs) {
}
}

function occurences(input, string) {
function occurrences(input, string) {
if (string.length <= 0) {
return input.length + 1
}
Expand All @@ -606,8 +656,8 @@ function occurences(input, string) {
}

const validateCSS = (content, character1, character2) => {
const count1 = occurences(content, character1)
const count2 = occurences(content, character2)
const count1 = occurrences(content, character1)
const count2 = occurrences(content, character2)
if (count1 !== count2) {
return {
valid: false,
Expand Down Expand Up @@ -639,7 +689,7 @@ css.load = function (path) {
const content = readFile(file, "utf8")
const { valid, message } = isCSSValid(content)
if (!valid) {
throw new Error(`CSSError: invalid CSS for path "${file}": ${message}`)
throw new CSSError(`invalid CSS for path "${file}": ${message}`)
}
return css`
${content}
Expand Down Expand Up @@ -871,9 +921,7 @@ function base64({ content, path }) {
nodes.Img.load = function (path) {
const type = extension(path)
if (!ALLOWED_IMAGE_EXTENSIONS.includes(type)) {
throw new Error(
`ImageError: unsupported image type "${type}" for path "${path}"`
)
throw new ImageError(`unsupported image type "${type}" for path "${path}"`)
}
const content = readFile(path, "base64")
return (options) => {
Expand Down Expand Up @@ -926,9 +974,7 @@ const sanitizeSVG = (content) => {
nodes.Svg.load = function (path, options = {}) {
const type = extension(path)
if (type !== "svg") {
throw new Error(
`SVGError: unsupported SVG type "${type}" for path "${path}"`
)
throw new SVGError(`unsupported SVG type "${type}" for path "${path}"`)
}
let content = readFile(path, "utf8")
if (options.sanitize !== false) {
Expand Down Expand Up @@ -968,15 +1014,29 @@ const json = {
try {
return JSON.parse(content)
} catch (exception) {
throw new Error(
`JSONError: cannot parse file "${file}": ${exception.message}`
)
throw new JSONError(`cannot parse file "${file}": ${exception.message}`)
}
},
}

function i18n(translations) {
return function translate(language, key) {
if (key === undefined) {
throw new TranslationError("key is undefined")
}
if (language === undefined) {
throw new TranslationError("language is undefined")
}
if (translations[key] === undefined) {
throw new TranslationError(
`translation [${key}][${language}] is undefined`,
)
}
if (translations[key][language] === undefined) {
throw new TranslationError(
`translation [${key}][${language}] is undefined`,
)
}
return translations[key][language]
}
}
Expand All @@ -995,14 +1055,14 @@ i18n.load = function (path, options = {}) {

return function translate(language, key) {
if (!language) {
throw new Error(`TranslationError: language is undefined`)
throw new TranslationError(`language is undefined`)
}
if (!key) {
throw new Error(`TranslationError: key is undefined`)
throw new TranslationError(`key is undefined`)
}
if (!data[key] || !data[key][language]) {
throw new Error(
`TranslationError: translation [${key}][${language}] is undefined`
throw new TranslationError(
`translation [${key}][${language}] is undefined`,
)
}
return data[key][language]
Expand All @@ -1016,20 +1076,20 @@ function component(fn, { styles, i18n, scripts } = {}) {
}
if (i18n) {
if (!a || !a.language) {
throw new Error(
`TranslationError: language is undefined for component:\n${fn.toString()}`
throw new TranslationError(
`language is undefined for component:\n${fn.toString()}`,
)
}
const { language } = a
function translate(key) {
if (!key) {
throw new Error(
`TranslationError: key is undefined for component:\n${fn.toString()}`
throw new TranslationError(
`key is undefined for component:\n${fn.toString()}`,
)
}
if (!i18n[key] || !i18n[key][language]) {
throw new Error(
`TranslationError: translation [${key}][${language}] is undefined for component:\n${fn.toString()}`
throw new TranslationError(
`translation [${key}][${language}] is undefined for component:\n${fn.toString()}`,
)
}
const translation = i18n[key][language]
Expand Down Expand Up @@ -1069,5 +1129,12 @@ module.exports = {
json,
tag,
i18n,
TranslationError,
FileError,
RawError,
CSSError,
ImageError,
SVGError,
JSONError,
...nodes,
}
Loading