Skip to content

Commit d293e02

Browse files
committed
feat: suggest correct paths based on levenshtein distance
1 parent 8c76c2d commit d293e02

File tree

4 files changed

+125
-3
lines changed

4 files changed

+125
-3
lines changed

.changeset/gentle-crabs-provide.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'check-html-links': minor
3+
---
4+
5+
suggest correct paths based on levenshtein distance

packages/check-html-links/src/CheckHtmlLinksCli.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class CheckHtmlLinksCli {
7575
)} missing reference targets (used by ${referenceCount} links) while checking ${
7676
files.length
7777
} files:`,
78-
...formatErrors(errors)
78+
...formatErrors(errors, { files })
7979
.split('\n')
8080
.map(line => ` ${line}`),
8181
`Checking links duration: ${performance[0]}s ${performance[1] / 1000000}ms`,

packages/check-html-links/src/formatErrors.js

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import path from 'path';
22
import chalk from 'chalk';
3+
import levenshtein from './levenshtein.js';
34

45
/** @typedef {import('../types/main').Error} Error */
56

67
/**
78
* @param {Error[]} errors
8-
* @param {*} relativeFrom
9+
* @param {{ relativeFrom: string; files: string[] }} opts
910
*/
10-
export function formatErrors(errors, relativeFrom = process.cwd()) {
11+
export function formatErrors(errors, { relativeFrom = process.cwd(), files }) {
1112
let output = [];
1213
let number = 0;
1314
for (const error of errors) {
@@ -41,6 +42,27 @@ export function formatErrors(errors, relativeFrom = process.cwd()) {
4142
output.push(` ... ${more} more references to this target`);
4243
}
4344
output.push('');
45+
46+
/**
47+
* Also consider finding the updated path. This can be useful when documentation is restructured
48+
* For instance, the folder name was changed, but the file name was not.
49+
*/
50+
let suggestion;
51+
let lowestScore = -1;
52+
files.forEach(file => {
53+
const filePathToCompare = file.replace(relativeFrom + '/', '');
54+
const score = levenshtein(filePathToCompare, filePath);
55+
if (lowestScore !== -1 || score < lowestScore) {
56+
lowestScore = score;
57+
suggestion = filePathToCompare;
58+
}
59+
});
60+
61+
if (suggestion) {
62+
output.push(
63+
chalk.italic(`Suggestion: did you mean ${chalk.magenta(suggestion)} instead?\n\n`),
64+
);
65+
}
4466
}
4567
return output.join('\n');
4668
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/* eslint-disable */
2+
// https://github.com/gustf/js-levenshtein/blob/master/index.js
3+
4+
function _min(d0, d1, d2, bx, ay) {
5+
return d0 < d1 || d2 < d1 ? (d0 > d2 ? d2 + 1 : d0 + 1) : bx === ay ? d1 : d1 + 1;
6+
}
7+
8+
export default function (a, b) {
9+
if (a === b) {
10+
return 0;
11+
}
12+
13+
if (a.length > b.length) {
14+
var tmp = a;
15+
a = b;
16+
b = tmp;
17+
}
18+
19+
var la = a.length;
20+
var lb = b.length;
21+
22+
while (la > 0 && a.charCodeAt(la - 1) === b.charCodeAt(lb - 1)) {
23+
la--;
24+
lb--;
25+
}
26+
27+
var offset = 0;
28+
29+
while (offset < la && a.charCodeAt(offset) === b.charCodeAt(offset)) {
30+
offset++;
31+
}
32+
33+
la -= offset;
34+
lb -= offset;
35+
36+
if (la === 0 || lb < 3) {
37+
return lb;
38+
}
39+
40+
var x = 0;
41+
var y;
42+
var d0;
43+
var d1;
44+
var d2;
45+
var d3;
46+
var dd;
47+
var dy;
48+
var ay;
49+
var bx0;
50+
var bx1;
51+
var bx2;
52+
var bx3;
53+
54+
var vector = [];
55+
56+
for (y = 0; y < la; y++) {
57+
vector.push(y + 1);
58+
vector.push(a.charCodeAt(offset + y));
59+
}
60+
61+
var len = vector.length - 1;
62+
63+
for (; x < lb - 3; ) {
64+
bx0 = b.charCodeAt(offset + (d0 = x));
65+
bx1 = b.charCodeAt(offset + (d1 = x + 1));
66+
bx2 = b.charCodeAt(offset + (d2 = x + 2));
67+
bx3 = b.charCodeAt(offset + (d3 = x + 3));
68+
dd = x += 4;
69+
for (y = 0; y < len; y += 2) {
70+
dy = vector[y];
71+
ay = vector[y + 1];
72+
d0 = _min(dy, d0, d1, bx0, ay);
73+
d1 = _min(d0, d1, d2, bx1, ay);
74+
d2 = _min(d1, d2, d3, bx2, ay);
75+
dd = _min(d2, d3, dd, bx3, ay);
76+
vector[y] = dd;
77+
d3 = d2;
78+
d2 = d1;
79+
d1 = d0;
80+
d0 = dy;
81+
}
82+
}
83+
84+
for (; x < lb; ) {
85+
bx0 = b.charCodeAt(offset + (d0 = x));
86+
dd = ++x;
87+
for (y = 0; y < len; y += 2) {
88+
dy = vector[y];
89+
vector[y] = dd = _min(dy, d0, dd, bx0, vector[y + 1]);
90+
d0 = dy;
91+
}
92+
}
93+
94+
return dd;
95+
}

0 commit comments

Comments
 (0)