UNPKG

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