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.
@@ -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
- const wordAt = genText.slice(genCol).match(/^\w+/);
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
- const wordBefore = genText.slice(0, genCol).match(/(\w+)$/);
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(props?: { ... }) or constructor(props: { ... })
567
- const cm = cl[j].match(/^\s+constructor\((props)\??\s*:\s*(\{[^}]*\})\)/);
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 = `${cm[1]}: ${cm[2]}`;
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
- cl[k] = cl[k].replace('_init(props)', `_init(${propsType})`);
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
- cl[j] = cl[j].replace('_init(props)', '_init(props: Record<string, any>)');
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 __RipProps<K extends __RipTag> = { [P in __RipAttrKeys<__RipElementMap[K]>]?: __RipElementMap[K][P] } & __RipEvents & { ref?: string; class?: string; style?: string; [k: `data-${string}`]: any; [k: `aria-${string}`]: any };',
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?: string; style?: string; [k: `data-${string}`]: any; [k: `aria-${string}`]: any };');
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 < 2) continue;
1327
- let methName = entry[0]?.valueOf?.() ?? entry[0];
1328
- let funcDef = entry[1];
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;