sf-agentpmd 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +7 -5
- package/dist/commands/agentpmd/analyze.js +4141 -109
- package/dist/commands/agentpmd/analyze.js.map +7 -1
- package/dist/commands/agentpmd/install-skill.js +27 -30
- package/dist/commands/agentpmd/install-skill.js.map +7 -1
- package/dist/index.js +4155 -3
- package/dist/index.js.map +7 -1
- package/oclif.manifest.json +1 -1
- package/package.json +7 -8
- package/dist/analyzer/action-references.d.ts +0 -21
- package/dist/analyzer/action-references.js +0 -130
- package/dist/analyzer/action-references.js.map +0 -1
- package/dist/analyzer/analyze.d.ts +0 -43
- package/dist/analyzer/analyze.js +0 -222
- package/dist/analyzer/analyze.js.map +0 -1
- package/dist/analyzer/apex-analyze.d.ts +0 -14
- package/dist/analyzer/apex-analyze.js +0 -60
- package/dist/analyzer/apex-analyze.js.map +0 -1
- package/dist/analyzer/apex-complexity.d.ts +0 -27
- package/dist/analyzer/apex-complexity.js +0 -133
- package/dist/analyzer/apex-complexity.js.map +0 -1
- package/dist/analyzer/apex-parse.d.ts +0 -39
- package/dist/analyzer/apex-parse.js +0 -32
- package/dist/analyzer/apex-parse.js.map +0 -1
- package/dist/analyzer/apex-resolve.d.ts +0 -32
- package/dist/analyzer/apex-resolve.js +0 -59
- package/dist/analyzer/apex-resolve.js.map +0 -1
- package/dist/analyzer/complexity.d.ts +0 -30
- package/dist/analyzer/complexity.js +0 -126
- package/dist/analyzer/complexity.js.map +0 -1
- package/dist/analyzer/parse.d.ts +0 -51
- package/dist/analyzer/parse.js +0 -143
- package/dist/analyzer/parse.js.map +0 -1
- package/dist/analyzer/project.d.ts +0 -12
- package/dist/analyzer/project.js +0 -51
- package/dist/analyzer/project.js.map +0 -1
- package/dist/analyzer/types.d.ts +0 -76
- package/dist/analyzer/types.js +0 -2
- package/dist/analyzer/types.js.map +0 -1
- package/dist/commands/agentpmd/analyze.d.ts +0 -20
- package/dist/commands/agentpmd/install-skill.d.ts +0 -11
- package/dist/index.d.ts +0 -3
- package/dist/renderers/csv.d.ts +0 -6
- package/dist/renderers/csv.js +0 -78
- package/dist/renderers/csv.js.map +0 -1
- package/dist/renderers/index.d.ts +0 -8
- package/dist/renderers/index.js +0 -25
- package/dist/renderers/index.js.map +0 -1
- package/dist/renderers/markdown.d.ts +0 -12
- package/dist/renderers/markdown.js +0 -233
- package/dist/renderers/markdown.js.map +0 -1
- package/dist/renderers/options.d.ts +0 -20
- package/dist/renderers/options.js +0 -2
- package/dist/renderers/options.js.map +0 -1
- package/dist/renderers/sarif.d.ts +0 -3
- package/dist/renderers/sarif.js +0 -131
- package/dist/renderers/sarif.js.map +0 -1
- package/dist/renderers/text.d.ts +0 -3
- package/dist/renderers/text.js +0 -243
- package/dist/renderers/text.js.map +0 -1
- package/vendor/agentscript-parser-javascript/dist/cst-node.d.ts +0 -83
- package/vendor/agentscript-parser-javascript/dist/cst-node.js +0 -238
- package/vendor/agentscript-parser-javascript/dist/errors.d.ts +0 -34
- package/vendor/agentscript-parser-javascript/dist/errors.js +0 -74
- package/vendor/agentscript-parser-javascript/dist/expressions.d.ts +0 -36
- package/vendor/agentscript-parser-javascript/dist/expressions.js +0 -682
- package/vendor/agentscript-parser-javascript/dist/highlighter.d.ts +0 -24
- package/vendor/agentscript-parser-javascript/dist/highlighter.js +0 -260
- package/vendor/agentscript-parser-javascript/dist/index.d.ts +0 -29
- package/vendor/agentscript-parser-javascript/dist/index.js +0 -35
- package/vendor/agentscript-parser-javascript/dist/lexer.d.ts +0 -60
- package/vendor/agentscript-parser-javascript/dist/lexer.js +0 -630
- package/vendor/agentscript-parser-javascript/dist/parse-mapping.d.ts +0 -46
- package/vendor/agentscript-parser-javascript/dist/parse-mapping.js +0 -549
- package/vendor/agentscript-parser-javascript/dist/parse-sequence.d.ts +0 -10
- package/vendor/agentscript-parser-javascript/dist/parse-sequence.js +0 -118
- package/vendor/agentscript-parser-javascript/dist/parse-statements.d.ts +0 -15
- package/vendor/agentscript-parser-javascript/dist/parse-statements.js +0 -519
- package/vendor/agentscript-parser-javascript/dist/parse-templates.d.ts +0 -15
- package/vendor/agentscript-parser-javascript/dist/parse-templates.js +0 -323
- package/vendor/agentscript-parser-javascript/dist/parser.d.ts +0 -65
- package/vendor/agentscript-parser-javascript/dist/parser.js +0 -163
- package/vendor/agentscript-parser-javascript/dist/recovery.d.ts +0 -51
- package/vendor/agentscript-parser-javascript/dist/recovery.js +0 -199
- package/vendor/agentscript-parser-javascript/dist/token.d.ts +0 -58
- package/vendor/agentscript-parser-javascript/dist/token.js +0 -62
- package/vendor/agentscript-parser-javascript/package.json +0 -19
- package/vendor/agentscript-types/dist/comment.d.ts +0 -11
- package/vendor/agentscript-types/dist/comment.js +0 -10
- package/vendor/agentscript-types/dist/cst.d.ts +0 -7
- package/vendor/agentscript-types/dist/cst.js +0 -8
- package/vendor/agentscript-types/dist/diagnostic.d.ts +0 -34
- package/vendor/agentscript-types/dist/diagnostic.js +0 -23
- package/vendor/agentscript-types/dist/index.d.ts +0 -9
- package/vendor/agentscript-types/dist/index.js +0 -10
- package/vendor/agentscript-types/dist/position.d.ts +0 -11
- package/vendor/agentscript-types/dist/position.js +0 -16
- package/vendor/agentscript-types/dist/syntax-node.d.ts +0 -39
- package/vendor/agentscript-types/dist/syntax-node.js +0 -8
- package/vendor/agentscript-types/package.json +0 -15
|
@@ -1,630 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) 2026, Salesforce, Inc.
|
|
3
|
-
* All rights reserved.
|
|
4
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
-
* For full license text, see the LICENSE file in the repo root or https://www.apache.org/licenses/LICENSE-2.0
|
|
6
|
-
*/
|
|
7
|
-
/**
|
|
8
|
-
* Indentation-aware lexer for AgentScript.
|
|
9
|
-
*
|
|
10
|
-
* Produces a flat Token[] array including synthetic INDENT, DEDENT, and NEWLINE
|
|
11
|
-
* tokens. Keywords are emitted as ID — the parser checks token text.
|
|
12
|
-
*
|
|
13
|
-
* Indentation: space = 1 unit, tab = 3 units (matching scanner.c).
|
|
14
|
-
*/
|
|
15
|
-
import invariant from 'tiny-invariant';
|
|
16
|
-
import { TokenKind } from './token.js';
|
|
17
|
-
// ---------------------------------------------------------------------------
|
|
18
|
-
// Character classification via charCode (replaces regex hot-path checks)
|
|
19
|
-
// ---------------------------------------------------------------------------
|
|
20
|
-
const CH_TAB = 9; // \t
|
|
21
|
-
const CH_LF = 10; // \n
|
|
22
|
-
const CH_CR = 13; // \r
|
|
23
|
-
const CH_SPACE = 32;
|
|
24
|
-
const CH_BANG = 33; // !
|
|
25
|
-
const CH_DQUOTE = 34; // "
|
|
26
|
-
const CH_HASH = 35; // #
|
|
27
|
-
const CH_DASH = 45; // -
|
|
28
|
-
const CH_DOT = 46; // .
|
|
29
|
-
const CH_0 = 48;
|
|
30
|
-
const CH_9 = 57;
|
|
31
|
-
const CH_LT = 60; // <
|
|
32
|
-
const CH_EQ = 61; // =
|
|
33
|
-
const CH_GT = 62; // >
|
|
34
|
-
const CH_A = 65;
|
|
35
|
-
const CH_Z = 90;
|
|
36
|
-
const CH_BACKSLASH = 92; // \
|
|
37
|
-
const CH_UNDERSCORE = 95; // _
|
|
38
|
-
const CH_a = 97;
|
|
39
|
-
const CH_z = 122;
|
|
40
|
-
const CH_LBRACE = 123; // {
|
|
41
|
-
const CH_NUL = 0;
|
|
42
|
-
function isIdStart(c) {
|
|
43
|
-
return ((c >= CH_A && c <= CH_Z) || (c >= CH_a && c <= CH_z) || c === CH_UNDERSCORE);
|
|
44
|
-
}
|
|
45
|
-
function isIdCont(c) {
|
|
46
|
-
return isIdStart(c) || (c >= CH_0 && c <= CH_9);
|
|
47
|
-
}
|
|
48
|
-
function isDigit(c) {
|
|
49
|
-
return c >= CH_0 && c <= CH_9;
|
|
50
|
-
}
|
|
51
|
-
function isHorizontalWs(c) {
|
|
52
|
-
return c === CH_SPACE || c === CH_TAB;
|
|
53
|
-
}
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
// charCode-indexed single-char token lookup (replaces Record<string, TokenKind>)
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
const SINGLE_CHAR_TOKENS = new Array(128).fill(0);
|
|
58
|
-
SINGLE_CHAR_TOKENS[43] = TokenKind.PLUS; // +
|
|
59
|
-
SINGLE_CHAR_TOKENS[CH_DASH] = TokenKind.MINUS; // -
|
|
60
|
-
SINGLE_CHAR_TOKENS[42] = TokenKind.STAR; // *
|
|
61
|
-
SINGLE_CHAR_TOKENS[47] = TokenKind.SLASH; // /
|
|
62
|
-
// % is not a valid operator in AgentScript (tree-sitter parity)
|
|
63
|
-
SINGLE_CHAR_TOKENS[CH_DOT] = TokenKind.DOT; // .
|
|
64
|
-
SINGLE_CHAR_TOKENS[44] = TokenKind.COMMA; // ,
|
|
65
|
-
SINGLE_CHAR_TOKENS[58] = TokenKind.COLON; // :
|
|
66
|
-
SINGLE_CHAR_TOKENS[61] = TokenKind.EQ; // =
|
|
67
|
-
SINGLE_CHAR_TOKENS[60] = TokenKind.LT; // <
|
|
68
|
-
SINGLE_CHAR_TOKENS[CH_GT] = TokenKind.GT; // >
|
|
69
|
-
SINGLE_CHAR_TOKENS[124] = TokenKind.PIPE; // |
|
|
70
|
-
SINGLE_CHAR_TOKENS[64] = TokenKind.AT; // @
|
|
71
|
-
SINGLE_CHAR_TOKENS[40] = TokenKind.LPAREN; // (
|
|
72
|
-
SINGLE_CHAR_TOKENS[41] = TokenKind.RPAREN; // )
|
|
73
|
-
SINGLE_CHAR_TOKENS[91] = TokenKind.LBRACKET; // [
|
|
74
|
-
SINGLE_CHAR_TOKENS[93] = TokenKind.RBRACKET; // ]
|
|
75
|
-
SINGLE_CHAR_TOKENS[CH_LBRACE] = TokenKind.LBRACE; // {
|
|
76
|
-
SINGLE_CHAR_TOKENS[125] = TokenKind.RBRACE; // }
|
|
77
|
-
export class Lexer {
|
|
78
|
-
source;
|
|
79
|
-
offset = 0;
|
|
80
|
-
row = 0;
|
|
81
|
-
col = 0;
|
|
82
|
-
tokens = [];
|
|
83
|
-
indentStack = [0];
|
|
84
|
-
/** True when the current line started with `|` (template line). */
|
|
85
|
-
onTemplateLine = false;
|
|
86
|
-
/** Indent level of the line containing `|`. Content deeper than this is template content. */
|
|
87
|
-
templateBaseIndent = -1;
|
|
88
|
-
/** Nested brace depth inside a template expression (for `{` inside `{!...}`). -1 means not inside a template expression. */
|
|
89
|
-
templateExprBraceDepth = -1;
|
|
90
|
-
get inTemplateExpr() {
|
|
91
|
-
return this.templateExprBraceDepth >= 0;
|
|
92
|
-
}
|
|
93
|
-
/** Parenthesis depth — suppresses INDENT/DEDENT/NEWLINE when > 0 to support multi-line call expressions. */
|
|
94
|
-
bracketDepth = 0;
|
|
95
|
-
constructor(source) {
|
|
96
|
-
this.source = source;
|
|
97
|
-
}
|
|
98
|
-
tokenize() {
|
|
99
|
-
// Pre-allocate token array backing store for large inputs
|
|
100
|
-
this.tokens = [];
|
|
101
|
-
const estimate = (this.source.length / 8) | 0;
|
|
102
|
-
if (estimate > 64) {
|
|
103
|
-
this.tokens.length = estimate;
|
|
104
|
-
this.tokens.length = 0;
|
|
105
|
-
}
|
|
106
|
-
this.offset = 0;
|
|
107
|
-
this.row = 0;
|
|
108
|
-
this.col = 0;
|
|
109
|
-
this.indentStack = [0];
|
|
110
|
-
this.bracketDepth = 0;
|
|
111
|
-
while (this.hasMore) {
|
|
112
|
-
this.tokenizeLine();
|
|
113
|
-
}
|
|
114
|
-
// Emit remaining DEDENTs
|
|
115
|
-
while (this.indentStack.length > 1) {
|
|
116
|
-
this.indentStack.pop();
|
|
117
|
-
this.emitVirtual(TokenKind.DEDENT);
|
|
118
|
-
}
|
|
119
|
-
this.emitVirtual(TokenKind.EOF);
|
|
120
|
-
return this.tokens;
|
|
121
|
-
}
|
|
122
|
-
tokenizeLine() {
|
|
123
|
-
// Note: onTemplateLine persists across continuation lines and is only
|
|
124
|
-
// reset when indentation decreases (DEDENT) in emitIndentation().
|
|
125
|
-
// Measure leading indentation
|
|
126
|
-
const indentLength = this.consumeIndentation();
|
|
127
|
-
if (this.consumeNewline()) {
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
const c = this.peekCharCode();
|
|
131
|
-
// Comment-only line: tree-sitter's scanner skips past comment-only lines
|
|
132
|
-
// when deciding INDENT/DEDENT. If this comment is at deeper indent than
|
|
133
|
-
// current but no real content follows at that depth, emit NEWLINE instead.
|
|
134
|
-
// Similarly, if a comment is at shallower indent but the next real content
|
|
135
|
-
// is back at the current block's depth, suppress the DEDENT.
|
|
136
|
-
// When on a template line, only treat `#` as a comment if it's at or below
|
|
137
|
-
// the template's base indent (outside the template content area).
|
|
138
|
-
if (c === CH_HASH &&
|
|
139
|
-
(!this.onTemplateLine || indentLength <= this.templateBaseIndent)) {
|
|
140
|
-
const currentIndent = this.indentStack[this.indentStack.length - 1];
|
|
141
|
-
if (indentLength > currentIndent) {
|
|
142
|
-
const nextContentIndent = this.peekNextContentIndent();
|
|
143
|
-
if (nextContentIndent < indentLength) {
|
|
144
|
-
// No real content at this depth — suppress INDENT
|
|
145
|
-
this.emitIndentation(currentIndent);
|
|
146
|
-
return this.tokenizeComment();
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
else if (indentLength < currentIndent) {
|
|
150
|
-
const nextContentIndent = this.peekNextContentIndent();
|
|
151
|
-
if (nextContentIndent > indentLength) {
|
|
152
|
-
// Next real content is at a deeper indent than the comment.
|
|
153
|
-
// Emit indentation based on where the next content is, not
|
|
154
|
-
// where the comment sits, so we only close the blocks that
|
|
155
|
-
// actually end (not the ones the comment just happens to be
|
|
156
|
-
// outside of).
|
|
157
|
-
this.emitIndentation(nextContentIndent);
|
|
158
|
-
return this.tokenizeComment();
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
// Comment at same indent as current block. If next real content
|
|
163
|
-
// is deeper (i.e. the comment sits between a key and its indented
|
|
164
|
-
// body), suppress NEWLINE so the body's INDENT fires on the next
|
|
165
|
-
// iteration. Example:
|
|
166
|
-
// a:
|
|
167
|
-
// # comment ← same indent as current block
|
|
168
|
-
// body: "x" ← should INDENT into a's body
|
|
169
|
-
const nextContentIndent = this.peekNextContentIndent();
|
|
170
|
-
if (nextContentIndent > indentLength) {
|
|
171
|
-
return this.tokenizeComment();
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
this.emitIndentation(indentLength);
|
|
175
|
-
return this.tokenizeComment();
|
|
176
|
-
}
|
|
177
|
-
// Normal line — emit indentation tokens.
|
|
178
|
-
// Template continuation lines (deeper than templateBaseIndent) should
|
|
179
|
-
// not cause INDENT/DEDENT when their indent varies relative to other
|
|
180
|
-
// template lines. The initial INDENT into the template block is needed
|
|
181
|
-
// (it establishes the template_content grammar rule), but subsequent
|
|
182
|
-
// indent variation within the template corrupts the indent stack and
|
|
183
|
-
// breaks downstream blocks. Detect "already inside template block" by
|
|
184
|
-
// checking if the indent stack top is already deeper than templateBaseIndent.
|
|
185
|
-
const currentIndent = this.indentStack[this.indentStack.length - 1];
|
|
186
|
-
if (this.onTemplateLine &&
|
|
187
|
-
indentLength > this.templateBaseIndent &&
|
|
188
|
-
currentIndent > this.templateBaseIndent &&
|
|
189
|
-
indentLength !== currentIndent) {
|
|
190
|
-
this.emitIndentation(currentIndent);
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
this.emitIndentation(indentLength);
|
|
194
|
-
}
|
|
195
|
-
// Check for "- " sequence element at start of content
|
|
196
|
-
if (this.bracketDepth === 0 && c === CH_DASH) {
|
|
197
|
-
const nc = this.peekCharCode(1); // NaN at EOF — won't match any CH_*
|
|
198
|
-
const atEOF = this.offset + 1 >= this.source.length;
|
|
199
|
-
if (nc === CH_SPACE || this.atNewline(1) || atEOF) {
|
|
200
|
-
this.emit(TokenKind.DASH_SPACE, nc === CH_SPACE ? '- ' : '-');
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
// Tokenize the rest of the line
|
|
204
|
-
while (this.hasMore) {
|
|
205
|
-
const c = this.peekCharCode();
|
|
206
|
-
// Newline ends the line
|
|
207
|
-
if (this.consumeNewline()) {
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
if (c === CH_CR) {
|
|
211
|
-
// Not followed with line feed (otherwise this.consumeLine() would be true)
|
|
212
|
-
invariant(!this.atNewline());
|
|
213
|
-
this.advance();
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
// Skip horizontal whitespace
|
|
217
|
-
if (isHorizontalWs(c)) {
|
|
218
|
-
this.advance();
|
|
219
|
-
continue;
|
|
220
|
-
}
|
|
221
|
-
// Line continuation
|
|
222
|
-
if (c === CH_BACKSLASH) {
|
|
223
|
-
if (this.atNewline(1)) {
|
|
224
|
-
this.advance(); // skip backslash
|
|
225
|
-
invariant(this.consumeNewline());
|
|
226
|
-
// Skip leading whitespace on continuation line
|
|
227
|
-
while (isHorizontalWs(this.peekCharCode())) {
|
|
228
|
-
this.advance();
|
|
229
|
-
}
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
// Comment (but not on template content lines where # is literal text)
|
|
234
|
-
if (c === CH_HASH && !this.onTemplateLine) {
|
|
235
|
-
return this.tokenizeComment();
|
|
236
|
-
}
|
|
237
|
-
this.tokenizeToken();
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
emitIndentation(indentLength) {
|
|
241
|
-
if (this.bracketDepth > 0)
|
|
242
|
-
return;
|
|
243
|
-
const currentIndent = this.indentStack[this.indentStack.length - 1];
|
|
244
|
-
if (indentLength > currentIndent) {
|
|
245
|
-
this.indentStack.push(indentLength);
|
|
246
|
-
this.emitVirtual(TokenKind.INDENT);
|
|
247
|
-
}
|
|
248
|
-
else if (indentLength < currentIndent) {
|
|
249
|
-
// Emit DEDENTs and a NEWLINE; leave template context only when
|
|
250
|
-
// indentation drops to or below the template's base indent level.
|
|
251
|
-
if (indentLength <= this.templateBaseIndent) {
|
|
252
|
-
this.onTemplateLine = false;
|
|
253
|
-
this.templateExprBraceDepth = -1;
|
|
254
|
-
}
|
|
255
|
-
while (this.indentStack.length > 1 &&
|
|
256
|
-
this.indentStack[this.indentStack.length - 1] > indentLength) {
|
|
257
|
-
this.indentStack.pop();
|
|
258
|
-
this.emitVirtual(TokenKind.DEDENT);
|
|
259
|
-
}
|
|
260
|
-
this.emitVirtual(TokenKind.NEWLINE);
|
|
261
|
-
}
|
|
262
|
-
else {
|
|
263
|
-
// Same indent — emit NEWLINE (line separator) unless we're at the start.
|
|
264
|
-
// Leave template context only when at or below the template's base indent.
|
|
265
|
-
if (indentLength <= this.templateBaseIndent) {
|
|
266
|
-
this.onTemplateLine = false;
|
|
267
|
-
this.templateExprBraceDepth = -1;
|
|
268
|
-
}
|
|
269
|
-
if (this.tokens.length > 0) {
|
|
270
|
-
this.emitVirtual(TokenKind.NEWLINE);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
tokenizeToken() {
|
|
275
|
-
const c = this.peekCharCode();
|
|
276
|
-
// Datetimes or numbers
|
|
277
|
-
if (isDigit(c)) {
|
|
278
|
-
// Datetime literal: YYYY-MM-DD...
|
|
279
|
-
// Must check before numbers since datetimes start with digits
|
|
280
|
-
if (this.tryDatetime()) {
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
this.tokenizeNumber();
|
|
284
|
-
return;
|
|
285
|
-
}
|
|
286
|
-
// Identifier
|
|
287
|
-
if (isIdStart(c)) {
|
|
288
|
-
this.tokenizeId();
|
|
289
|
-
return;
|
|
290
|
-
}
|
|
291
|
-
// String (double-quoted always, single-quoted only if not a contraction)
|
|
292
|
-
// Inside template lines (after |), quotes are literal characters — don't
|
|
293
|
-
// start string tokenization unless we're inside a {!...} expression.
|
|
294
|
-
if (!this.onTemplateLine || this.inTemplateExpr) {
|
|
295
|
-
if (c === CH_DQUOTE) {
|
|
296
|
-
this.tokenizeString();
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
// Template expression start {!
|
|
301
|
-
if (c === CH_LBRACE && this.peekCharCode(1) === CH_BANG) {
|
|
302
|
-
this.templateExprBraceDepth = 0;
|
|
303
|
-
this.emit(TokenKind.TEMPLATE_EXPR_START, '{!');
|
|
304
|
-
return;
|
|
305
|
-
}
|
|
306
|
-
// Tokens beginning with .
|
|
307
|
-
if (c === CH_DOT) {
|
|
308
|
-
if (this.peekCharCode(1) === CH_DOT && this.peekCharCode(2) === CH_DOT) {
|
|
309
|
-
this.emit(TokenKind.ELLIPSIS, '...');
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
// Leading-dot number (e.g., .5, .123) — but not after identifiers
|
|
313
|
-
// (which would be member access like @variable.5)
|
|
314
|
-
if (isDigit(this.peekCharCode(1))) {
|
|
315
|
-
const prev = this.tokens[this.tokens.length - 1];
|
|
316
|
-
const isMemberAccess = prev !== undefined &&
|
|
317
|
-
(prev.kind === TokenKind.ID ||
|
|
318
|
-
prev.kind === TokenKind.NUMBER ||
|
|
319
|
-
prev.kind === TokenKind.RPAREN ||
|
|
320
|
-
prev.kind === TokenKind.RBRACKET);
|
|
321
|
-
if (!isMemberAccess) {
|
|
322
|
-
this.tokenizeNumber();
|
|
323
|
-
return;
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if (c === CH_DASH) {
|
|
328
|
-
if (this.peekCharCode(1) === CH_GT) {
|
|
329
|
-
return this.emit(TokenKind.ARROW, '->');
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
// == != <= >=
|
|
333
|
-
const nc = this.peekCharCode(1);
|
|
334
|
-
if (nc === CH_EQ) {
|
|
335
|
-
// next is '='
|
|
336
|
-
if (c === CH_EQ) {
|
|
337
|
-
return this.emit(TokenKind.EQEQ, '==');
|
|
338
|
-
}
|
|
339
|
-
if (c === CH_BANG) {
|
|
340
|
-
return this.emit(TokenKind.NEQ, '!=');
|
|
341
|
-
}
|
|
342
|
-
if (c === CH_LT) {
|
|
343
|
-
return this.emit(TokenKind.LTE, '<=');
|
|
344
|
-
}
|
|
345
|
-
if (c === CH_GT) {
|
|
346
|
-
return this.emit(TokenKind.GTE, '>=');
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
// Single-char tokens (charCode-indexed lookup)
|
|
350
|
-
const kind = c < 128 ? SINGLE_CHAR_TOKENS[c] : 0;
|
|
351
|
-
if (kind) {
|
|
352
|
-
this.emitSpan(kind, 1);
|
|
353
|
-
switch (kind) {
|
|
354
|
-
// Track template lines: `|` starts a template context for this line
|
|
355
|
-
// and continuation lines indented deeper than this level.
|
|
356
|
-
case TokenKind.PIPE:
|
|
357
|
-
this.onTemplateLine = true;
|
|
358
|
-
this.templateBaseIndent =
|
|
359
|
-
this.indentStack[this.indentStack.length - 1];
|
|
360
|
-
break;
|
|
361
|
-
// Track parenthesis depth to suppress structural tokens inside
|
|
362
|
-
// multi-line call expressions. Skip when inside a template line —
|
|
363
|
-
// parens in template content are literal text and must not suppress
|
|
364
|
-
// INDENT/DEDENT emission (unmatched parens would eat the rest of
|
|
365
|
-
// the file).
|
|
366
|
-
case TokenKind.LPAREN:
|
|
367
|
-
if (!this.onTemplateLine)
|
|
368
|
-
this.bracketDepth++;
|
|
369
|
-
break;
|
|
370
|
-
case TokenKind.RPAREN:
|
|
371
|
-
if (!this.onTemplateLine)
|
|
372
|
-
this.bracketDepth--;
|
|
373
|
-
break;
|
|
374
|
-
// Track brace depth inside {!...} template expressions so that nested
|
|
375
|
-
// braces (e.g. JSON objects) don't prematurely close the expression.
|
|
376
|
-
case TokenKind.LBRACE:
|
|
377
|
-
if (this.inTemplateExpr) {
|
|
378
|
-
this.templateExprBraceDepth++;
|
|
379
|
-
}
|
|
380
|
-
break;
|
|
381
|
-
case TokenKind.RBRACE:
|
|
382
|
-
if (this.inTemplateExpr) {
|
|
383
|
-
this.templateExprBraceDepth--;
|
|
384
|
-
}
|
|
385
|
-
break;
|
|
386
|
-
}
|
|
387
|
-
return;
|
|
388
|
-
}
|
|
389
|
-
// Unknown character — emit error token
|
|
390
|
-
this.emitSpan(TokenKind.ERROR_TOKEN, 1);
|
|
391
|
-
}
|
|
392
|
-
tokenizeId() {
|
|
393
|
-
let i = 0;
|
|
394
|
-
for (;; i++) {
|
|
395
|
-
const c = this.peekCharCode(i);
|
|
396
|
-
if (!isIdCont(c))
|
|
397
|
-
break;
|
|
398
|
-
}
|
|
399
|
-
this.emitSpan(TokenKind.ID, i);
|
|
400
|
-
}
|
|
401
|
-
tokenizeNumber() {
|
|
402
|
-
let tokenLength = 0;
|
|
403
|
-
// Leading dot (e.g., .5) — consume the `.` first
|
|
404
|
-
const leadingDot = this.peekCharCode(tokenLength) === CH_DOT;
|
|
405
|
-
if (leadingDot) {
|
|
406
|
-
tokenLength++;
|
|
407
|
-
}
|
|
408
|
-
// Integer part — inline advance (digits never contain newlines)
|
|
409
|
-
while (isDigit(this.peekCharCode(tokenLength))) {
|
|
410
|
-
tokenLength++;
|
|
411
|
-
}
|
|
412
|
-
// Decimal part — only consume `.` if followed by a digit (and no leading dot)
|
|
413
|
-
if (!leadingDot && this.peekCharCode(tokenLength) === CH_DOT) {
|
|
414
|
-
tokenLength++;
|
|
415
|
-
}
|
|
416
|
-
while (isDigit(this.peekCharCode(tokenLength))) {
|
|
417
|
-
tokenLength++;
|
|
418
|
-
}
|
|
419
|
-
this.emitSpan(TokenKind.NUMBER, tokenLength);
|
|
420
|
-
}
|
|
421
|
-
tryDatetime() {
|
|
422
|
-
// ISO 8601: YYYY-MM-DD optionally followed by time
|
|
423
|
-
// Need at least YYYY-MM-DD = 10 chars
|
|
424
|
-
const remaining = this.source.length - this.offset;
|
|
425
|
-
if (remaining < 10)
|
|
426
|
-
return false;
|
|
427
|
-
// Fast reject: most numbers aren't datetimes. Check the fixed '-' positions
|
|
428
|
-
// before allocating a slice or running the regex.
|
|
429
|
-
if (this.source.charCodeAt(this.offset + 4) !== CH_DASH ||
|
|
430
|
-
this.source.charCodeAt(this.offset + 7) !== CH_DASH) {
|
|
431
|
-
return false;
|
|
432
|
-
}
|
|
433
|
-
const slice = this.source.slice(this.offset, this.offset + 30);
|
|
434
|
-
const match = slice.match(/^\d{4}-\d{2}-\d{2}(T\d{1,2}(:\d{2})?(:\d{2})?(\.\d+)?Z?)?/);
|
|
435
|
-
if (!match)
|
|
436
|
-
return false;
|
|
437
|
-
// Only treat as datetime if it has the full YYYY-MM-DD pattern
|
|
438
|
-
// and the character after isn't an identifier character
|
|
439
|
-
const matchText = match[0];
|
|
440
|
-
if (matchText.length < 10)
|
|
441
|
-
return false; // Must have at least YYYY-MM-DD
|
|
442
|
-
this.emit(TokenKind.DATETIME, matchText);
|
|
443
|
-
return true;
|
|
444
|
-
}
|
|
445
|
-
tokenizeString() {
|
|
446
|
-
const start = this.position;
|
|
447
|
-
const startOffset = this.offset;
|
|
448
|
-
const quoteCode = this.peekCharCode(); // " or '
|
|
449
|
-
// Opening quote
|
|
450
|
-
this.advance();
|
|
451
|
-
while (this.hasMore) {
|
|
452
|
-
const c = this.peekCharCode();
|
|
453
|
-
if (c === quoteCode) {
|
|
454
|
-
this.advance(); // closing quote
|
|
455
|
-
const text = this.source.slice(startOffset, this.offset);
|
|
456
|
-
this.tokens.push(this.makeToken(TokenKind.STRING, text, start, this.position, startOffset));
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
if (c === CH_BACKSLASH) {
|
|
460
|
-
this.advance(2);
|
|
461
|
-
continue;
|
|
462
|
-
}
|
|
463
|
-
if (this.atNewline()) {
|
|
464
|
-
// Unclosed string — stop at newline for error recovery
|
|
465
|
-
break;
|
|
466
|
-
}
|
|
467
|
-
if (c === CH_CR) {
|
|
468
|
-
// Bare \r inside string — treat as content
|
|
469
|
-
invariant(!this.atNewline());
|
|
470
|
-
this.advance();
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
if (c === CH_NUL) {
|
|
474
|
-
// Null byte — tree-sitter rejects these in string content
|
|
475
|
-
break;
|
|
476
|
-
}
|
|
477
|
-
this.advance();
|
|
478
|
-
}
|
|
479
|
-
// Unclosed string
|
|
480
|
-
const text = this.source.slice(startOffset, this.offset);
|
|
481
|
-
this.tokens.push(this.makeToken(TokenKind.STRING, text, start, this.position, startOffset));
|
|
482
|
-
}
|
|
483
|
-
tokenizeComment() {
|
|
484
|
-
const start = this.position;
|
|
485
|
-
const startOffset = this.offset;
|
|
486
|
-
// Consume # and everything until end of line or EOF
|
|
487
|
-
while (this.hasMore && !this.atNewline()) {
|
|
488
|
-
this.advance();
|
|
489
|
-
}
|
|
490
|
-
const text = this.source.slice(startOffset, this.offset);
|
|
491
|
-
this.tokens.push(this.makeToken(TokenKind.COMMENT, text, start, this.position, startOffset));
|
|
492
|
-
this.consumeNewline();
|
|
493
|
-
}
|
|
494
|
-
consumeIndentation() {
|
|
495
|
-
let indentLength = 0;
|
|
496
|
-
while (this.hasMore) {
|
|
497
|
-
const c = this.peekCharCode();
|
|
498
|
-
if (c === CH_SPACE) {
|
|
499
|
-
indentLength += 1;
|
|
500
|
-
this.advance();
|
|
501
|
-
}
|
|
502
|
-
else if (c === CH_TAB) {
|
|
503
|
-
indentLength += 3;
|
|
504
|
-
this.advance();
|
|
505
|
-
}
|
|
506
|
-
else if (c === CH_CR) {
|
|
507
|
-
// Bare \r (not followed by \n) resets indent to 0,
|
|
508
|
-
// matching tree-sitter scanner.c behavior (line 140-142)
|
|
509
|
-
indentLength = 0;
|
|
510
|
-
this.advance();
|
|
511
|
-
}
|
|
512
|
-
else {
|
|
513
|
-
break;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
return indentLength;
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* Scan ahead (without advancing) past comment/blank lines to find the indent
|
|
520
|
-
* of the next line with real (non-comment) content. Returns -1 if only
|
|
521
|
-
* comments, blanks, or EOF remain. Matches tree-sitter scanner behavior which
|
|
522
|
-
* skips past comment-only lines when computing INDENT/DEDENT.
|
|
523
|
-
*/
|
|
524
|
-
peekNextContentIndent() {
|
|
525
|
-
const startPosition = this.position;
|
|
526
|
-
const startOffset = this.offset;
|
|
527
|
-
// Skip past the current comment line
|
|
528
|
-
while (this.hasMore) {
|
|
529
|
-
if (this.consumeNewline())
|
|
530
|
-
break;
|
|
531
|
-
this.advance();
|
|
532
|
-
}
|
|
533
|
-
// Scan subsequent lines
|
|
534
|
-
while (this.hasMore) {
|
|
535
|
-
// Measure indent
|
|
536
|
-
const lineIndent = this.consumeIndentation();
|
|
537
|
-
// Blank line — skip
|
|
538
|
-
if (this.consumeNewline())
|
|
539
|
-
continue;
|
|
540
|
-
// Comment line — skip
|
|
541
|
-
const c = this.peekCharCode();
|
|
542
|
-
if (c === CH_HASH) {
|
|
543
|
-
while (this.hasMore) {
|
|
544
|
-
if (this.consumeNewline())
|
|
545
|
-
break;
|
|
546
|
-
this.advance();
|
|
547
|
-
}
|
|
548
|
-
continue;
|
|
549
|
-
}
|
|
550
|
-
this.offset = startOffset;
|
|
551
|
-
this.row = startPosition.row;
|
|
552
|
-
this.col = startPosition.column;
|
|
553
|
-
// Real content — return its indent
|
|
554
|
-
return lineIndent;
|
|
555
|
-
}
|
|
556
|
-
this.offset = startOffset;
|
|
557
|
-
this.row = startPosition.row;
|
|
558
|
-
this.col = startPosition.column;
|
|
559
|
-
return -1;
|
|
560
|
-
}
|
|
561
|
-
// --- Utility methods ---
|
|
562
|
-
peekCharCode(additiveOffset = 0) {
|
|
563
|
-
return this.source.charCodeAt(this.offset + additiveOffset);
|
|
564
|
-
}
|
|
565
|
-
get hasMore() {
|
|
566
|
-
return this.offset < this.source.length && this.offset >= 0;
|
|
567
|
-
}
|
|
568
|
-
/**
|
|
569
|
-
* Attempt to advance n characters.
|
|
570
|
-
* @returns how many characters were advanced.
|
|
571
|
-
*/
|
|
572
|
-
advance(n = 1) {
|
|
573
|
-
n = Math.max(0, Math.min(n, this.source.length - this.offset));
|
|
574
|
-
this.col += n;
|
|
575
|
-
for (let i = 0; i < n; i++) {
|
|
576
|
-
if (this.peekCharCode(i) === CH_LF) {
|
|
577
|
-
this.row++;
|
|
578
|
-
this.col = n - i - 1;
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
this.offset += n;
|
|
582
|
-
return n;
|
|
583
|
-
}
|
|
584
|
-
/**
|
|
585
|
-
* Attempt to consume a newline.
|
|
586
|
-
* @returns whether a newline was consumed.
|
|
587
|
-
*/
|
|
588
|
-
consumeNewline() {
|
|
589
|
-
const newChars = this.atNewline();
|
|
590
|
-
if (newChars > 0) {
|
|
591
|
-
invariant(this.advance(newChars));
|
|
592
|
-
return true;
|
|
593
|
-
}
|
|
594
|
-
return false;
|
|
595
|
-
}
|
|
596
|
-
/**
|
|
597
|
-
* Checks if the current position is at a newline.
|
|
598
|
-
* @param additiveOffset
|
|
599
|
-
* @returns 0 if not at a newline, 1 if at an LF newline, 2 if at a CR LF newline.
|
|
600
|
-
*/
|
|
601
|
-
atNewline(additiveOffset = 0) {
|
|
602
|
-
const firstChar = this.peekCharCode(additiveOffset);
|
|
603
|
-
if (firstChar === CH_LF)
|
|
604
|
-
return 1;
|
|
605
|
-
if (firstChar === CH_CR && this.peekCharCode(additiveOffset + 1) === CH_LF)
|
|
606
|
-
return 2;
|
|
607
|
-
return 0;
|
|
608
|
-
}
|
|
609
|
-
get position() {
|
|
610
|
-
return { row: this.row, column: this.col };
|
|
611
|
-
}
|
|
612
|
-
emitSpan(kind, length) {
|
|
613
|
-
const text = this.source.slice(this.offset, this.offset + length);
|
|
614
|
-
return this.emit(kind, text);
|
|
615
|
-
}
|
|
616
|
-
emit(kind, text) {
|
|
617
|
-
const startPosition = this.position;
|
|
618
|
-
const startOffset = this.offset;
|
|
619
|
-
invariant(text === this.source.slice(startOffset, startOffset + text.length), `expected '${text}' but got ${this.source.slice(startOffset, startOffset + text.length)} at offset ${startOffset}`);
|
|
620
|
-
this.advance(text.length);
|
|
621
|
-
this.tokens.push(this.makeToken(kind, text, startPosition, this.position, startOffset));
|
|
622
|
-
}
|
|
623
|
-
emitVirtual(kind) {
|
|
624
|
-
return this.emit(kind, '');
|
|
625
|
-
}
|
|
626
|
-
makeToken(kind, text, start, end, startOffset) {
|
|
627
|
-
return { kind, text, start, end, startOffset };
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
//# sourceMappingURL=lexer.js.map
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { CSTNode } from './cst-node.js';
|
|
2
|
-
import type { ParserContext } from './parser.js';
|
|
3
|
-
/** Callback type for parseSequence to break circular dependency. */
|
|
4
|
-
export type ParseSequenceFn = (ctx: ParserContext) => CSTNode;
|
|
5
|
-
/**
|
|
6
|
-
* Parse a mapping-or-expression at the top level.
|
|
7
|
-
* If the current position starts a mapping, delegates to parseMapping;
|
|
8
|
-
* otherwise parses an expression (possibly an assignment).
|
|
9
|
-
*/
|
|
10
|
-
export declare function parseMappingOrExpression(ctx: ParserContext, parseSequence: ParseSequenceFn): CSTNode | null;
|
|
11
|
-
/**
|
|
12
|
-
* Lookahead to determine if the current position starts a mapping (key-value
|
|
13
|
-
* pairs) rather than an expression.
|
|
14
|
-
*
|
|
15
|
-
* Keys are at most a few tokens (1-3 words, possibly with hyphens/dots), so
|
|
16
|
-
* we only need a small lookahead window. The limit exists as a safety cap —
|
|
17
|
-
* it should never be reached on valid input.
|
|
18
|
-
*/
|
|
19
|
-
export declare function isMappingStart(ctx: ParserContext): boolean;
|
|
20
|
-
/**
|
|
21
|
-
* Parse a mapping (sequence of key-value pairs).
|
|
22
|
-
*/
|
|
23
|
-
export declare function parseMapping(ctx: ParserContext, parseSequence: ParseSequenceFn): CSTNode;
|
|
24
|
-
/**
|
|
25
|
-
* Parse a single mapping item (statement, template, comment, or key:value element).
|
|
26
|
-
*/
|
|
27
|
-
export declare function parseMappingItem(ctx: ParserContext, parseSequence: ParseSequenceFn): CSTNode | null;
|
|
28
|
-
/**
|
|
29
|
-
* Check if the current position starts a colinear mapping element (key: value
|
|
30
|
-
* on same line after "- ").
|
|
31
|
-
*/
|
|
32
|
-
export declare function isColinearMappingElement(ctx: ParserContext): boolean;
|
|
33
|
-
/**
|
|
34
|
-
* Parse a colinear mapping element (key: value on the same line as "- ").
|
|
35
|
-
*/
|
|
36
|
-
export declare function parseColinearMappingElement(ctx: ParserContext): CSTNode;
|
|
37
|
-
/**
|
|
38
|
-
* Try to parse a colinear value (template, variable declaration, or expression).
|
|
39
|
-
* Returns the parsed value node and optional error prefix, or null if nothing
|
|
40
|
-
* can be parsed.
|
|
41
|
-
*/
|
|
42
|
-
export declare function tryParseColinearValue(ctx: ParserContext): {
|
|
43
|
-
value: CSTNode;
|
|
44
|
-
errorPrefix?: CSTNode;
|
|
45
|
-
} | null;
|
|
46
|
-
//# sourceMappingURL=parse-mapping.d.ts.map
|