A stylesheet language written in TypeScript that compiles to CSS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

mixin.ts 2.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import * as AST from '../ast';
  2. import Env, { EnvError } from '../env';
  3. import Token from '../token';
  4. interface Expansion {
  5. rules: (string | EnvError)[];
  6. children: (string | EnvError)[];
  7. }
  8. export class Mixin {
  9. public name: Token;
  10. public parameters: AST.Identifier[];
  11. public children: AST.Child[];
  12. public constructor(
  13. name: Token,
  14. parameters: AST.Identifier[],
  15. children: AST.Child[]
  16. ) {
  17. this.name = name;
  18. this.parameters = parameters;
  19. this.children = children;
  20. }
  21. public compile(env: Env, _opts: AST.Opts) {
  22. env.set(this.name.value, this);
  23. return '';
  24. }
  25. public expand(
  26. env: Env,
  27. opts: AST.Opts,
  28. parents: AST.Selector[],
  29. args: AST.Node[]
  30. ): Expansion | EnvError {
  31. if (this.parameters.length !== args.length) {
  32. return new EnvError(
  33. this.name.line,
  34. `Expected ${this.parameters.length} arguments but received ${
  35. args.length
  36. }`
  37. );
  38. }
  39. const expandedArgs: AST.Node[] = [];
  40. for (const arg of args) {
  41. if (arg instanceof AST.Identifier) {
  42. const lookup = env.get(arg.name);
  43. if (lookup instanceof EnvError) return lookup;
  44. expandedArgs.push(lookup);
  45. }
  46. expandedArgs.push(arg);
  47. }
  48. const mixinEnv = new Env(env);
  49. this.parameters.forEach((param, index) => {
  50. mixinEnv.set(param.name.value, expandedArgs[index]);
  51. });
  52. const rules = this.children
  53. .filter((child: AST.Node): child is AST.Rule => child instanceof AST.Rule)
  54. .map((rule: AST.Rule) => rule.compile(mixinEnv, opts));
  55. const children = this.children
  56. .filter(
  57. (child: AST.Node): child is AST.RuleSet => child instanceof AST.RuleSet
  58. )
  59. .map((ruleSet: AST.RuleSet) => {
  60. ruleSet.selectors.forEach((sel) => (sel.parents = parents));
  61. return ruleSet.compile(mixinEnv, opts);
  62. });
  63. const applicationExpansions: (Expansion | EnvError)[] = this.children
  64. .filter(
  65. (child: AST.Node): child is AST.Application =>
  66. child instanceof AST.Application
  67. )
  68. .map((application: AST.Application) => {
  69. const mixin = mixinEnv.get(application.name.value);
  70. if (mixin instanceof EnvError) {
  71. return mixin;
  72. } else if (mixin instanceof AST.Mixin) {
  73. return mixin.expand(mixinEnv, opts, parents, application.arguments);
  74. } else {
  75. return new EnvError(1, 'Expected a mixin');
  76. }
  77. });
  78. const expansionError = applicationExpansions.find(
  79. (el) => el instanceof EnvError
  80. );
  81. if (expansionError) {
  82. return expansionError;
  83. }
  84. applicationExpansions.forEach((el) => {
  85. if (!(el instanceof EnvError)) {
  86. el.rules.forEach((rule) => {
  87. if (!(rule instanceof EnvError)) {
  88. rules.push(rule);
  89. }
  90. });
  91. }
  92. });
  93. return { rules, children };
  94. }
  95. }