UNPKG

8.1 kBJavaScriptView Raw
1const { createMacro, MacroError } = require('babel-plugin-macros')
2const { addNamed } = require('@babel/helper-module-imports')
3
4module.exports = createMacro(importer, {
5 configName: 'fontawesome-svg-core'
6})
7
8const styles = [
9 'solid',
10 'regular',
11 'light',
12 'thin',
13 'duotone',
14 'brands'
15]
16
17const macroNames = [
18 ...styles,
19 'icon'
20]
21
22const families = [
23 'classic',
24 'duotone',
25 'sharp'
26]
27
28function importer ({references, state, babel, source, config}) {
29 const license = (config !== undefined ? config.license : 'free')
30
31 if (!['free', 'pro'].includes(license)) {
32 throw new Error(
33 "config license must be either 'free' or 'pro'"
34 )
35 }
36
37 Object.keys(references).forEach((key) => {
38 replace({
39 macroName: key,
40 license,
41 references: references[key],
42 state,
43 babel,
44 source
45 })
46 })
47}
48
49function replace ({ macroName, license, references, state, babel, source }) {
50 references.forEach((nodePath) => {
51 const {iconName, style, family} = resolveReplacement({ nodePath, babel, state, macroName })
52
53 const name = `fa${capitalize(camelCase(iconName))}`
54 const importFrom = getImport({family, style, license, name})
55
56 const importName = addNamed(nodePath, name, importFrom)
57
58 nodePath.parentPath.replaceWith(importName)
59 })
60}
61
62function getImport ({family, style, license, name}) {
63 if (family) {
64 return `@fortawesome/${family.toLowerCase()}-${style}-svg-icons/${name}`
65 } else {
66 return `@fortawesome/${license}-${style}-svg-icons/${name}`
67 }
68}
69
70function resolveReplacement ({ nodePath, babel, state, macroName }) {
71 if('icon' === macroName) {
72 return resolveReplacementIcon({ nodePath, babel, state, macroName })
73 } else {
74 return resolveReplacementLegacyStyle({ nodePath, babel, state, macroName })
75 }
76}
77
78// The macros corresonding to legacy style names: solid(), regular(), light(), thin(), duotone(), brands().
79function resolveReplacementLegacyStyle({ nodePath, babel, state, macroName }) {
80 const { types: t } = babel
81 const { parentPath } = nodePath
82
83 if (!styles.includes(macroName)) {
84 throw parentPath.buildCodeFrameError(
85 `${macroName} is not a valid macro name. Use one of ${macroNames.join(', ')}`,
86 MacroError
87 )
88 }
89
90 if (parentPath.node.arguments) {
91 if (parentPath.node.arguments.length < 1) {
92 throw parentPath.buildCodeFrameError(
93 `Received an invalid number of arguments for ${macroName} macro: must be exactly 1`,
94 MacroError
95 )
96 }
97
98 if (parentPath.node.arguments.length > 1) {
99 throw parentPath.buildCodeFrameError(
100 `Received an invalid number of arguments for ${macroName} macro: must be exactly 1`,
101 MacroError
102 )
103 }
104
105 if (
106 (parentPath.node.arguments.length === 1 ||
107 parentPath.node.arguments.length === 2) &&
108 t.isStringLiteral(parentPath.node.arguments[0]) &&
109 nodePath.parentPath.node.arguments[0].value.startsWith('fa-')
110 ) {
111 throw parentPath.buildCodeFrameError(
112 `Don't begin the icon name with fa-, just use ${nodePath.parentPath.node.arguments[0].value.slice(3)}`,
113 MacroError
114 )
115 }
116
117 if ((parentPath.node.arguments.length === 1 ||
118 parentPath.node.arguments.length === 2) &&
119 !t.isStringLiteral(parentPath.node.arguments[0])) {
120 throw parentPath.buildCodeFrameError(
121 'Only string literals are supported when referencing icons (use a string here instead)',
122 MacroError
123 )
124 }
125 } else {
126 throw parentPath.buildCodeFrameError(
127 'Pass the icon name you would like to import as an argument.',
128 MacroError
129 )
130 }
131
132 return {
133 iconName: nodePath.parentPath.node.arguments[0].value,
134 style: macroName,
135 family: undefined
136 }
137}
138
139// The icon() macro.
140function resolveReplacementIcon ({ nodePath, babel, state, macroName }) {
141 const { types: t } = babel
142 const { parentPath } = nodePath
143
144 if ('icon' !== macroName) {
145 throw parentPath.buildCodeFrameError(
146 `${macroName} is not a valid macro name. Use one of ${macroNames.join(', ')}`,
147 MacroError
148 )
149 }
150
151 if (parentPath.node.arguments.length !== 1) {
152 throw parentPath.buildCodeFrameError(
153 `Received an invalid number of arguments for ${macroName} macro: must be exactly 1`,
154 MacroError
155 )
156 }
157
158 if (!t.isObjectExpression(parentPath.node.arguments[0])) {
159 throw parentPath.buildCodeFrameError(
160 'Only object expressions are supported when referencing icons with this macro, like this: { name: \'star\' }',
161 MacroError
162 )
163 }
164
165 const properties = (parentPath.node.arguments[0].properties || [])
166
167 const namePropIndex = properties.findIndex((prop) => 'name' === prop.key.name)
168
169 const name = namePropIndex >= 0
170 ? getStringLiteralPropertyValue(t, parentPath, parentPath.node.arguments[0].properties[namePropIndex])
171 : undefined
172
173 if(!name) {
174 throw parentPath.buildCodeFrameError(
175 'The object argument to the icon() macro must have a name property',
176 MacroError
177 )
178 }
179
180 const stylePropIndex = properties.findIndex((prop) => 'style' === prop.key.name)
181
182 let style = stylePropIndex >= 0
183 ? getStringLiteralPropertyValue(t, parentPath, parentPath.node.arguments[0].properties[stylePropIndex])
184 : undefined
185
186 if(style && !styles.includes(style)) {
187 throw parentPath.buildCodeFrameError(
188 `Invalid style name: ${style}. It must be one of the following: ${styles.join(', ')}`,
189 MacroError
190 )
191 }
192
193 const familyPropIndex = properties.findIndex((prop) => 'family' === prop.key.name)
194
195 let family = familyPropIndex >= 0
196 ? getStringLiteralPropertyValue(t, parentPath, parentPath.node.arguments[0].properties[familyPropIndex])
197 : undefined
198
199 if(family && !families.includes(family)) {
200 throw parentPath.buildCodeFrameError(
201 `Invalid family name: ${family}. It must be one of the following: ${families.join(', ')}`,
202 MacroError
203 )
204 }
205
206 if('duotone' === style && family && 'classic' !== family) {
207 throw parentPath.buildCodeFrameError(
208 `duotone cannot be used as a style name with any family other than classic`,
209 MacroError
210 )
211 }
212
213 if('brands' === style && family && 'classic' !== family) {
214 throw parentPath.buildCodeFrameError(
215 `brands cannot be used as a style name with any family other than classic`,
216 MacroError
217 )
218 }
219
220 if(family && !style) {
221 throw parentPath.buildCodeFrameError(
222 `When a family is specified, a style must also be specified`,
223 MacroError
224 )
225 }
226
227 if('duotone' === style || 'duotone' === family) {
228 family = undefined
229 style = 'duotone'
230 }
231
232 if('brands' === style) {
233 family = undefined
234 }
235
236 // defaults
237 if(!style) {
238 style = 'solid'
239 }
240
241 if('classic' === family) {
242 family = undefined
243 }
244
245 return {
246 iconName: name,
247 family,
248 style
249 }
250}
251
252function getStringLiteralPropertyValue(t, parentPath, property) {
253 if(!('object' === typeof t && 'function' === typeof t.isStringLiteral)) {
254 throw Error("ERROR: invalid babel-types arg. This is probably a programming error in import.macro")
255 }
256
257 if(!('object' === typeof property && 'object' === typeof property.value && 'object' == typeof property.key)) {
258 throw Error("ERROR: invalid babel property arg. This is probably a programming error in import.macro")
259 }
260
261 if(!('object' === typeof parentPath && 'function' === typeof parentPath.buildCodeFrameError)) {
262 throw Error("ERROR: invalid babel parentPath arg. This is probably a programming error in import.macro")
263 }
264
265 if(!t.isStringLiteral(property.value)) {
266 throw parentPath.buildCodeFrameError(
267 `Only string literals are supported for the ${property.key.name} property (use a string here instead)`,
268 MacroError
269 )
270 }
271
272 return property.value.value
273}
274
275function capitalize (str) {
276 return str[0].toUpperCase() + str.slice(1)
277}
278
279function camelCase (str) {
280 return str
281 .split('-')
282 .map((s, index) => {
283 return (
284 (index === 0 ? s[0].toLowerCase() : s[0].toUpperCase()) +
285 s.slice(1).toLowerCase()
286 )
287 })
288 .join('')
289}