Skip to content

Commit 34c97c1

Browse files
committed
Fix source audit issues
1 parent 40a9a4b commit 34c97c1

9 files changed

Lines changed: 106 additions & 50 deletions

File tree

src/components/SidebarItem/SidebarItem.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import list2Tree from "../../utilities/list2Tree/index.js";
1414
* @returns {boolean}
1515
*/
1616
function isOpen(currentPage, url) {
17-
return new RegExp(`${currentPage}/?$`).test(url);
17+
const normalize = (value = "") => value.replace(/\/?$/, "/");
18+
return normalize(url) === normalize(currentPage);
1819
}
1920

2021
/**

src/components/SidebarItem/SidebarItem.test.jsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,18 @@ describe("SidebarItem", () => {
3838
expect(wrapper.getAttribute("data-open")).toBe("true");
3939
});
4040

41+
it("matches URLs with regexp characters literally", () => {
42+
const { container } = renderWithRouter(
43+
<SidebarItem
44+
{...defaultProps}
45+
currentPage="/api/v3.0/[draft]"
46+
url="/api/v3.0/[draft]/"
47+
/>,
48+
);
49+
const wrapper = container.firstChild;
50+
expect(wrapper.getAttribute("data-open")).toBe("true");
51+
});
52+
4153
it("toggles open state when chevron button is clicked", () => {
4254
const anchors = [
4355
{

src/remark-plugins/remark-refractor/index.mjs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,14 @@ function attacher({ include, exclude } = {}) {
5151
try {
5252
data.hChildren = refractor.highlight(node.value, lang).children;
5353
for (const child of data.hChildren) {
54-
if (
55-
child.properties &&
56-
child.properties.className.includes("keyword")
57-
) {
58-
if (child.children[1]) {
59-
child.properties.componentname = child.children[1].value.trim();
54+
if (child.properties?.className?.includes("keyword")) {
55+
const componentName = child.children?.[1]?.value?.trim();
56+
if (componentName) {
57+
child.properties.componentname = componentName;
6058
}
61-
if (child.children[2]) {
62-
child.properties.url =
63-
child.children[2].children[0].value.replaceAll('"', "");
59+
const url = child.children?.[2]?.children?.[0]?.value;
60+
if (url) {
61+
child.properties.url = url.replaceAll('"', "");
6462
}
6563
}
6664
}

src/utilities/content-tree-enhancers.mjs

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,7 @@ export const enhance = (tree, options) => {
5252
.use(extractAnchors, { anchors, levels: 3 })
5353
.use(remarkRemoveHeadingId)
5454
.use(remarkHtml)
55-
.process(content, (err) => {
56-
if (err) {
57-
throw err;
58-
}
59-
});
55+
.processSync(content);
6056

6157
tree.anchors = anchors;
6258

@@ -73,14 +69,16 @@ export const enhance = (tree, options) => {
7369

7470
const isBlogItem = normalizedPath.includes("/blog/");
7571
if (isBlogItem) {
76-
const teaser = (body || "")
72+
const teaserLines = (body || "")
7773
.split("\n")
78-
.filter((line) => line.trim() && !line.trim().startsWith("#"))
74+
.filter((line) => line.trim() && !line.trim().startsWith("#"));
75+
const teaserText = teaserLines
7976
.slice(0, 3)
8077
.join(" ")
81-
.replaceAll(/\[([^\]]+)\]\([^)]+\)/g, "$1") // Strip markdown links but keep text
82-
.slice(0, 240);
83-
tree.teaser = `${teaser}...`;
78+
.replaceAll(/\[([^\]]+)\]\([^)]+\)/g, "$1"); // Strip markdown links but keep text
79+
const teaserContent = teaserText.slice(0, 240);
80+
const isTruncated = teaserLines.length > 3 || teaserText.length > 240;
81+
tree.teaser = isTruncated ? `${teaserContent}...` : teaserContent;
8482
}
8583

8684
Object.assign(

src/utilities/content-tree-enhancers.test.mjs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1+
import fs from "node:fs";
2+
import os from "node:os";
3+
import path from "node:path";
14
// eslint-disable-next-line import/no-extraneous-dependencies
25
import { describe, expect } from "@jest/globals";
3-
import { restructure } from "./content-tree-enhancers.mjs";
6+
import { enhance, restructure } from "./content-tree-enhancers.mjs";
47

58
describe("restructure", () => {
69
it("applies filter result back to children array", () => {
@@ -55,3 +58,41 @@ describe("restructure", () => {
5558
expect(root.children.map((item) => item.title)).toEqual(["API", "Guides"]);
5659
});
5760
});
61+
62+
describe("enhance", () => {
63+
const createBlogTree = (body) => {
64+
const root = fs.mkdtempSync(path.join(os.tmpdir(), "webpack-blog-"));
65+
const blogDir = path.join(root, "blog");
66+
fs.mkdirSync(blogDir);
67+
const filePath = path.join(blogDir, "example.mdx");
68+
fs.writeFileSync(filePath, `---\ntitle: Example\n---\n\n${body}`);
69+
70+
return {
71+
root,
72+
tree: {
73+
type: "file",
74+
path: filePath,
75+
extension: ".mdx",
76+
name: "example.mdx",
77+
},
78+
};
79+
};
80+
81+
it("does not append an ellipsis to an untruncated blog teaser", () => {
82+
const { root, tree } = createBlogTree("Short body.");
83+
84+
enhance(tree, { dir: root });
85+
86+
expect(tree.teaser).toBe("Short body.");
87+
});
88+
89+
it("appends an ellipsis to truncated blog teasers", () => {
90+
const { root, tree } = createBlogTree(
91+
["First line.", "Second line.", "Third line.", "Fourth line."].join("\n"),
92+
);
93+
94+
enhance(tree, { dir: root });
95+
96+
expect(tree.teaser).toBe("First line. Second line. Third line....");
97+
});
98+
});

src/utilities/content-utils.mjs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,11 @@ export const getPageTitle = (tree, path) => {
8787
// non page found
8888
if (!page) return "webpack";
8989

90-
if (page) {
91-
if (path.includes("/printable")) {
92-
return "Combined printable page | webpack";
93-
}
94-
if (path === "/") return page.title || "webpack";
95-
return `${page.title} | webpack`;
90+
if (path.includes("/printable")) {
91+
return "Combined printable page | webpack";
9692
}
93+
if (path === "/") return page.title || "webpack";
94+
return `${page.title} | webpack`;
9795
};
9896

9997
export const getPageDescription = (tree, path) => {

src/utilities/flatten-content-tree.mjs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,15 @@ export default (tree) => {
77
}
88

99
if ("children" in node) {
10-
node.children.map(crawl);
10+
for (const child of node.children) {
11+
crawl(child);
12+
}
1113
}
1214
};
1315

14-
tree.children.map(crawl);
16+
for (const child of tree.children) {
17+
crawl(child);
18+
}
1519

1620
return paths;
1721
};

src/utilities/process-readme.mjs

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,10 @@ function linkFixerFactory(sourceUrl) {
6666

6767
// Lowercase all fragment links, since markdown generators do the same
6868
if (href.includes("#")) {
69-
const [urlPath, urlFragment] = href.split("#");
70-
71-
href = `${urlPath}#${urlFragment.toLowerCase()}`;
69+
const fragmentIndex = href.indexOf("#");
70+
href = `${href.slice(0, fragmentIndex)}#${href
71+
.slice(fragmentIndex + 1)
72+
.toLowerCase()}`;
7273
}
7374

7475
if (oldHref !== href) {
@@ -79,16 +80,6 @@ function linkFixerFactory(sourceUrl) {
7980
};
8081
}
8182

82-
function getMatches(string, regex) {
83-
const matches = [];
84-
let match;
85-
86-
while ((match = regex.exec(string))) {
87-
matches.push(match);
88-
}
89-
return matches;
90-
}
91-
9283
export default function processREADME(body, options = {}) {
9384
let processingString = body
9485
// close <img> tags
@@ -141,10 +132,11 @@ export default function processREADME(body, options = {}) {
141132
);
142133

143134
// find the loaders links
144-
const loaderMatches = getMatches(
145-
processingString,
146-
/https?:\/\/github.com\/(webpack|webpack-contrib)\/([-A-za-z0-9]+-loader\/?)([)"])/g,
147-
);
135+
const loaderMatches = [
136+
...processingString.matchAll(
137+
/https?:\/\/github.com\/(webpack|webpack-contrib)\/([-A-za-z0-9]+-loader\/?)([)"])/g,
138+
),
139+
];
148140
// dont make relative links for excluded loaders
149141
for (const match of loaderMatches) {
150142
if (!excludedLoaders.includes(`${match[1]}/${match[2]}`)) {
@@ -155,10 +147,11 @@ export default function processREADME(body, options = {}) {
155147
}
156148
}
157149

158-
const pluginMatches = getMatches(
159-
processingString,
160-
/https?:\/\/github.com\/(webpack|webpack-contrib)\/([-A-za-z0-9]+-plugin\/?)([)"])/g,
161-
);
150+
const pluginMatches = [
151+
...processingString.matchAll(
152+
/https?:\/\/github.com\/(webpack|webpack-contrib)\/([-A-za-z0-9]+-plugin\/?)([)"])/g,
153+
),
154+
];
162155
// dont make relative links for excluded loaders
163156
for (const match of pluginMatches) {
164157
if (!excludedPlugins.includes(`${match[1]}/${match[2]}`)) {

src/utilities/process-readme.test.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,17 @@ describe("processReadme", () => {
4444
);
4545
});
4646

47+
it("preserves additional hash fragments when lowercasing anchors", () => {
48+
const options = {
49+
source:
50+
"https://raw.githubusercontent.com/webpack/postcss-loader/main/README.md",
51+
};
52+
const loaderMDData = "[Example](https://example.com/page#Section#Nested)";
53+
expect(processReadme(loaderMDData, options)).toBe(
54+
"[Example](https://example.com/page#section#nested)",
55+
);
56+
});
57+
4758
it("should preserve comments inside code blocks", () => {
4859
const options = {
4960
source:

0 commit comments

Comments
 (0)