(ast: AstNode[], designSystem: DesignSystem)
| 8 | import { walk, WalkAction } from './walk' |
| 9 | |
| 10 | export function substituteAtApply(ast: AstNode[], designSystem: DesignSystem) { |
| 11 | let features = Features.None |
| 12 | |
| 13 | // Wrap the whole AST in a root rule to make sure there is always a parent |
| 14 | // available for `@apply` at-rules. In some cases, the incoming `ast` just |
| 15 | // contains `@apply` at-rules which means that there is no proper parent to |
| 16 | // rely on. |
| 17 | let root = rule('&', ast) |
| 18 | |
| 19 | // Track all nodes containing `@apply` |
| 20 | let parents = new Set<AstNode>() |
| 21 | |
| 22 | // Track all the dependencies of an `AstNode` |
| 23 | let dependencies = new DefaultMap<AstNode, Set<string>>(() => new Set<string>()) |
| 24 | |
| 25 | // Track all `@utility` definitions by its root (name) |
| 26 | let definitions = new DefaultMap(() => new Set<AstNode>()) |
| 27 | |
| 28 | // Collect all new `@utility` definitions and all `@apply` rules first |
| 29 | walk([root], (node, ctx) => { |
| 30 | if (node.kind !== 'at-rule') return |
| 31 | |
| 32 | // Do not allow `@apply` rules inside `@keyframes` rules. |
| 33 | if (node.name === '@keyframes') { |
| 34 | walk(node.nodes, (child) => { |
| 35 | if (child.kind === 'at-rule' && child.name === '@apply') { |
| 36 | throw new Error(`You cannot use \`@apply\` inside \`@keyframes\`.`) |
| 37 | } |
| 38 | }) |
| 39 | return WalkAction.Skip |
| 40 | } |
| 41 | |
| 42 | // `@utility` defines a utility, which is important information in order to |
| 43 | // do a correct topological sort later on. |
| 44 | if (node.name === '@utility') { |
| 45 | let name = node.params.replace(/-\*$/, '') |
| 46 | definitions.get(name).add(node) |
| 47 | |
| 48 | // In case `@apply` rules are used inside `@utility` rules. |
| 49 | walk(node.nodes, (child) => { |
| 50 | if (child.kind !== 'at-rule' || child.name !== '@apply') return |
| 51 | |
| 52 | parents.add(node) |
| 53 | |
| 54 | for (let dependency of resolveApplyDependencies(child, designSystem)) { |
| 55 | dependencies.get(node).add(dependency) |
| 56 | } |
| 57 | }) |
| 58 | return |
| 59 | } |
| 60 | |
| 61 | // Any other `@apply` node. |
| 62 | if (node.name === '@apply') { |
| 63 | // `@apply` cannot be top-level, so we need to have a parent such that we |
| 64 | // can replace the `@apply` node with the actual utility classes later. |
| 65 | if (ctx.parent === null) return |
| 66 | |
| 67 | features |= Features.AtApply |
no test coverage detected