1 | const { createMacro, MacroError } = require('babel-plugin-macros')
|
2 | const { addNamed } = require('@babel/helper-module-imports')
|
3 |
|
4 | module.exports = createMacro(importer, {
|
5 | configName: 'fontawesome-svg-core'
|
6 | })
|
7 |
|
8 | const styles = [
|
9 | 'solid',
|
10 | 'regular',
|
11 | 'light',
|
12 | 'thin',
|
13 | 'duotone',
|
14 | 'brands'
|
15 | ]
|
16 |
|
17 | const macroNames = [
|
18 | ...styles,
|
19 | 'icon'
|
20 | ]
|
21 |
|
22 | const families = [
|
23 | 'classic',
|
24 | 'duotone',
|
25 | 'sharp'
|
26 | ]
|
27 |
|
28 | function 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 |
|
49 | function 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 |
|
62 | function 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 |
|
70 | function 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 |
|
79 | function 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 |
|
140 | function 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 |
|
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 |
|
252 | function 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 |
|
275 | function capitalize (str) {
|
276 | return str[0].toUpperCase() + str.slice(1)
|
277 | }
|
278 |
|
279 | function 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 | }
|