|
| 1 | +import type { |
| 2 | + API, |
| 3 | + ASTPath, |
| 4 | + FileInfo, |
| 5 | + ImportDeclaration, |
| 6 | + JSXElement, |
| 7 | + JSXOpeningElement, |
| 8 | +} from "jscodeshift"; |
| 9 | + |
| 10 | +/** |
| 11 | + * Finds a component import, accounting for sub-components and aliases. |
| 12 | + * Returns the local name of the component. If the component is not found, returns null. |
| 13 | + */ |
| 14 | +function findComponentImport(input: { |
| 15 | + j: API["j"]; |
| 16 | + file: FileInfo; |
| 17 | + name: string; |
| 18 | + packageType?: "react" | "tokens"; |
| 19 | +}) { |
| 20 | + const { j, file, name: _name, packageType = "react" } = input; |
| 21 | + |
| 22 | + /* Account for sub-components */ |
| 23 | + const name = _name.includes(".") ? _name.split(".")[0] : _name; |
| 24 | + |
| 25 | + const root = j(file.source); |
| 26 | + |
| 27 | + let foundName: string | null = null; |
| 28 | + |
| 29 | + root.find(j.ImportDeclaration).forEach((path) => { |
| 30 | + if (packageType === "react" && !isAkselReactImport(path)) { |
| 31 | + return; |
| 32 | + } |
| 33 | + |
| 34 | + if (packageType === "tokens" && !isAkselTokensImport(path)) { |
| 35 | + return; |
| 36 | + } |
| 37 | + |
| 38 | + path.node.specifiers.forEach((specifier) => { |
| 39 | + if ( |
| 40 | + specifier.type === "ImportSpecifier" && |
| 41 | + specifier.imported.name === name |
| 42 | + ) { |
| 43 | + foundName = specifier.local |
| 44 | + ? specifier.local.name |
| 45 | + : specifier.imported.name; |
| 46 | + } |
| 47 | + }); |
| 48 | + }); |
| 49 | + |
| 50 | + return foundName; |
| 51 | +} |
| 52 | + |
| 53 | +/** |
| 54 | + * Finds a JSX element in the AST, accounting for sub-components. |
| 55 | + */ |
| 56 | +function findJSXElement(input: { |
| 57 | + root: ReturnType<API["j"]>; |
| 58 | + j: API["j"]; |
| 59 | + name: string; |
| 60 | + originalName: string; |
| 61 | +}) { |
| 62 | + const { root, j, name, originalName } = input; |
| 63 | + |
| 64 | + const isSubComponent = originalName.includes("."); |
| 65 | + |
| 66 | + const openingElement = ( |
| 67 | + isSubComponent |
| 68 | + ? { |
| 69 | + name: { |
| 70 | + type: "JSXMemberExpression", |
| 71 | + object: { |
| 72 | + name, |
| 73 | + }, |
| 74 | + property: { |
| 75 | + name: originalName.split(".")[1], |
| 76 | + }, |
| 77 | + }, |
| 78 | + } |
| 79 | + : { |
| 80 | + name: { |
| 81 | + type: "JSXIdentifier", |
| 82 | + name, |
| 83 | + }, |
| 84 | + } |
| 85 | + ) as JSXOpeningElement; |
| 86 | + |
| 87 | + return root.find(j.JSXElement, { |
| 88 | + openingElement, |
| 89 | + }); |
| 90 | +} |
| 91 | + |
| 92 | +/** |
| 93 | + * Finds a prop in a JSX element. |
| 94 | + */ |
| 95 | +function findProp(input: { |
| 96 | + j: API["j"]; |
| 97 | + path: ASTPath<JSXElement>; |
| 98 | + name: string; |
| 99 | +}) { |
| 100 | + const { j, path, name } = input; |
| 101 | + |
| 102 | + return j(path).find(j.JSXAttribute, { |
| 103 | + name: { |
| 104 | + name, |
| 105 | + }, |
| 106 | + }); |
| 107 | +} |
| 108 | + |
| 109 | +/** |
| 110 | + * Checks if an import is from @navikt/ds-react. |
| 111 | + */ |
| 112 | +function isAkselReactImport(path: ASTPath<ImportDeclaration>) { |
| 113 | + const importSource = path.node.source.value; |
| 114 | + return ( |
| 115 | + typeof importSource === "string" && |
| 116 | + importSource.startsWith("@navikt/ds-react") |
| 117 | + ); |
| 118 | +} |
| 119 | + |
| 120 | +/** |
| 121 | + * Checks if an import is from @navikt/ds-tokens. |
| 122 | + */ |
| 123 | +function isAkselTokensImport(path: ASTPath<ImportDeclaration>) { |
| 124 | + const importSource = path.node.source.value; |
| 125 | + return ( |
| 126 | + typeof importSource === "string" && |
| 127 | + importSource.startsWith("@navikt/ds-tokens") |
| 128 | + ); |
| 129 | +} |
| 130 | + |
| 131 | +/** |
| 132 | + * Maps old spacing-token values to new space-tokens |
| 133 | + */ |
| 134 | +const legacySpacingTokenMap = new Map<string, string>([ |
| 135 | + ["32", "128"], |
| 136 | + ["24", "96"], |
| 137 | + ["20", "80"], |
| 138 | + ["18", "72"], |
| 139 | + ["16", "64"], |
| 140 | + ["14", "56"], |
| 141 | + ["12", "48"], |
| 142 | + ["11", "44"], |
| 143 | + ["10", "40"], |
| 144 | + ["9", "36"], |
| 145 | + ["8", "32"], |
| 146 | + ["7", "28"], |
| 147 | + ["6", "24"], |
| 148 | + ["5", "20"], |
| 149 | + ["4", "16"], |
| 150 | + ["3", "12"], |
| 151 | + ["2", "8"], |
| 152 | + ["1-alt", "6"], |
| 153 | + ["1", "4"], |
| 154 | + ["05", "2"], |
| 155 | + ["0", "0"], |
| 156 | +]); |
| 157 | + |
| 158 | +function convertSpacingToSpace(spacing: string): string | null { |
| 159 | + return legacySpacingTokenMap.get(spacing) || null; |
| 160 | +} |
| 161 | + |
| 162 | +export { |
| 163 | + findComponentImport, |
| 164 | + findJSXElement, |
| 165 | + findProp, |
| 166 | + convertSpacingToSpace, |
| 167 | + legacySpacingTokenMap, |
| 168 | +}; |
0 commit comments