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

thednp / svg-path-commander / 30933010626

04 Aug 2026 05:12PM UTC coverage: 99.706% (-0.3%) from 100.0%
30933010626

push

github

thednp
chore: restore .vitest-attachments ignore rule

1008 of 1014 branches covered (99.41%)

Branch coverage included in aggregate %.

2040 of 2043 relevant lines covered (99.85%)

16452.43 hits per line

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

95.81
/src/parser/scanParam.ts
1
import { isDigit } from "./isDigit.ts";
2
import { invalidPathValue } from "./invalidPathValue.ts";
3
import { error } from "../util/error.ts";
4
import type { PathParser } from "./pathParser.ts";
5

6
/**
7
 * Validates every character of the path string,
8
 * every path command, negative numbers or floating point numbers.
9
 *
10
 * @param path - The PathParser instance
11
 */
12
export const scanParam = (path: PathParser) => {
2✔
13
  const { max, pathValue, index: start } = path;
16,710✔
14
  let index = start;
16,710✔
15
  let zeroFirst = false;
16,710✔
16
  let hasCeiling = false;
16,710✔
17
  let hasDecimal = false;
16,710✔
18
  let hasDot = false;
16,710✔
19
  let ch;
20

21
  if (index >= max) {
16,710✔
22
    path.err =
3✔
23
      `${error}: ${invalidPathValue} at index ${index}, "pathValue" is missing param`;
24
    return;
3✔
25
  }
26
  ch = pathValue.charCodeAt(index);
16,707✔
27

28
  if (ch === 0x2b /* + */ || ch === 0x2d /* - */) {
16,707✔
29
    index += 1;
2,312✔
30
    // ch = (index < max) ? pathValue.charCodeAt(index) : 0;
31
    ch = pathValue.charCodeAt(index);
2,312✔
32
  }
33

34
  // This logic is shamelessly borrowed from Esprima
35
  // https://github.com/ariya/esprimas
36
  if (!isDigit(ch) && ch !== 0x2e /* . */) {
16,707✔
37
    // path.err = 'SvgPath: param should start with 0..9 or `.` (at pos ' + index + ')';
38
    path.err = `${error}: ${invalidPathValue} at index ${index}, "${
1✔
39
      pathValue[index]
40
    }" is not a number`;
41
    return;
1✔
42
  }
43

44
  if (ch !== 0x2e /* . */) {
16,706✔
45
    zeroFirst = ch === 0x30 /* 0 */;
16,255✔
46
    index += 1;
16,255✔
47

48
    ch = pathValue.charCodeAt(index);
16,255✔
49

50
    if (zeroFirst && index < max) {
16,255✔
51
      // decimal number starts with '0' such as '09' is illegal.
52
      if (ch && isDigit(ch)) {
4,719✔
53
        // path.err = 'SvgPath: numbers started with `0` such as `09`
54
        // are illegal (at pos ' + start + ')';
55
        path.err = `${error}: ${invalidPathValue} at index ${start}, "${
1✔
56
          pathValue[start]
57
        }" illegal number`;
58
        return;
1✔
59
      }
60
    }
61

62
    while (index < max && isDigit(pathValue.charCodeAt(index))) {
16,254✔
63
      index += 1;
4,642✔
64
      hasCeiling = true;
4,642✔
65
    }
66

67
    ch = pathValue.charCodeAt(index);
16,254✔
68
  }
69

70
  if (ch === 0x2e /* . */) {
16,705✔
71
    hasDot = true;
8,830✔
72
    index += 1;
8,830✔
73
    while (isDigit(pathValue.charCodeAt(index))) {
8,830✔
74
      index += 1;
20,208✔
75
      hasDecimal = true;
20,208✔
76
    }
77

78
    ch = pathValue.charCodeAt(index);
8,830✔
79
  }
80

81
  if (ch === 0x65 /* e */ || ch === 0x45 /* E */) {
16,705✔
82
    if (hasDot && !hasCeiling && !hasDecimal) {
18✔
83
      path.err = `${error}: ${invalidPathValue} at index ${index}, "${
1✔
84
        pathValue[index]
85
      }" invalid float exponent`;
86
      return;
1✔
87
    }
88

89
    index += 1;
17✔
90

91
    ch = pathValue.charCodeAt(index);
17✔
92

93
    if (ch === 0x2b /* + */ || ch === 0x2d /* - */) {
17✔
94
      index += 1;
16✔
95
    }
96
    if (index < max && isDigit(pathValue.charCodeAt(index))) {
17✔
97
      while (index < max && isDigit(pathValue.charCodeAt(index))) {
16✔
98
        index += 1;
32✔
99
      }
100
    } else {
101
      path.err = `${error}: ${invalidPathValue} at index ${index}, "${
1✔
102
        pathValue[index]
103
      }" invalid integer exponent`;
104
      return;
1✔
105
    }
106
  }
107

108
  path.index = index;
16,703✔
109
  path.param = scanNumber(pathValue, start, index);
16,703✔
110
};
111

112
/**
113
 * Converts a validated number substring to a Number without
114
 * allocating a new string, matching `+str.slice(start, end)`.
115
 *
116
 * Falls back to the native conversion when the value cannot be
117
 * represented exactly with plain double arithmetic (more than 15
118
 * significant digits or an exponent beyond +-22).
119
 *
120
 * @param str - the path string
121
 * @param start - the index of the first char of the number
122
 * @param end - the index after the last char of the number
123
 * @returns the parsed number
124
 */
125
const scanNumber = (str: string, start: number, end: number): number => {
2✔
126
  let i = start;
16,703✔
127
  let sign = 1;
16,703✔
128
  let param = 0;
16,703✔
129
  let decimals = 0;
16,703✔
130
  let sigDigits = 0;
16,703✔
131
  let inFraction = false;
16,703✔
132
  let expSign = 1;
16,703✔
133
  let exp = 0;
16,703✔
134

135
  const ch = str.charCodeAt(i);
16,703✔
136
  if (ch === 0x2b /* + */) {
16,703!
137
    i += 1;
×
138
  } else if (ch === 0x2d /* - */) {
16,703✔
139
    sign = -1;
2,312✔
140
    i += 1;
2,312✔
141
  }
142

143
  for (; i < end; i += 1) {
16,703✔
144
    const code = str.charCodeAt(i);
49,945✔
145
    if (code === 0x2e /* . */) {
49,945✔
146
      inFraction = true;
8,828✔
147
    } else if (code === 0x65 /* e */ || code === 0x45 /* E */) {
41,117✔
148
      i += 1;
16✔
149
      const expCode = str.charCodeAt(i);
16✔
150
      if (expCode === 0x2b /* + */) {
16!
151
        i += 1;
×
152
      } else if (expCode === 0x2d /* - */) {
16!
153
        expSign = -1;
16✔
154
        i += 1;
16✔
155
      }
156
      for (; i < end; i += 1) {
16✔
157
        exp = exp * 10 + (str.charCodeAt(i) - 0x30);
32✔
158
      }
159
    } else {
160
      const digit = code - 0x30;
41,101✔
161
      if (digit !== 0 || param !== 0) {
41,101✔
162
        sigDigits += 1;
35,988✔
163
      }
164
      param = param * 10 + digit;
41,101✔
165
      if (inFraction) {
41,101✔
166
        decimals += 1;
20,207✔
167
      }
168
    }
169
  }
170

171
  if (sigDigits > 15 || sigDigits === 0) {
16,703✔
172
    return +str.slice(start, end);
2,156✔
173
  }
174

175
  const scale = expSign * exp - decimals;
14,547✔
176
  if (scale > 22 || scale < -22) {
14,547!
177
    return +str.slice(start, end);
×
178
  }
179

180
  if (scale >= 0) {
14,547✔
181
    return sign * param * 10 ** scale;
5,775✔
182
  }
183
  return sign * param / 10 ** -scale;
8,772✔
184
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc