rip-lang 3.13.129 → 3.13.130
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/README.md +6 -6
- package/docs/RIP-LANG.md +12 -0
- package/docs/dist/rip.js +469 -364
- package/docs/dist/rip.min.js +151 -149
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/ui/hljs-rip.js +7 -0
- package/package.json +1 -1
- package/src/AGENTS.md +1 -0
- package/src/compiler.js +36 -4
- package/src/components.js +17 -8
- package/src/grammar/grammar.rip +40 -0
- package/src/lexer.js +31 -8
- package/src/parser.js +223 -221
- package/src/sourcemap-utils.js +46 -2
- package/src/typecheck.js +39 -9
- package/src/types.js +5 -5
package/src/sourcemap-utils.js
CHANGED
|
@@ -58,6 +58,17 @@ export function findNearestWord(text, word, approx) {
|
|
|
58
58
|
return bestIdx;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// Check whether an offset falls on an injected function overload signature line
|
|
62
|
+
// (generated by compileForCheck, not from user code). These are body lines that
|
|
63
|
+
// match `function NAME(...): TYPE;` and have no genToSrc entry.
|
|
64
|
+
export function isInjectedOverload(entry, offset) {
|
|
65
|
+
const tsLine = offsetToLine(entry.tsContent, offset);
|
|
66
|
+
if (tsLine < entry.headerLines) return false;
|
|
67
|
+
if (entry.genToSrc.get(tsLine) !== undefined) return false;
|
|
68
|
+
const lineText = getLineText(entry.tsContent, tsLine);
|
|
69
|
+
return /^(?:async\s+)?function\s+\w+\s*\(/.test(lineText) && lineText.trimEnd().endsWith(';');
|
|
70
|
+
}
|
|
71
|
+
|
|
61
72
|
export function offsetToLine(text, offset) {
|
|
62
73
|
let line = 0;
|
|
63
74
|
for (let i = 0; i < offset && i < text.length; i++) {
|
|
@@ -92,6 +103,12 @@ export function mapToSourcePos(entry, offset) {
|
|
|
92
103
|
if (tsLine < entry.headerLines) {
|
|
93
104
|
// DTS preamble — find the identifier at the offset and locate it in the source
|
|
94
105
|
const genLineText = getLineText(entry.tsContent, tsLine);
|
|
106
|
+
|
|
107
|
+
// Skip compiler-injected stdlib declarations (declare function warn, etc.)
|
|
108
|
+
// — diagnostics on these lines are never user-authored and would incorrectly
|
|
109
|
+
// match string literals or identifiers in the source.
|
|
110
|
+
if (/^declare\s+function\s/.test(genLineText)) return null;
|
|
111
|
+
|
|
95
112
|
let lineStart = 0, curLine = 0;
|
|
96
113
|
for (let i = 0; i < entry.tsContent.length; i++) {
|
|
97
114
|
if (curLine === tsLine) { lineStart = i; break; }
|
|
@@ -189,6 +206,25 @@ export function mapToSourcePos(entry, offset) {
|
|
|
189
206
|
}
|
|
190
207
|
}
|
|
191
208
|
|
|
209
|
+
// Injected function overload signatures (e.g. `function fetchUser(id: number): Promise<User>;`)
|
|
210
|
+
// have no genToSrc entry. The backward-walk approximation below can map them to
|
|
211
|
+
// wildly wrong source lines. Extract the function name and find its `def` in the source.
|
|
212
|
+
const bodyLine = getLineText(entry.tsContent, tsLine);
|
|
213
|
+
if (entry.genToSrc.get(tsLine) === undefined && entry.source) {
|
|
214
|
+
const overloadMatch = bodyLine.match(/^(?:async\s+)?function\s+(\w+)\s*\(/);
|
|
215
|
+
if (overloadMatch && bodyLine.trimEnd().endsWith(';')) {
|
|
216
|
+
const fnName = overloadMatch[1];
|
|
217
|
+
const srcLines = entry.source.split('\n');
|
|
218
|
+
const defRe = new RegExp('\\bdef\\s+' + fnName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\b');
|
|
219
|
+
for (let s = 0; s < srcLines.length; s++) {
|
|
220
|
+
if (defRe.test(srcLines[s])) {
|
|
221
|
+
const col = srcLines[s].indexOf(fnName);
|
|
222
|
+
return { line: s, col: col >= 0 ? col : 0 };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
192
228
|
// Resolve source line from genToSrc
|
|
193
229
|
let srcLine = entry.genToSrc.get(tsLine);
|
|
194
230
|
if (srcLine === undefined) {
|
|
@@ -235,7 +271,11 @@ export function mapToSourcePos(entry, offset) {
|
|
|
235
271
|
const srcText = entry.source ? getLineText(entry.source, srcLine) : '';
|
|
236
272
|
// Text-match: find the word at genCol in the gen line, then locate it in the source line
|
|
237
273
|
if (srcText) {
|
|
238
|
-
|
|
274
|
+
let wordAt = genText.slice(genCol).match(/^\w+/);
|
|
275
|
+
// Quoted string literal (e.g. __ripEl('tag')) — peek inside the quotes
|
|
276
|
+
if (!wordAt && (genText[genCol] === "'" || genText[genCol] === '"')) {
|
|
277
|
+
wordAt = genText.slice(genCol + 1).match(/^\w+/);
|
|
278
|
+
}
|
|
239
279
|
if (wordAt) {
|
|
240
280
|
let word = wordAt[0];
|
|
241
281
|
let idx = findNearestWord(srcText, word, approx);
|
|
@@ -247,7 +287,11 @@ export function mapToSourcePos(entry, offset) {
|
|
|
247
287
|
if (idx >= 0) return { line: srcLine, col: idx };
|
|
248
288
|
}
|
|
249
289
|
if (genCol > 0 && (!wordAt || genCol >= genText.length)) {
|
|
250
|
-
|
|
290
|
+
let wordBefore = genText.slice(0, genCol).match(/(\w+)$/);
|
|
291
|
+
// Closing quote — peek inside to find the word (e.g. end of __ripEl('tag'))
|
|
292
|
+
if (!wordBefore && (genText[genCol - 1] === "'" || genText[genCol - 1] === '"')) {
|
|
293
|
+
wordBefore = genText.slice(0, genCol - 1).match(/(\w+)$/);
|
|
294
|
+
}
|
|
251
295
|
if (wordBefore) {
|
|
252
296
|
let word = wordBefore[0];
|
|
253
297
|
let idx = findNearestWord(srcText, word, approx - word.length);
|
package/src/typecheck.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
import { Compiler, getStdlibCode } from './compiler.js';
|
|
14
14
|
import { createRequire } from 'module';
|
|
15
15
|
import { readFileSync, existsSync, readdirSync } from 'fs';
|
|
16
|
-
import { mapToSourcePos, offsetToLine, getLineText, findNearestWord, lineColToOffset, offsetToLineCol, adjustSwitchDiagnostic } from './sourcemap-utils.js';
|
|
16
|
+
import { mapToSourcePos, offsetToLine, getLineText, findNearestWord, lineColToOffset, offsetToLineCol, adjustSwitchDiagnostic, isInjectedOverload } from './sourcemap-utils.js';
|
|
17
17
|
import { resolve, relative, dirname } from 'path';
|
|
18
18
|
import { buildLineMap } from './sourcemaps.js';
|
|
19
19
|
|
|
@@ -250,6 +250,11 @@ export function cleanDiagnosticMessage(msg) {
|
|
|
250
250
|
msg = msg.replace(/\b__RipProps<['"](\w+)['"]>/g, '<$1> props');
|
|
251
251
|
msg = msg.replace(/\b__RipElementMap\b/g, 'ElementMap');
|
|
252
252
|
msg = msg.replace(/\b__ripEl\b/g, 'element');
|
|
253
|
+
// Rewrite verbose __ripEl tag union mismatch into a clean JSX-like message
|
|
254
|
+
msg = msg.replace(
|
|
255
|
+
/Argument of type '"([\w-]+)"' is not assignable to parameter of type '(?:__RipTag|[^']*\bkeyof HTMLElementTagNameMap\b[^']*)'\./,
|
|
256
|
+
"'$1' is not a known HTML or SVG element."
|
|
257
|
+
);
|
|
253
258
|
// Deduplicate consecutive identical lines (unwrapping can collapse nested messages)
|
|
254
259
|
msg = msg.split('\n').filter((line, i, arr) => i === 0 || line.trim() !== arr[i - 1].trim()).join('\n');
|
|
255
260
|
// Remove redundant nested "Type 'X' is not assignable to type 'Y'" when
|
|
@@ -563,23 +568,37 @@ export function compileForCheck(filePath, source, compiler) {
|
|
|
563
568
|
const cl = code.split('\n');
|
|
564
569
|
let changed = false;
|
|
565
570
|
for (let j = 0; j < cl.length; j++) {
|
|
566
|
-
// Match: constructor(
|
|
567
|
-
const cm = cl[j].match(/^\s+constructor\(
|
|
571
|
+
// Match: constructor(_props?: { ... }) or constructor(_props: { ... })
|
|
572
|
+
const cm = cl[j].match(/^\s+constructor\(_props\??\s*:\s*(\{[^}]*\})\)/);
|
|
568
573
|
if (cm) {
|
|
569
|
-
const propsType =
|
|
574
|
+
const propsType = cm[1];
|
|
570
575
|
// Find _init(props) in the same class
|
|
571
576
|
for (let k = j + 1; k < cl.length; k++) {
|
|
572
577
|
if (cl[k].match(/^((?:export\s+)?(?:const|class)\s+)/)) break;
|
|
573
578
|
if (cl[k].match(/^\s+_init\(props\)\s*\{?\s*$/)) {
|
|
574
|
-
|
|
579
|
+
// Check if the body actually uses `props`
|
|
580
|
+
let usesProps = false;
|
|
581
|
+
for (let m = k + 1; m < cl.length; m++) {
|
|
582
|
+
if (/^\s+\}/.test(cl[m]) || /^((?:export\s+)?(?:const|class)\s+)/.test(cl[m])) break;
|
|
583
|
+
if (/\bprops\b/.test(cl[m])) { usesProps = true; break; }
|
|
584
|
+
}
|
|
585
|
+
const paramName = usesProps ? 'props' : '_props';
|
|
586
|
+
cl[k] = cl[k].replace('_init(props)', `_init(${paramName}: ${propsType})`);
|
|
575
587
|
changed = true;
|
|
576
588
|
break;
|
|
577
589
|
}
|
|
578
590
|
}
|
|
579
591
|
}
|
|
580
|
-
// For component classes without typed constructors, type _init(props) as any
|
|
592
|
+
// For component classes without typed constructors, type _init(props) as any.
|
|
593
|
+
// Check if the body actually uses `props` — if not, prefix with _ to suppress 6133.
|
|
581
594
|
if (cl[j].match(/^\s+_init\(props\)\s*\{?\s*$/)) {
|
|
582
|
-
|
|
595
|
+
let usesProps = false;
|
|
596
|
+
for (let k = j + 1; k < cl.length; k++) {
|
|
597
|
+
if (/^\s+\}/.test(cl[k]) || /^((?:export\s+)?(?:const|class)\s+)/.test(cl[k])) break;
|
|
598
|
+
if (/\bprops\b/.test(cl[k])) { usesProps = true; break; }
|
|
599
|
+
}
|
|
600
|
+
const paramName = usesProps ? 'props' : '_props';
|
|
601
|
+
cl[j] = cl[j].replace('_init(props)', `_init(${paramName}: Record<string, any>)`);
|
|
583
602
|
changed = true;
|
|
584
603
|
}
|
|
585
604
|
}
|
|
@@ -737,7 +756,8 @@ export function compileForCheck(filePath, source, compiler) {
|
|
|
737
756
|
"type __RipDomEl<K extends __RipTag> = Omit<__RipElementMap[K], 'querySelector' | 'querySelectorAll' | 'closest' | 'setAttribute' | 'hidden'> & __RipBrowserElement;",
|
|
738
757
|
"type __RipAttrKeys<T> = { [K in keyof T]-?: K extends 'style' ? never : T[K] extends (...args: any[]) => any ? never : K }[keyof T] & string;",
|
|
739
758
|
'type __RipEvents = { [K in keyof HTMLElementEventMap as `@${K}`]?: ((event: HTMLElementEventMap[K]) => void) | null };',
|
|
740
|
-
'type
|
|
759
|
+
'type __RipClassValue = string | boolean | null | undefined | Record<string, boolean> | __RipClassValue[];',
|
|
760
|
+
'type __RipProps<K extends __RipTag> = { [P in __RipAttrKeys<__RipElementMap[K]>]?: __RipElementMap[K][P] } & __RipEvents & { ref?: string; class?: __RipClassValue | __RipClassValue[]; style?: string; [k: `data-${string}`]: any; [k: `aria-${string}`]: any };',
|
|
741
761
|
'declare function __ripEl<K extends __RipTag>(tag: K, props?: __RipProps<K>): void;',
|
|
742
762
|
];
|
|
743
763
|
headerDts = intrinsicDecls.join('\n') + '\n' + headerDts;
|
|
@@ -900,7 +920,7 @@ export function compileForCheck(filePath, source, compiler) {
|
|
|
900
920
|
|
|
901
921
|
// ── Source mapping helpers (delegated to sourcemap-utils.js) ───────
|
|
902
922
|
|
|
903
|
-
export { mapToSourcePos, offsetToLine, getLineText, findNearestWord, lineColToOffset, offsetToLineCol, adjustSwitchDiagnostic } from './sourcemap-utils.js';
|
|
923
|
+
export { mapToSourcePos, offsetToLine, getLineText, findNearestWord, lineColToOffset, offsetToLineCol, adjustSwitchDiagnostic, isInjectedOverload } from './sourcemap-utils.js';
|
|
904
924
|
|
|
905
925
|
// Map a TypeScript diagnostic offset back to a Rip source line number.
|
|
906
926
|
// Returns -1 if the offset falls in the DTS header.
|
|
@@ -1100,6 +1120,16 @@ export async function runCheck(targetDir, opts = {}) {
|
|
|
1100
1120
|
if (d.start === undefined) continue;
|
|
1101
1121
|
if (SKIP_CODES.has(d.code)) continue;
|
|
1102
1122
|
|
|
1123
|
+
// Skip 6133 on compiler-generated _render() construction variables (_0, _1, …)
|
|
1124
|
+
if ((d.code === 6133 || d.code === 6196) && d.length > 0) {
|
|
1125
|
+
const span = entry.tsContent.substring(d.start, d.start + d.length);
|
|
1126
|
+
if (/^_\d+$/.test(span.trim())) continue;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Skip diagnostics on injected overload signatures — the real function
|
|
1130
|
+
// definition already carries the same diagnostic.
|
|
1131
|
+
if (isInjectedOverload(entry, d.start)) continue;
|
|
1132
|
+
|
|
1103
1133
|
const pos = mapToSourcePos(entry, d.start);
|
|
1104
1134
|
if (!pos) continue;
|
|
1105
1135
|
|
package/src/types.js
CHANGED
|
@@ -1149,7 +1149,7 @@ export function emitTypes(tokens, sexpr = null, source = '') {
|
|
|
1149
1149
|
preamble.push("type __RipDomEl<K extends __RipTag> = Omit<__RipElementMap[K], 'querySelector' | 'querySelectorAll' | 'closest' | 'setAttribute' | 'hidden'> & __RipBrowserElement;");
|
|
1150
1150
|
preamble.push("type __RipAttrKeys<T> = { [K in keyof T]-?: K extends 'style' ? never : T[K] extends (...args: any[]) => any ? never : K }[keyof T] & string;");
|
|
1151
1151
|
preamble.push('type __RipEvents = { [K in keyof HTMLElementEventMap as `@${K}`]?: ((event: HTMLElementEventMap[K]) => void) | null };');
|
|
1152
|
-
preamble.push('type __RipProps<K extends __RipTag> = { [P in __RipAttrKeys<__RipElementMap[K]>]?: __RipElementMap[K][P] } & __RipEvents & { ref?: string; class?:
|
|
1152
|
+
preamble.push('type __RipClassValue = string | boolean | null | undefined | Record<string, boolean> | __RipClassValue[]'); preamble.push('type __RipProps<K extends __RipTag> = { [P in __RipAttrKeys<__RipElementMap[K]>]?: __RipElementMap[K][P] } & __RipEvents & { ref?: string; class?: __RipClassValue | __RipClassValue[]; style?: string; [k: `data-${string}`]: any; [k: `aria-${string}`]: any };');
|
|
1153
1153
|
}
|
|
1154
1154
|
if (/\bARIA\./.test(source)) {
|
|
1155
1155
|
preamble.push("type __RipAriaNavHandlers = { next?: () => void; prev?: () => void; first?: () => void; last?: () => void; select?: () => void; dismiss?: () => void; tab?: () => void; char?: () => void; };");
|
|
@@ -1320,12 +1320,12 @@ function emitComponentTypes(sexpr, lines, indent, indentLevel, componentVars, so
|
|
|
1320
1320
|
hasDefault = false;
|
|
1321
1321
|
if (!isProp && propName) componentVars.add(propName);
|
|
1322
1322
|
} else if (mHead === 'object') {
|
|
1323
|
-
// Method definitions: (object (methodName (-> (params...) (block ...))
|
|
1323
|
+
// Method definitions: (object (: methodName (-> (params...) (block ...))))
|
|
1324
1324
|
for (let i = 1; i < member.length; i++) {
|
|
1325
1325
|
let entry = member[i];
|
|
1326
|
-
if (!Array.isArray(entry) || entry.length <
|
|
1327
|
-
let methName = entry[
|
|
1328
|
-
let funcDef = entry[
|
|
1326
|
+
if (!Array.isArray(entry) || entry.length < 3) continue;
|
|
1327
|
+
let methName = entry[1]?.valueOf?.() ?? entry[1];
|
|
1328
|
+
let funcDef = entry[2];
|
|
1329
1329
|
if (!Array.isArray(funcDef)) continue;
|
|
1330
1330
|
let fHead = funcDef[0]?.valueOf?.() ?? funcDef[0];
|
|
1331
1331
|
if (fHead !== '->' && fHead !== '=>') continue;
|