Font-Awesome/js-packages/@fortawesome/fontawesome-svg-core/import.macro.js
Rob Madole a03a91d681
Release 6.7.0 (#20418)
Co-authored-by: robmadole <robmadole@users.noreply.github.com>
2024-11-18 16:38:46 -05:00

221 lines
7.9 KiB
JavaScript

const { createMacro, MacroError } = require('babel-plugin-macros')
const { addNamed } = require('@babel/helper-module-imports')
const { FAFamilyIds, FAFamilyClassicId, FAFamilyDuotoneId } = require('@fortawesome-internal-tools/fontawesome-icons/canonical')
const { FALegacyStyleIds } = require('@fortawesome-internal-tools/fontawesome-icons/legacy')
module.exports = createMacro(importer, {
configName: 'fontawesome-svg-core'
})
const styles = FALegacyStyleIds
const macroNames = [...styles, 'icon']
const families = FAFamilyIds.map((family) => family.toLowerCase())
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 if (style === 'brands') {
return `@fortawesome/free-brands-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 (FAFamilyDuotoneId === style && family && FAFamilyClassicId !== family) {
throw parentPath.buildCodeFrameError(`duotone cannot be used as a style name with any family other than classic`, MacroError)
}
if ('brands' === style && family && FAFamilyClassicId !== 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 (FAFamilyDuotoneId === style || FAFamilyDuotoneId === family) {
family = undefined
style = FAFamilyDuotoneId
}
if ('brands' === style) {
family = undefined
}
// defaults
if (!style) {
style = 'solid'
}
if (FAFamilyClassicId === 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('')
}