reslop 0.1.1 → 0.1.3

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/lib/github.js CHANGED
@@ -3,8 +3,9 @@
3
3
  const { delay } = require('metautil');
4
4
 
5
5
  const diff = require('./diff.js');
6
- const { parseDiff, itemsFromFiles } = diff;
7
- const { itemPath } = require('./files.js');
6
+ const { parseDiff, itemsFromFiles, isCtxType } = diff;
7
+ const { itemPath, isPathInScope } = require('./files.js');
8
+ const { foldDepItems } = require('./deps.js');
8
9
 
9
10
  const GITHUB_API = 'https://api.github.com';
10
11
  const API_VERSION = '2022-11-28';
@@ -70,7 +71,6 @@ const parseGithubPrUrl = (text) => {
70
71
 
71
72
  const githubToken = (env) => {
72
73
  const from = env ?? process.env;
73
- if (!isRecord(from)) return '';
74
74
  return from.GITHUB_TOKEN || from.GH_TOKEN || '';
75
75
  };
76
76
 
@@ -134,18 +134,15 @@ const isRetryable = (error) => {
134
134
  const retry = async (run, options) => {
135
135
  const attempts = options.attempts ?? RETRY_ATTEMPTS;
136
136
  const delayMs = options.delayMs ?? RETRY_DELAY_MS;
137
- let lastError = null;
138
- for (let i = 0; i < attempts; i++) {
137
+ for (let i = 0; ; i++) {
139
138
  try {
140
139
  return await run();
141
140
  } catch (error) {
142
- lastError = error;
143
141
  const isLast = i === attempts - 1;
144
142
  if (isLast || !isRetryable(error)) throw error;
145
143
  await delay(delayMs * (i + 1));
146
144
  }
147
145
  }
148
- throw lastError;
149
146
  };
150
147
 
151
148
  const githubHeaders = (token, accept) => {
@@ -164,7 +161,8 @@ const headerValue = (headers, name) => {
164
161
  return headers.get(name) || '';
165
162
  }
166
163
  const wanted = name.toLowerCase();
167
- for (const [key, value] of Object.entries(headers)) {
164
+ for (const key of Object.keys(headers)) {
165
+ const value = headers[key];
168
166
  if (key.toLowerCase() === wanted) return `${value}`;
169
167
  }
170
168
  return '';
@@ -252,19 +250,6 @@ const requestGraphql = async (query, variables, options) => {
252
250
 
253
251
  const changePath = (file) => file.newPath || file.oldPath || '';
254
252
 
255
- const normalizePathSpec = (spec) =>
256
- `${spec ?? ''}`.replaceAll('\\', '/').replace(/\/+$/, '');
257
-
258
- const isPathInScope = (rel, paths) => {
259
- if (!paths.length) return true;
260
- for (const spec of paths) {
261
- const norm = normalizePathSpec(spec);
262
- if (!norm || norm === '.') return true;
263
- if (rel === norm || rel.startsWith(`${norm}/`)) return true;
264
- }
265
- return false;
266
- };
267
-
268
253
  const filterChangeFiles = (files, paths) => {
269
254
  if (!paths.length) return files;
270
255
  const kept = [];
@@ -314,14 +299,23 @@ const readRepoName = (data, pr) => {
314
299
  return `${pr.owner}/${pr.repo}`;
315
300
  };
316
301
 
302
+ const pushChangeFile = (files, seen, rel) => {
303
+ if (!rel || seen.has(rel)) return;
304
+ seen.add(rel);
305
+ files.push(rel);
306
+ };
307
+
317
308
  const changeFiles = (items) => {
318
309
  const files = [];
319
310
  const seen = new Set();
320
311
  for (const item of items) {
312
+ if (item.dep) {
313
+ const depFiles = item.dep.files ?? [];
314
+ for (const rel of depFiles) pushChangeFile(files, seen, rel);
315
+ continue;
316
+ }
321
317
  const rel = itemPath(item);
322
- if (!rel || seen.has(rel)) continue;
323
- seen.add(rel);
324
- files.push(rel);
318
+ pushChangeFile(files, seen, rel);
325
319
  }
326
320
  return files;
327
321
  };
@@ -406,8 +400,12 @@ const locateLineInItem = (item, rel, line, side) => {
406
400
  const inBlock = entry.blockId === item.blockId;
407
401
  return { inBlock };
408
402
  }
409
- if (entry.type === 'ctx' || entry.type === 'del') oldNo += 1;
410
- if (entry.type === 'ctx' || entry.type === 'add') newNo += 1;
403
+ if (isCtxType(entry.type) || entry.type === 'del') {
404
+ oldNo += 1;
405
+ }
406
+ if (isCtxType(entry.type) || entry.type === 'add') {
407
+ newNo += 1;
408
+ }
411
409
  }
412
410
  return null;
413
411
  };
@@ -546,7 +544,6 @@ const loadReviewThreads = async (pr, options) => {
546
544
  const threadComments = (thread) => {
547
545
  if (!isRecord(thread)) return [];
548
546
  const comments = thread.comments;
549
- if (Array.isArray(comments)) return comments;
550
547
  if (isRecord(comments) && Array.isArray(comments.nodes)) {
551
548
  return comments.nodes;
552
549
  }
@@ -581,20 +578,18 @@ const loadDiscussion = async (pr, options) => {
581
578
  return { reviewComments, reviews, issueComments, resolvedById };
582
579
  };
583
580
 
584
- const emptyImported = () => ({ feedback: [], todos: [] });
585
-
586
581
  const loadImportedNotes = async (pr, items, paths, options) => {
587
582
  try {
588
583
  const discussion = await loadDiscussion(pr, options);
589
584
  return discussionToNotes(discussion, items, paths);
590
585
  } catch {
591
586
  // ignore: discussion import is optional
592
- return emptyImported();
587
+ return { feedback: [], todos: [] };
593
588
  }
594
589
  };
595
590
 
596
591
  const loadPullRequest = async (pr, options = {}) => {
597
- const token = options.token ?? githubToken(options.env);
592
+ const token = options.token ?? '';
598
593
  const fetchImpl = options.fetch ?? globalThis.fetch;
599
594
  const paths = options.paths ?? [];
600
595
  const retryOpts = options.retry ?? {};
@@ -611,7 +606,8 @@ const loadPullRequest = async (pr, options = {}) => {
611
606
  });
612
607
  const parsed = parseDiff(diffPage.body);
613
608
  const files = filterChangeFiles(parsed, paths);
614
- const items = itemsFromFiles(files, 'pr');
609
+ const parsedItems = itemsFromFiles(files, 'pr');
610
+ const items = foldDepItems(parsedItems);
615
611
  const change = toChange(pr, data, items);
616
612
  const sourceLabel = `#${change.number}`;
617
613
  const imported = await loadImportedNotes(pr, items, paths, request);
@@ -619,8 +615,6 @@ const loadPullRequest = async (pr, options = {}) => {
619
615
  };
620
616
 
621
617
  module.exports = {
622
- GITHUB_API,
623
- PR_TODO_FILE,
624
618
  parseGithubPrUrl,
625
619
  githubToken,
626
620
  filterChangeFiles,
package/lib/highlight.js CHANGED
@@ -294,7 +294,7 @@ const NEXT_EXPECT = {
294
294
  };
295
295
 
296
296
  const wordStyle = (words, style) => {
297
- const table = Object.create(null);
297
+ const table = {};
298
298
  for (const word of words) table[word] = style;
299
299
  return table;
300
300
  };
@@ -424,8 +424,6 @@ const styleProperty = ({ out }) => {
424
424
  return /\.$/.test(prev) ? 'property' : null;
425
425
  };
426
426
 
427
- const styleVariable = () => 'variable';
428
-
429
427
  const IDENT_RULES = [
430
428
  styleExpected,
431
429
  styleKeyword,
@@ -434,7 +432,6 @@ const IDENT_RULES = [
434
432
  styleCall,
435
433
  styleAssign,
436
434
  styleProperty,
437
- styleVariable,
438
435
  ];
439
436
 
440
437
  const identStyle = (ctx) => {
@@ -442,7 +439,7 @@ const identStyle = (ctx) => {
442
439
  const style = rule(ctx);
443
440
  if (style) return style;
444
441
  }
445
- return 'plain';
442
+ return 'variable';
446
443
  };
447
444
 
448
445
  const readString = (source, i) => {
@@ -519,150 +516,293 @@ const isRegexContext = (out) => {
519
516
  return afterOperator || afterKeyword;
520
517
  };
521
518
 
522
- class JsHighlighter {
523
- highlightJsFamily(source, ts = false) {
524
- const out = [];
525
- let i = 0;
526
- const n = source.length;
527
- let expectName = null;
528
-
529
- while (i < n) {
530
- const ch = source[i];
531
- const next = source[i + 1];
532
-
533
- if (ch === '/' && next === '/') {
534
- const { text, end } = readUntilNl(source, i);
535
- emit(out, 'comment', text);
536
- i = end;
537
- continue;
538
- }
539
-
540
- if (ch === '/' && next === '*') {
541
- const { text, end } = readBlockComment(source, i);
542
- emit(out, 'comment', text);
543
- i = end;
544
- continue;
545
- }
546
-
547
- if (ch === '`') {
548
- const r = this.highlightTemplate(source, i);
549
- for (const t of r.tokens) out.push(t);
550
- i = r.end;
551
- continue;
552
- }
553
-
554
- if (QUOTES.includes(ch)) {
555
- const { text, end } = readString(source, i);
556
- emit(out, 'string', text);
557
- i = end;
558
- continue;
519
+ const highlightTemplate = (source, i, highlightExpr) => {
520
+ const n = source.length;
521
+ let j = i + 1;
522
+ const parts = [];
523
+ emit(parts, 'template', '`');
524
+ let buf = '';
525
+ const flush = () => {
526
+ if (buf) {
527
+ emit(parts, 'template', buf);
528
+ buf = '';
529
+ }
530
+ };
531
+ while (j < n) {
532
+ if (source[j] === '\\') {
533
+ flush();
534
+ emit(parts, 'escape', source.slice(j, j + 2));
535
+ j += 2;
536
+ continue;
537
+ }
538
+ if (source[j] === '`') {
539
+ flush();
540
+ emit(parts, 'template', '`');
541
+ j += 1;
542
+ break;
543
+ }
544
+ if (source[j] === '$' && source[j + 1] === '{') {
545
+ flush();
546
+ emit(parts, 'interpolation', '${');
547
+ j += 2;
548
+ const interp = readTemplateInterp(source, j);
549
+ const inner = highlightExpr(interp.expr);
550
+ for (const t of inner) parts.push(t);
551
+ j = interp.end;
552
+ if (source[j] === '}') {
553
+ emit(parts, 'interpolation', '}');
554
+ j += 1;
559
555
  }
556
+ continue;
557
+ }
558
+ buf += source[j];
559
+ j += 1;
560
+ }
561
+ flush();
562
+ return { tokens: parts, end: j };
563
+ };
560
564
 
561
- if (ch === '/' && isRegexContext(out)) {
562
- const { text, end } = readRegex(source, i);
563
- emit(out, 'regex', text);
564
- i = end;
565
- continue;
566
- }
565
+ const isJsxTagStart = (source, i) => {
566
+ if (source[i] !== '<') return false;
567
+ if (i > 0 && /[A-Za-z0-9_$]/.test(source[i - 1])) return false;
568
+ const next = source[i + 1];
569
+ if (next === '>' || next === '/') return true;
570
+ if (!/[A-Za-z_$]/.test(next || '')) return false;
571
+ let j = i + 1;
572
+ while (j < source.length && /[A-Za-z0-9_$.:-]/.test(source[j])) j += 1;
573
+ const after = source[skipWs(source, j)];
574
+ if (after === '>' || after === '/' || after === '{') return true;
575
+ return /[A-Za-z_$]/.test(after || '');
576
+ };
567
577
 
568
- if (/[0-9]/.test(ch) || (ch === '.' && /[0-9]/.test(next || ''))) {
569
- const { text, end } = readJsNumber(source, i);
570
- emit(out, 'number', text);
571
- i = end;
572
- continue;
573
- }
578
+ const jsxNameStyle = (name) => {
579
+ if (/^[A-Z]/.test(name) || name.includes('.')) return 'className';
580
+ return 'tag';
581
+ };
574
582
 
575
- if (ch === '@') {
576
- let j = i + 1;
577
- while (j < n && /[A-Za-z0-9_$]/.test(source[j])) j += 1;
578
- emit(out, 'decorator', source.slice(i, j));
579
- i = j;
580
- expectName = null;
581
- continue;
582
- }
583
+ const readJsxName = (source, i) => {
584
+ let j = i;
585
+ while (j < source.length && /[A-Za-z0-9_$.:-]/.test(source[j])) j += 1;
586
+ return { text: source.slice(i, j), end: j };
587
+ };
583
588
 
584
- if (/[A-Za-z_$]/.test(ch)) {
585
- const word = peekIdent(source, i);
586
- const end = i + word.length;
587
- const k = skipWs(source, end);
588
- const after = source[k];
589
- const ctx = { word, ts, after, source, k, out, expectName };
590
- emit(out, identStyle(ctx), word);
591
- if (EXPECT_STYLE[expectName]) expectName = null;
592
- else expectName = NEXT_EXPECT[word] ?? null;
593
- i = end;
594
- continue;
595
- }
589
+ const highlightJsxExpr = (source, i, inner) => {
590
+ const out = [];
591
+ emit(out, 'interpolation', '{');
592
+ const interp = readTemplateInterp(source, i + 1);
593
+ const tokens = inner(interp.expr);
594
+ for (const t of tokens) out.push(t);
595
+ let end = interp.end;
596
+ if (source[end] === '}') {
597
+ emit(out, 'interpolation', '}');
598
+ end += 1;
599
+ }
600
+ return { tokens: out, end };
601
+ };
596
602
 
597
- if (/[=<>!+\-*/%&|^~?:]/.test(ch)) {
598
- const { text, end } = readJsOperator(source, i);
599
- emit(out, 'operator', text);
600
- i = end;
601
- expectName = null;
602
- continue;
603
- }
604
- if (/[{}()[\];,.]/.test(ch)) {
605
- emit(out, 'punct', ch);
606
- i += 1;
607
- continue;
608
- }
603
+ const highlightJsxTagTail = (source, i, inner, out) => {
604
+ const n = source.length;
605
+ let selfClosing = false;
606
+ while (i < n) {
607
+ const ch = source[i];
608
+ if (ch === '>') {
609
+ emit(out, 'punct', '>');
610
+ i += 1;
611
+ break;
612
+ }
613
+ if (ch === '/' && source[i + 1] === '>') {
614
+ emit(out, 'punct', '/>');
615
+ i += 2;
616
+ selfClosing = true;
617
+ break;
618
+ }
619
+ if (/\s/.test(ch)) {
609
620
  emit(out, 'plain', ch);
610
621
  i += 1;
622
+ continue;
611
623
  }
612
- return out;
624
+ if (ch === '{') {
625
+ const expr = highlightJsxExpr(source, i, inner);
626
+ for (const t of expr.tokens) out.push(t);
627
+ i = expr.end;
628
+ continue;
629
+ }
630
+ if (QUOTES.includes(ch)) {
631
+ const { text, end } = readString(source, i);
632
+ emit(out, 'string', text);
633
+ i = end;
634
+ continue;
635
+ }
636
+ if (ch === '=') {
637
+ emit(out, 'punct', '=');
638
+ i += 1;
639
+ continue;
640
+ }
641
+ if (/[A-Za-z_$]/.test(ch)) {
642
+ const { text, end } = readJsxName(source, i);
643
+ emit(out, 'attr', text);
644
+ i = end;
645
+ continue;
646
+ }
647
+ emit(out, 'punct', ch);
648
+ i += 1;
613
649
  }
650
+ return { end: i, selfClosing };
651
+ };
614
652
 
615
- highlightTemplate(source, i) {
616
- const n = source.length;
617
- let j = i + 1;
618
- const parts = [];
619
- emit(parts, 'template', '`');
620
- let buf = '';
621
- const flush = () => {
622
- if (buf) {
623
- emit(parts, 'template', buf);
624
- buf = '';
625
- }
626
- };
627
- while (j < n) {
628
- if (source[j] === '\\') {
629
- flush();
630
- emit(parts, 'escape', source.slice(j, j + 2));
631
- j += 2;
632
- continue;
633
- }
634
- if (source[j] === '`') {
635
- flush();
636
- emit(parts, 'template', '`');
637
- j += 1;
638
- break;
639
- }
640
- if (source[j] === '$' && source[j + 1] === '{') {
641
- flush();
642
- emit(parts, 'interpolation', '${');
643
- j += 2;
644
- const interp = readTemplateInterp(source, j);
645
- const inner = this.highlightJsFamily(interp.expr, false);
646
- for (const t of inner) parts.push(t);
647
- j = interp.end;
648
- if (source[j] === '}') {
649
- emit(parts, 'interpolation', '}');
650
- j += 1;
651
- }
652
- continue;
653
- }
654
- buf += source[j];
655
- j += 1;
653
+ const highlightJsxElement = (source, i, inner) => {
654
+ const out = [];
655
+ const n = source.length;
656
+ emit(out, 'punct', '<');
657
+ i += 1;
658
+ let closing = false;
659
+ if (source[i] === '/') {
660
+ emit(out, 'punct', '/');
661
+ i += 1;
662
+ closing = true;
663
+ }
664
+ const nameRead = readJsxName(source, i);
665
+ const name = nameRead.text;
666
+ if (name) {
667
+ emit(out, jsxNameStyle(name), name);
668
+ i = nameRead.end;
669
+ }
670
+ const tail = highlightJsxTagTail(source, i, inner, out);
671
+ i = tail.end;
672
+ if (closing || tail.selfClosing) return { tokens: out, end: i };
673
+ while (i < n) {
674
+ if (source.startsWith('</', i)) {
675
+ const close = highlightJsxElement(source, i, inner);
676
+ for (const t of close.tokens) out.push(t);
677
+ return { tokens: out, end: close.end };
678
+ }
679
+ if (source[i] === '{') {
680
+ const expr = highlightJsxExpr(source, i, inner);
681
+ for (const t of expr.tokens) out.push(t);
682
+ i = expr.end;
683
+ continue;
684
+ }
685
+ if (source[i] === '<' && isJsxTagStart(source, i)) {
686
+ const child = highlightJsxElement(source, i, inner);
687
+ for (const t of child.tokens) out.push(t);
688
+ i = child.end;
689
+ continue;
690
+ }
691
+ let j = i;
692
+ while (j < n && source[j] !== '<' && source[j] !== '{') j += 1;
693
+ if (j === i) {
694
+ emit(out, 'plain', source[i]);
695
+ i += 1;
696
+ continue;
656
697
  }
657
- flush();
658
- return { tokens: parts, end: j };
698
+ emit(out, 'plain', source.slice(i, j));
699
+ i = j;
659
700
  }
660
- }
701
+ return { tokens: out, end: i };
702
+ };
703
+
704
+ const highlightJsFamily = (source, options = {}) => {
705
+ const ts = options.ts === true;
706
+ const jsx = options.jsx === true;
707
+ const inner = (expr) => highlightJsFamily(expr, options);
708
+ const out = [];
709
+ let i = 0;
710
+ const n = source.length;
711
+ let expectName = null;
712
+
713
+ while (i < n) {
714
+ const ch = source[i];
715
+ const next = source[i + 1];
716
+
717
+ if (ch === '/' && next === '/') {
718
+ const { text, end } = readUntilNl(source, i);
719
+ emit(out, 'comment', text);
720
+ i = end;
721
+ continue;
722
+ }
723
+
724
+ if (ch === '/' && next === '*') {
725
+ const { text, end } = readBlockComment(source, i);
726
+ emit(out, 'comment', text);
727
+ i = end;
728
+ continue;
729
+ }
730
+
731
+ if (ch === '`') {
732
+ const r = highlightTemplate(source, i, inner);
733
+ for (const t of r.tokens) out.push(t);
734
+ i = r.end;
735
+ continue;
736
+ }
737
+
738
+ if (QUOTES.includes(ch)) {
739
+ const { text, end } = readString(source, i);
740
+ emit(out, 'string', text);
741
+ i = end;
742
+ continue;
743
+ }
744
+
745
+ if (ch === '/' && isRegexContext(out)) {
746
+ const { text, end } = readRegex(source, i);
747
+ emit(out, 'regex', text);
748
+ i = end;
749
+ continue;
750
+ }
751
+
752
+ if (/[0-9]/.test(ch) || (ch === '.' && /[0-9]/.test(next || ''))) {
753
+ const { text, end } = readJsNumber(source, i);
754
+ emit(out, 'number', text);
755
+ i = end;
756
+ continue;
757
+ }
758
+
759
+ if (ch === '@') {
760
+ let j = i + 1;
761
+ while (j < n && /[A-Za-z0-9_$]/.test(source[j])) j += 1;
762
+ emit(out, 'decorator', source.slice(i, j));
763
+ i = j;
764
+ expectName = null;
765
+ continue;
766
+ }
661
767
 
662
- const jsHighlighter = new JsHighlighter();
768
+ if (/[A-Za-z_$]/.test(ch)) {
769
+ const word = peekIdent(source, i);
770
+ const end = i + word.length;
771
+ const k = skipWs(source, end);
772
+ const after = source[k];
773
+ const ctx = { word, ts, after, source, k, out, expectName };
774
+ emit(out, identStyle(ctx), word);
775
+ if (EXPECT_STYLE[expectName]) expectName = null;
776
+ else expectName = NEXT_EXPECT[word] ?? null;
777
+ i = end;
778
+ continue;
779
+ }
780
+
781
+ if (jsx && ch === '<' && isJsxTagStart(source, i)) {
782
+ const node = highlightJsxElement(source, i, inner);
783
+ for (const t of node.tokens) out.push(t);
784
+ i = node.end;
785
+ expectName = null;
786
+ continue;
787
+ }
663
788
 
664
- const highlightJsFamily = (source, ts = false) =>
665
- jsHighlighter.highlightJsFamily(source, ts);
789
+ if (/[=<>!+\-*/%&|^~?:]/.test(ch)) {
790
+ const { text, end } = readJsOperator(source, i);
791
+ emit(out, 'operator', text);
792
+ i = end;
793
+ expectName = null;
794
+ continue;
795
+ }
796
+ if (/[{}()[\];,.]/.test(ch)) {
797
+ emit(out, 'punct', ch);
798
+ i += 1;
799
+ continue;
800
+ }
801
+ emit(out, 'plain', ch);
802
+ i += 1;
803
+ }
804
+ return out;
805
+ };
666
806
 
667
807
  const highlightJson = (source) => {
668
808
  const out = [];
@@ -1146,21 +1286,23 @@ const highlightTxt = (source) => {
1146
1286
  return out;
1147
1287
  };
1148
1288
 
1149
- const highlightJs = (source) => highlightJsFamily(source, false);
1150
- const highlightTs = (source) => highlightJsFamily(source, true);
1289
+ const highlightJs = (source) => highlightJsFamily(source, {});
1290
+ const highlightTs = (source) => highlightJsFamily(source, { ts: true });
1291
+ const highlightJsx = (source) => highlightJsFamily(source, { jsx: true });
1292
+ const highlightTsx = (source) =>
1293
+ highlightJsFamily(source, { ts: true, jsx: true });
1151
1294
 
1152
1295
  const HIGHLIGHTERS = {
1153
1296
  js: highlightJs,
1154
1297
  mjs: highlightJs,
1155
1298
  ts: highlightTs,
1299
+ jsx: highlightJsx,
1300
+ tsx: highlightTsx,
1156
1301
  json: highlightJson,
1157
1302
  css: highlightCss,
1158
1303
  html: highlightHtml,
1159
1304
  csv: highlightCsv,
1160
1305
  bash: highlightBash,
1161
- sh: highlightBash,
1162
- shell: highlightBash,
1163
- zsh: highlightBash,
1164
1306
  log: highlightLog,
1165
1307
  dot: highlightDot,
1166
1308
  txt: highlightTxt,
@@ -1224,8 +1366,7 @@ const overlayTokens = (tokens, spans) => {
1224
1366
  };
1225
1367
 
1226
1368
  module.exports = {
1369
+ tokensText,
1227
1370
  tokenize,
1228
1371
  overlayTokens,
1229
- tokensText,
1230
- HIGHLIGHTERS,
1231
1372
  };