|
| 1 | +// Copyright 2024 Google Inc. Use of this source code is governed by an |
| 2 | +// MIT-style license that can be found in the LICENSE file or at |
| 3 | +// https://opensource.org/licenses/MIT. |
| 4 | + |
| 5 | +import * as postcss from 'postcss'; |
| 6 | +import type {AtRuleRaws} from 'postcss/lib/at-rule'; |
| 7 | + |
| 8 | +import {convertExpression} from '../expression/convert'; |
| 9 | +import {Expression, ExpressionProps} from '../expression'; |
| 10 | +import {fromProps} from '../expression/from-props'; |
| 11 | +import {LazySource} from '../lazy-source'; |
| 12 | +import type * as sassInternal from '../sass-internal'; |
| 13 | +import * as utils from '../utils'; |
| 14 | +import { |
| 15 | + ChildNode, |
| 16 | + ContainerProps, |
| 17 | + NewNode, |
| 18 | + Statement, |
| 19 | + StatementWithChildren, |
| 20 | + appendInternalChildren, |
| 21 | + normalize, |
| 22 | +} from '.'; |
| 23 | +import {_AtRule} from './at-rule-internal'; |
| 24 | +import {interceptIsClean} from './intercept-is-clean'; |
| 25 | +import * as sassParser from '../..'; |
| 26 | + |
| 27 | +/** |
| 28 | + * The set of raws supported by {@link ForRule}. |
| 29 | + * |
| 30 | + * @category Statement |
| 31 | + */ |
| 32 | +export interface ForRuleRaws extends Omit<AtRuleRaws, 'params'> { |
| 33 | + /** The whitespace after {@link ForRule.variable}. */ |
| 34 | + afterVariable?: string; |
| 35 | + |
| 36 | + /** The whitespace after a {@link ForRule}'s `from` keyword. */ |
| 37 | + afterFrom?: string; |
| 38 | + |
| 39 | + /** The whitespace after {@link ForRule.fromExpression}. */ |
| 40 | + afterFromExpression?: string; |
| 41 | + |
| 42 | + /** The whitespace after a {@link ForRule}'s `to` or `through` keyword. */ |
| 43 | + afterTo?: string; |
| 44 | +} |
| 45 | + |
| 46 | +/** |
| 47 | + * The initializer properties for {@link ForRule}. |
| 48 | + * |
| 49 | + * @category Statement |
| 50 | + */ |
| 51 | +export type ForRuleProps = ContainerProps & { |
| 52 | + raws?: ForRuleRaws; |
| 53 | + variable: string; |
| 54 | + fromExpression: Expression | ExpressionProps; |
| 55 | + toExpression: Expression | ExpressionProps; |
| 56 | + to?: 'to' | 'through'; |
| 57 | +}; |
| 58 | + |
| 59 | +/** |
| 60 | + * A `@for` rule. Extends [`postcss.AtRule`]. |
| 61 | + * |
| 62 | + * [`postcss.AtRule`]: https://postcss.org/api/#atrule |
| 63 | + * |
| 64 | + * @category Statement |
| 65 | + */ |
| 66 | +export class ForRule |
| 67 | + extends _AtRule<Partial<ForRuleProps>> |
| 68 | + implements Statement |
| 69 | +{ |
| 70 | + readonly sassType = 'for-rule' as const; |
| 71 | + declare parent: StatementWithChildren | undefined; |
| 72 | + declare raws: ForRuleRaws; |
| 73 | + declare nodes: ChildNode[]; |
| 74 | + |
| 75 | + /** The variabl names assigned for for iteration, without `"$"`. */ |
| 76 | + declare variable: string; |
| 77 | + |
| 78 | + /** |
| 79 | + * The keyword that appears before {@link toExpression}. |
| 80 | + * |
| 81 | + * If this is `"to"`, the loop is exclusive; if it's `"through"`, the loop is |
| 82 | + * inclusive. It defaults to `"to"` when creating a new `ForRule`. |
| 83 | + */ |
| 84 | + declare to: 'to' | 'through'; |
| 85 | + |
| 86 | + get name(): string { |
| 87 | + return 'for'; |
| 88 | + } |
| 89 | + set name(value: string) { |
| 90 | + throw new Error("ForRule.name can't be overwritten."); |
| 91 | + } |
| 92 | + |
| 93 | + get params(): string { |
| 94 | + return ( |
| 95 | + `$${this.variable}${this.raws.afterVariable ?? ' '}from` + |
| 96 | + `${this.raws.afterFrom ?? ' '}${this.fromExpression}` + |
| 97 | + `${this.raws.afterFromExpression ?? ' '}${this.to}` + |
| 98 | + `${this.raws.afterTo ?? ' '}${this.toExpression}` |
| 99 | + ); |
| 100 | + } |
| 101 | + set params(value: string | number | undefined) { |
| 102 | + throw new Error("ForRule.params can't be overwritten."); |
| 103 | + } |
| 104 | + |
| 105 | + /** The expresison whose value is the starting point of the iteration. */ |
| 106 | + get fromExpression(): Expression { |
| 107 | + return this._fromExpression!; |
| 108 | + } |
| 109 | + set fromExpression(fromExpression: Expression | ExpressionProps) { |
| 110 | + if (this._fromExpression) this._fromExpression.parent = undefined; |
| 111 | + if (!('sassType' in fromExpression)) { |
| 112 | + fromExpression = fromProps(fromExpression); |
| 113 | + } |
| 114 | + if (fromExpression) fromExpression.parent = this; |
| 115 | + this._fromExpression = fromExpression; |
| 116 | + } |
| 117 | + private _fromExpression?: Expression; |
| 118 | + |
| 119 | + /** The expresison whose value is the ending point of the iteration. */ |
| 120 | + get toExpression(): Expression { |
| 121 | + return this._toExpression!; |
| 122 | + } |
| 123 | + set toExpression(toExpression: Expression | ExpressionProps) { |
| 124 | + if (this._toExpression) this._toExpression.parent = undefined; |
| 125 | + if (!('sassType' in toExpression)) { |
| 126 | + toExpression = fromProps(toExpression); |
| 127 | + } |
| 128 | + if (toExpression) toExpression.parent = this; |
| 129 | + this._toExpression = toExpression; |
| 130 | + } |
| 131 | + private _toExpression?: Expression; |
| 132 | + |
| 133 | + constructor(defaults: ForRuleProps); |
| 134 | + /** @hidden */ |
| 135 | + constructor(_: undefined, inner: sassInternal.ForRule); |
| 136 | + constructor(defaults?: ForRuleProps, inner?: sassInternal.ForRule) { |
| 137 | + super(defaults as unknown as postcss.AtRuleProps); |
| 138 | + this.nodes ??= []; |
| 139 | + |
| 140 | + if (inner) { |
| 141 | + this.source = new LazySource(inner); |
| 142 | + this.variable = inner.variable; |
| 143 | + this.to = inner.isExclusive ? 'to' : 'through'; |
| 144 | + this.fromExpression = convertExpression(inner.from); |
| 145 | + this.toExpression = convertExpression(inner.to); |
| 146 | + appendInternalChildren(this, inner.children); |
| 147 | + } |
| 148 | + |
| 149 | + this.to ??= 'to'; |
| 150 | + } |
| 151 | + |
| 152 | + clone(overrides?: Partial<ForRuleProps>): this { |
| 153 | + return utils.cloneNode(this, overrides, [ |
| 154 | + 'raws', |
| 155 | + 'variable', |
| 156 | + 'to', |
| 157 | + 'fromExpression', |
| 158 | + 'toExpression', |
| 159 | + ]); |
| 160 | + } |
| 161 | + |
| 162 | + toJSON(): object; |
| 163 | + /** @hidden */ |
| 164 | + toJSON(_: string, inputs: Map<postcss.Input, number>): object; |
| 165 | + toJSON(_?: string, inputs?: Map<postcss.Input, number>): object { |
| 166 | + return utils.toJSON( |
| 167 | + this, |
| 168 | + [ |
| 169 | + 'name', |
| 170 | + 'variable', |
| 171 | + 'to', |
| 172 | + 'fromExpression', |
| 173 | + 'toExpression', |
| 174 | + 'params', |
| 175 | + 'nodes', |
| 176 | + ], |
| 177 | + inputs |
| 178 | + ); |
| 179 | + } |
| 180 | + |
| 181 | + /** @hidden */ |
| 182 | + toString( |
| 183 | + stringifier: postcss.Stringifier | postcss.Syntax = sassParser.scss |
| 184 | + .stringify |
| 185 | + ): string { |
| 186 | + return super.toString(stringifier); |
| 187 | + } |
| 188 | + |
| 189 | + /** @hidden */ |
| 190 | + get nonStatementChildren(): ReadonlyArray<Expression> { |
| 191 | + return [this.fromExpression, this.toExpression]; |
| 192 | + } |
| 193 | + |
| 194 | + /** @hidden */ |
| 195 | + normalize(node: NewNode, sample?: postcss.Node): ChildNode[] { |
| 196 | + return normalize(this, node, sample); |
| 197 | + } |
| 198 | +} |
| 199 | + |
| 200 | +interceptIsClean(ForRule); |
0 commit comments