@barefootjs/jsx 0.4.0 → 0.5.0
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/dist/adapters/interface.d.ts +20 -0
- package/dist/adapters/interface.d.ts.map +1 -1
- package/dist/expression-parser.d.ts +36 -19
- package/dist/expression-parser.d.ts.map +1 -1
- package/dist/import-map.d.ts +56 -0
- package/dist/import-map.d.ts.map +1 -0
- package/dist/import-map.js +18 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +154 -162
- package/dist/ir-to-client-js/utils.d.ts.map +1 -1
- package/dist/scanner/js-scanner.d.ts +10 -0
- package/dist/scanner/js-scanner.d.ts.map +1 -1
- package/dist/scanner/js-scanner.js +5 -0
- package/package.json +7 -3
- package/src/__tests__/__snapshots__/doc-examples.test.ts.snap +134 -8
- package/src/__tests__/child-components-in-map.test.ts +76 -0
- package/src/__tests__/import-map.test.ts +75 -0
- package/src/__tests__/ir-sort-comparator.test.ts +212 -9
- package/src/__tests__/token-contains-ident.test.ts +27 -0
- package/src/__tests__/unsupported-expression.test.ts +42 -13
- package/src/adapters/interface.ts +20 -0
- package/src/expression-parser.ts +265 -50
- package/src/import-map.ts +72 -0
- package/src/index.ts +5 -1
- package/src/ir-to-client-js/stringify/static-array-child-init.ts +8 -4
- package/src/ir-to-client-js/utils.ts +29 -115
- package/src/scanner/js-scanner.ts +16 -1
|
@@ -7,7 +7,12 @@ import ts from 'typescript'
|
|
|
7
7
|
import type { AttrValue, IRTemplatePart, LoopParamBinding, FreeReference, IRNode } from '../types'
|
|
8
8
|
import type { TopLevelLoop, BranchLoop } from './types'
|
|
9
9
|
import { buildLoopChainExpr } from '../loop-chain'
|
|
10
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
iterateJsTokens,
|
|
12
|
+
isIdentifierLikeToken,
|
|
13
|
+
isTriviaKind,
|
|
14
|
+
replaceInExprContexts,
|
|
15
|
+
} from '../scanner/js-scanner'
|
|
11
16
|
import {
|
|
12
17
|
BF_KEY as DATA_KEY,
|
|
13
18
|
BF_KEY_PREFIX as DATA_KEY_PREFIX,
|
|
@@ -376,124 +381,33 @@ export function tokenContainsIdent(expr: string, ident: string): boolean {
|
|
|
376
381
|
return scanForIdentifiers(expr, (token) => token === ident)
|
|
377
382
|
}
|
|
378
383
|
|
|
379
|
-
const IDENT_START_RE = /[A-Za-z_$]/
|
|
380
|
-
const IDENT_PART_RE = /[A-Za-z0-9_$]/
|
|
381
|
-
|
|
382
384
|
/**
|
|
383
|
-
*
|
|
384
|
-
*
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
* name
|
|
385
|
+
* Walk a JS-like expression string via the shared `ts.createScanner`-based
|
|
386
|
+
* lexer and invoke `predicate` on every identifier-like token found in a
|
|
387
|
+
* position where bare identifiers are semantically possible — i.e. not
|
|
388
|
+
* inside a string / template-string body / comment / regex literal, and
|
|
389
|
+
* not the property name of a member-access expression. Returns true on the
|
|
390
|
+
* first hit.
|
|
391
|
+
*
|
|
392
|
+
* Delegating to `iterateJsTokens` (rather than a hand-rolled char-by-char
|
|
393
|
+
* state machine) means regex literals are recognised: `/it's/.test(foo)`
|
|
394
|
+
* no longer reads the apostrophe as a string opener, and an identifier
|
|
395
|
+
* inside a regex body (`/className/`) is correctly treated as opaque (#1370).
|
|
388
396
|
*/
|
|
389
397
|
function scanForIdentifiers(expr: string, predicate: (token: string) => boolean): boolean {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
let braceDepth = 0
|
|
402
|
-
|
|
403
|
-
while (i < n) {
|
|
404
|
-
const ch = expr[i]
|
|
405
|
-
|
|
406
|
-
switch (state) {
|
|
407
|
-
case 0: // code
|
|
408
|
-
case 4: { // template expression — same lexing rules as code
|
|
409
|
-
// String / template literal openers
|
|
410
|
-
if (ch === "'") { state = 1; i++; continue }
|
|
411
|
-
if (ch === '"') { state = 2; i++; continue }
|
|
412
|
-
if (ch === '`') { state = 3; i++; continue }
|
|
413
|
-
// Comment openers
|
|
414
|
-
if (ch === '/' && i + 1 < n) {
|
|
415
|
-
const next = expr[i + 1]
|
|
416
|
-
if (next === '/') { state = 5; i += 2; continue }
|
|
417
|
-
if (next === '*') { state = 6; i += 2; continue }
|
|
418
|
-
}
|
|
419
|
-
// Track braces only inside template-expression state, so we know when
|
|
420
|
-
// we leave `${ ... }` back to the surrounding template text.
|
|
421
|
-
if (state === 4) {
|
|
422
|
-
if (ch === '{') { braceDepth++; i++; continue }
|
|
423
|
-
if (ch === '}') {
|
|
424
|
-
if (braceDepth === 0) {
|
|
425
|
-
// Closing `}` of `${ ... }` — pop back to enclosing tmpl state.
|
|
426
|
-
const restored = tmplExprStack.pop()
|
|
427
|
-
braceDepth = restored ?? 0
|
|
428
|
-
state = 3
|
|
429
|
-
i++
|
|
430
|
-
continue
|
|
431
|
-
}
|
|
432
|
-
braceDepth--
|
|
433
|
-
i++
|
|
434
|
-
continue
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
// Identifier start
|
|
438
|
-
if (IDENT_START_RE.test(ch)) {
|
|
439
|
-
let j = i + 1
|
|
440
|
-
while (j < n && IDENT_PART_RE.test(expr[j])) j++
|
|
441
|
-
const token = expr.slice(i, j)
|
|
442
|
-
// Skip member-access tail: identifier preceded by `.` (ignoring
|
|
443
|
-
// whitespace).
|
|
444
|
-
let prev = i - 1
|
|
445
|
-
while (prev >= 0 && (expr[prev] === ' ' || expr[prev] === '\t' || expr[prev] === '\n' || expr[prev] === '\r')) prev--
|
|
446
|
-
const isMemberTail = prev >= 0 && expr[prev] === '.' && (prev === 0 || expr[prev - 1] !== '.') // not `..` (spread)
|
|
447
|
-
if (!isMemberTail && predicate(token)) return true
|
|
448
|
-
i = j
|
|
449
|
-
continue
|
|
450
|
-
}
|
|
451
|
-
i++
|
|
452
|
-
continue
|
|
453
|
-
}
|
|
454
|
-
case 1: { // single-quote string
|
|
455
|
-
if (ch === '\\' && i + 1 < n) { i += 2; continue }
|
|
456
|
-
if (ch === "'") { state = 0; i++; continue }
|
|
457
|
-
i++
|
|
458
|
-
continue
|
|
459
|
-
}
|
|
460
|
-
case 2: { // double-quote string
|
|
461
|
-
if (ch === '\\' && i + 1 < n) { i += 2; continue }
|
|
462
|
-
if (ch === '"') { state = 0; i++; continue }
|
|
463
|
-
i++
|
|
464
|
-
continue
|
|
465
|
-
}
|
|
466
|
-
case 3: { // template literal text
|
|
467
|
-
if (ch === '\\' && i + 1 < n) { i += 2; continue }
|
|
468
|
-
if (ch === '`') {
|
|
469
|
-
// Closing the template literal; return to whatever code state we
|
|
470
|
-
// came from (either top-level code or an outer template expression).
|
|
471
|
-
state = tmplExprStack.length > 0 ? 4 : 0
|
|
472
|
-
i++
|
|
473
|
-
continue
|
|
474
|
-
}
|
|
475
|
-
if (ch === '$' && i + 1 < n && expr[i + 1] === '{') {
|
|
476
|
-
// Entering `${ ... }`: save current outer brace depth, reset for new.
|
|
477
|
-
tmplExprStack.push(braceDepth)
|
|
478
|
-
braceDepth = 0
|
|
479
|
-
state = 4
|
|
480
|
-
i += 2
|
|
481
|
-
continue
|
|
482
|
-
}
|
|
483
|
-
i++
|
|
484
|
-
continue
|
|
485
|
-
}
|
|
486
|
-
case 5: { // line comment
|
|
487
|
-
if (ch === '\n' || ch === '\r') { state = 0; i++; continue }
|
|
488
|
-
i++
|
|
489
|
-
continue
|
|
490
|
-
}
|
|
491
|
-
case 6: { // block comment
|
|
492
|
-
if (ch === '*' && i + 1 < n && expr[i + 1] === '/') { state = 0; i += 2; continue }
|
|
493
|
-
i++
|
|
494
|
-
continue
|
|
495
|
-
}
|
|
398
|
+
// Previous *significant* (non-trivia) token kind, used to skip the tail
|
|
399
|
+
// of a member access (`a.foo`, `a?.foo`) while still treating the head
|
|
400
|
+
// (`foo.bar`) and spread targets (`...foo`) as real references.
|
|
401
|
+
let prevSignificant: ts.SyntaxKind | undefined
|
|
402
|
+
for (const tok of iterateJsTokens(expr)) {
|
|
403
|
+
if (isTriviaKind(tok.kind)) continue
|
|
404
|
+
if (isIdentifierLikeToken(tok.kind)) {
|
|
405
|
+
const isMemberTail =
|
|
406
|
+
prevSignificant === ts.SyntaxKind.DotToken
|
|
407
|
+
|| prevSignificant === ts.SyntaxKind.QuestionDotToken
|
|
408
|
+
if (!isMemberTail && predicate(expr.slice(tok.pos, tok.end))) return true
|
|
496
409
|
}
|
|
410
|
+
prevSignificant = tok.kind
|
|
497
411
|
}
|
|
498
412
|
return false
|
|
499
413
|
}
|
|
@@ -110,7 +110,7 @@ export function* iterateJsTokens(
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
-
function isTriviaKind(kind: ts.SyntaxKind): boolean {
|
|
113
|
+
export function isTriviaKind(kind: ts.SyntaxKind): boolean {
|
|
114
114
|
return (
|
|
115
115
|
kind === ts.SyntaxKind.WhitespaceTrivia
|
|
116
116
|
|| kind === ts.SyntaxKind.NewLineTrivia
|
|
@@ -164,6 +164,21 @@ function canRegexStartHere(prev: ts.SyntaxKind | undefined): boolean {
|
|
|
164
164
|
// ---------------------------------------------------------------------------
|
|
165
165
|
// Token classification helpers used by the consumers below.
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Whether `kind` is an identifier-like token — a plain identifier or any
|
|
169
|
+
* reserved/contextual keyword. Keywords lex to their own token kinds but
|
|
170
|
+
* still match the `[A-Za-z_$][\w$]*` shape the previous hand-rolled
|
|
171
|
+
* scanners treated as candidate identifier tokens, so consumers that want
|
|
172
|
+
* "every bare word in code context" (e.g. `tokenContainsIdent`) include
|
|
173
|
+
* them and compare the slice text themselves.
|
|
174
|
+
*/
|
|
175
|
+
export function isIdentifierLikeToken(kind: ts.SyntaxKind): boolean {
|
|
176
|
+
return (
|
|
177
|
+
kind === ts.SyntaxKind.Identifier
|
|
178
|
+
|| (kind >= ts.SyntaxKind.FirstKeyword && kind <= ts.SyntaxKind.LastKeyword)
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
167
182
|
/** A token whose textual content is a non-code region (string body, regex, comment). */
|
|
168
183
|
function isOpaqueContentKind(kind: ts.SyntaxKind): boolean {
|
|
169
184
|
return (
|