• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / luma.gl / 27017296180

05 Jun 2026 01:20PM UTC coverage: 70.704% (+0.1%) from 70.564%
27017296180

push

github

web-flow
chore: Minor API completions and doc improvements (#2665)

8861 of 14167 branches covered (62.55%)

Branch coverage included in aggregate %.

174 of 196 new or added lines in 15 files covered. (88.78%)

1 existing line in 1 file now uncovered.

18353 of 24323 relevant lines covered (75.46%)

4341.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.15
/modules/shadertools/src/lib/preprocessor/preprocessor.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
const DEFINE_NAME_PATTERN = '([a-zA-Z_][a-zA-Z0-9_]*)';
160✔
6
const IF_REGEXP = /^\s*\#\s*if\s+(.+?)\s*(?:\/\/.*)?$/;
160✔
7
const IFDEF_REGEXP = new RegExp(`^\\s*\\#\\s*ifdef\\s*${DEFINE_NAME_PATTERN}\\s*$`);
160✔
8
const IFNDEF_REGEXP = new RegExp(`^\\s*\\#\\s*ifndef\\s*${DEFINE_NAME_PATTERN}\\s*(?:\\/\\/.*)?$`);
160✔
9
const ELSE_REGEXP = /^\s*\#\s*else\s*(?:\/\/.*)?$/;
160✔
10
const ENDIF_REGEXP = /^\s*\#\s*endif\s*$/;
160✔
11
const IFDEF_WITH_COMMENT_REGEXP = new RegExp(
160✔
12
  `^\\s*\\#\\s*ifdef\\s*${DEFINE_NAME_PATTERN}\\s*(?:\\/\\/.*)?$`
13
);
14
const ENDIF_WITH_COMMENT_REGEXP = /^\s*\#\s*endif\s*(?:\/\/.*)?$/;
160✔
15

16
/** Options for the luma.gl shader source preprocessor. */
17
export type PreprocessorOptions = {
18
  /** Boolean or numeric values used by `#if`, `#ifdef`, and `#ifndef` conditionals. */
19
  defines?: Record<string, boolean | number>;
20
};
21

22
/**
23
 * Removes inactive conditional branches from shader source.
24
 * Supports `#ifdef`, `#ifndef`, and simple `#if` expressions using a define name,
25
 * `!NAME`, `defined(NAME)`, `!defined(NAME)`, or a boolean or numeric literal.
26
 * @param source Shader source containing luma.gl-supported conditional directives.
27
 * @param options Defines used while evaluating conditional directives.
28
 * @returns Shader source with inactive branches removed.
29
 */
30
export function preprocess(source: string, options?: PreprocessorOptions): string {
31
  const lines = source.split('\n');
845✔
32
  const output: string[] = [];
845✔
33

34
  const conditionalStack: Array<{
35
    parentActive: boolean;
36
    branchTaken: boolean;
37
    active: boolean;
38
  }> = [];
845✔
39
  let conditional = true;
845✔
40

41
  for (const line of lines) {
845✔
42
    const matchIfExpression = line.match(IF_REGEXP);
157,989✔
43
    const matchIf = line.match(IFDEF_WITH_COMMENT_REGEXP) || line.match(IFDEF_REGEXP);
157,989✔
44
    const matchIfNot = line.match(IFNDEF_REGEXP);
157,989✔
45
    const matchElse = line.match(ELSE_REGEXP);
157,989✔
46
    const matchEnd = line.match(ENDIF_WITH_COMMENT_REGEXP) || line.match(ENDIF_REGEXP);
157,989✔
47

48
    if (matchIfExpression) {
157,989✔
49
      const branchTaken: boolean = evaluateIfExpression(
9✔
50
        matchIfExpression[1],
51
        options?.defines || {}
10✔
52
      );
53
      const active: boolean = conditional && branchTaken;
9✔
54
      conditionalStack.push({parentActive: conditional, branchTaken, active});
9✔
55
      conditional = active;
9✔
56
    } else if (matchIf || matchIfNot) {
157,980✔
57
      const defineName = (matchIf || matchIfNot)?.[1];
48✔
58
      const defineValue: boolean = Boolean(options?.defines?.[defineName!]);
48✔
59
      const branchTaken: boolean = matchIf ? defineValue : !defineValue;
48✔
60
      const active: boolean = conditional && branchTaken;
48✔
61
      conditionalStack.push({parentActive: conditional, branchTaken, active});
48✔
62
      conditional = active;
48✔
63
    } else if (matchElse) {
157,932✔
64
      const currentConditional = conditionalStack[conditionalStack.length - 1];
40✔
65
      if (!currentConditional) {
40✔
66
        throw new Error('Encountered #else without matching #if, #ifdef or #ifndef');
1✔
67
      }
68
      currentConditional.active =
39✔
69
        currentConditional.parentActive && !currentConditional.branchTaken;
78✔
70
      currentConditional.branchTaken = true;
40✔
71
      conditional = currentConditional.active;
40✔
72
    } else if (matchEnd) {
157,892✔
73
      conditionalStack.pop();
55✔
74
      conditional = conditionalStack.length
55✔
75
        ? conditionalStack[conditionalStack.length - 1].active
76
        : true;
77
    } else if (conditional) {
157,837✔
78
      output.push(line);
157,689✔
79
    }
80
  }
81

82
  if (conditionalStack.length > 0) {
843✔
83
    throw new Error('Unterminated conditional block in shader source');
1✔
84
  }
85

86
  return output.join('\n');
842✔
87
}
88

89
function evaluateIfExpression(
90
  expression: string,
91
  defines: Record<string, boolean | number>
92
): boolean {
93
  const trimmedExpression = expression.trim();
9✔
94

95
  if (/^[+-]?\d+(?:\.\d+)?$/.test(trimmedExpression)) {
9!
NEW
96
    return Number(trimmedExpression) !== 0;
×
97
  }
98

99
  if (trimmedExpression === 'true') {
9!
NEW
100
    return true;
×
101
  }
102

103
  if (trimmedExpression === 'false') {
9!
NEW
104
    return false;
×
105
  }
106

107
  const negatedDefineMatch = trimmedExpression.match(new RegExp(`^!\\s*${DEFINE_NAME_PATTERN}$`));
9✔
108
  if (negatedDefineMatch) {
9✔
109
    return !Boolean(defines[negatedDefineMatch[1]]);
1✔
110
  }
111

112
  const defineMatch = trimmedExpression.match(new RegExp(`^${DEFINE_NAME_PATTERN}$`));
8✔
113
  if (defineMatch) {
8✔
114
    return Boolean(defines[defineMatch[1]]);
7✔
115
  }
116

117
  const definedMatch = trimmedExpression.match(
1✔
118
    new RegExp(`^defined\\s*\\(\\s*${DEFINE_NAME_PATTERN}\\s*\\)$`)
119
  );
120
  if (definedMatch) {
1!
NEW
121
    return defines[definedMatch[1]] !== undefined;
×
122
  }
123

124
  const negatedDefinedMatch = trimmedExpression.match(
1✔
125
    new RegExp(`^!\\s*defined\\s*\\(\\s*${DEFINE_NAME_PATTERN}\\s*\\)$`)
126
  );
127
  if (negatedDefinedMatch) {
1!
NEW
128
    return defines[negatedDefinedMatch[1]] === undefined;
×
129
  }
130

131
  throw new Error(`Unsupported #if expression "${expression}"`);
1✔
132
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc