mirror of
https://github.com/FortAwesome/Font-Awesome.git
synced 2024-12-25 21:11:30 +08:00
290 lines
7.9 KiB
JavaScript
290 lines
7.9 KiB
JavaScript
const { createMacro, MacroError } = require('babel-plugin-macros')
|
|
const { addNamed } = require('@babel/helper-module-imports')
|
|
|
|
module.exports = createMacro(importer, {
|
|
configName: 'fontawesome-svg-core'
|
|
})
|
|
|
|
const styles = [
|
|
'solid',
|
|
'regular',
|
|
'light',
|
|
'thin',
|
|
'duotone',
|
|
'brands'
|
|
]
|
|
|
|
const macroNames = [
|
|
...styles,
|
|
'icon'
|
|
]
|
|
|
|
const families = [
|
|
'classic',
|
|
'duotone',
|
|
'sharp'
|
|
]
|
|
|
|
function importer ({references, state, babel, source, config}) {
|
|
const license = (config !== undefined ? config.license : 'free')
|
|
|
|
if (!['free', 'pro'].includes(license)) {
|
|
throw new Error(
|
|
"config license must be either 'free' or 'pro'"
|
|
)
|
|
}
|
|
|
|
Object.keys(references).forEach((key) => {
|
|
replace({
|
|
macroName: key,
|
|
license,
|
|
references: references[key],
|
|
state,
|
|
babel,
|
|
source
|
|
})
|
|
})
|
|
}
|
|
|
|
function replace ({ macroName, license, references, state, babel, source }) {
|
|
references.forEach((nodePath) => {
|
|
const {iconName, style, family} = resolveReplacement({ nodePath, babel, state, macroName })
|
|
|
|
const name = `fa${capitalize(camelCase(iconName))}`
|
|
const importFrom = getImport({family, style, license, name})
|
|
|
|
const importName = addNamed(nodePath, name, importFrom)
|
|
|
|
nodePath.parentPath.replaceWith(importName)
|
|
})
|
|
}
|
|
|
|
function getImport ({family, style, license, name}) {
|
|
if (family) {
|
|
return `@fortawesome/${family.toLowerCase()}-${style}-svg-icons/${name}`
|
|
} else {
|
|
return `@fortawesome/${license}-${style}-svg-icons/${name}`
|
|
}
|
|
}
|
|
|
|
function resolveReplacement ({ nodePath, babel, state, macroName }) {
|
|
if('icon' === macroName) {
|
|
return resolveReplacementIcon({ nodePath, babel, state, macroName })
|
|
} else {
|
|
return resolveReplacementLegacyStyle({ nodePath, babel, state, macroName })
|
|
}
|
|
}
|
|
|
|
// The macros corresonding to legacy style names: solid(), regular(), light(), thin(), duotone(), brands().
|
|
function resolveReplacementLegacyStyle({ nodePath, babel, state, macroName }) {
|
|
const { types: t } = babel
|
|
const { parentPath } = nodePath
|
|
|
|
if (!styles.includes(macroName)) {
|
|
throw parentPath.buildCodeFrameError(
|
|
`${macroName} is not a valid macro name. Use one of ${macroNames.join(', ')}`,
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
if (parentPath.node.arguments) {
|
|
if (parentPath.node.arguments.length < 1) {
|
|
throw parentPath.buildCodeFrameError(
|
|
`Received an invalid number of arguments for ${macroName} macro: must be exactly 1`,
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
if (parentPath.node.arguments.length > 1) {
|
|
throw parentPath.buildCodeFrameError(
|
|
`Received an invalid number of arguments for ${macroName} macro: must be exactly 1`,
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
if (
|
|
(parentPath.node.arguments.length === 1 ||
|
|
parentPath.node.arguments.length === 2) &&
|
|
t.isStringLiteral(parentPath.node.arguments[0]) &&
|
|
nodePath.parentPath.node.arguments[0].value.startsWith('fa-')
|
|
) {
|
|
throw parentPath.buildCodeFrameError(
|
|
`Don't begin the icon name with fa-, just use ${nodePath.parentPath.node.arguments[0].value.slice(3)}`,
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
if ((parentPath.node.arguments.length === 1 ||
|
|
parentPath.node.arguments.length === 2) &&
|
|
!t.isStringLiteral(parentPath.node.arguments[0])) {
|
|
throw parentPath.buildCodeFrameError(
|
|
'Only string literals are supported when referencing icons (use a string here instead)',
|
|
MacroError
|
|
)
|
|
}
|
|
} else {
|
|
throw parentPath.buildCodeFrameError(
|
|
'Pass the icon name you would like to import as an argument.',
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
return {
|
|
iconName: nodePath.parentPath.node.arguments[0].value,
|
|
style: macroName,
|
|
family: undefined
|
|
}
|
|
}
|
|
|
|
// The icon() macro.
|
|
function resolveReplacementIcon ({ nodePath, babel, state, macroName }) {
|
|
const { types: t } = babel
|
|
const { parentPath } = nodePath
|
|
|
|
if ('icon' !== macroName) {
|
|
throw parentPath.buildCodeFrameError(
|
|
`${macroName} is not a valid macro name. Use one of ${macroNames.join(', ')}`,
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
if (parentPath.node.arguments.length !== 1) {
|
|
throw parentPath.buildCodeFrameError(
|
|
`Received an invalid number of arguments for ${macroName} macro: must be exactly 1`,
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
if (!t.isObjectExpression(parentPath.node.arguments[0])) {
|
|
throw parentPath.buildCodeFrameError(
|
|
'Only object expressions are supported when referencing icons with this macro, like this: { name: \'star\' }',
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
const properties = (parentPath.node.arguments[0].properties || [])
|
|
|
|
const namePropIndex = properties.findIndex((prop) => 'name' === prop.key.name)
|
|
|
|
const name = namePropIndex >= 0
|
|
? getStringLiteralPropertyValue(t, parentPath, parentPath.node.arguments[0].properties[namePropIndex])
|
|
: undefined
|
|
|
|
if(!name) {
|
|
throw parentPath.buildCodeFrameError(
|
|
'The object argument to the icon() macro must have a name property',
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
const stylePropIndex = properties.findIndex((prop) => 'style' === prop.key.name)
|
|
|
|
let style = stylePropIndex >= 0
|
|
? getStringLiteralPropertyValue(t, parentPath, parentPath.node.arguments[0].properties[stylePropIndex])
|
|
: undefined
|
|
|
|
if(style && !styles.includes(style)) {
|
|
throw parentPath.buildCodeFrameError(
|
|
`Invalid style name: ${style}. It must be one of the following: ${styles.join(', ')}`,
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
const familyPropIndex = properties.findIndex((prop) => 'family' === prop.key.name)
|
|
|
|
let family = familyPropIndex >= 0
|
|
? getStringLiteralPropertyValue(t, parentPath, parentPath.node.arguments[0].properties[familyPropIndex])
|
|
: undefined
|
|
|
|
if(family && !families.includes(family)) {
|
|
throw parentPath.buildCodeFrameError(
|
|
`Invalid family name: ${family}. It must be one of the following: ${families.join(', ')}`,
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
if('duotone' === style && family && 'classic' !== family) {
|
|
throw parentPath.buildCodeFrameError(
|
|
`duotone cannot be used as a style name with any family other than classic`,
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
if('brands' === style && family && 'classic' !== family) {
|
|
throw parentPath.buildCodeFrameError(
|
|
`brands cannot be used as a style name with any family other than classic`,
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
if(family && !style) {
|
|
throw parentPath.buildCodeFrameError(
|
|
`When a family is specified, a style must also be specified`,
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
if('duotone' === style || 'duotone' === family) {
|
|
family = undefined
|
|
style = 'duotone'
|
|
}
|
|
|
|
if('brands' === style) {
|
|
family = undefined
|
|
}
|
|
|
|
// defaults
|
|
if(!style) {
|
|
style = 'solid'
|
|
}
|
|
|
|
if('classic' === family) {
|
|
family = undefined
|
|
}
|
|
|
|
return {
|
|
iconName: name,
|
|
family,
|
|
style
|
|
}
|
|
}
|
|
|
|
function getStringLiteralPropertyValue(t, parentPath, property) {
|
|
if(!('object' === typeof t && 'function' === typeof t.isStringLiteral)) {
|
|
throw Error("ERROR: invalid babel-types arg. This is probably a programming error in import.macro")
|
|
}
|
|
|
|
if(!('object' === typeof property && 'object' === typeof property.value && 'object' == typeof property.key)) {
|
|
throw Error("ERROR: invalid babel property arg. This is probably a programming error in import.macro")
|
|
}
|
|
|
|
if(!('object' === typeof parentPath && 'function' === typeof parentPath.buildCodeFrameError)) {
|
|
throw Error("ERROR: invalid babel parentPath arg. This is probably a programming error in import.macro")
|
|
}
|
|
|
|
if(!t.isStringLiteral(property.value)) {
|
|
throw parentPath.buildCodeFrameError(
|
|
`Only string literals are supported for the ${property.key.name} property (use a string here instead)`,
|
|
MacroError
|
|
)
|
|
}
|
|
|
|
return property.value.value
|
|
}
|
|
|
|
function capitalize (str) {
|
|
return str[0].toUpperCase() + str.slice(1)
|
|
}
|
|
|
|
function camelCase (str) {
|
|
return str
|
|
.split('-')
|
|
.map((s, index) => {
|
|
return (
|
|
(index === 0 ? s[0].toLowerCase() : s[0].toUpperCase()) +
|
|
s.slice(1).toLowerCase()
|
|
)
|
|
})
|
|
.join('')
|
|
}
|