Remove helpers.deepFreeze and call Object.freeze only on things that need it for ~11% time reduction measured via profile-fixture.mjs on Apple Silicon M1.

This commit is contained in:
David Anson 2022-06-09 23:56:44 -07:00
parent b6471fba31
commit 936c876810
5 changed files with 110 additions and 97 deletions

View file

@ -1113,27 +1113,6 @@ function getNextChildToken(parentToken, childToken, nextType, nextNextType) {
return null; return null;
} }
module.exports.getNextChildToken = getNextChildToken; module.exports.getNextChildToken = getNextChildToken;
/**
* Calls Object.freeze() on an object and its children.
*
* @param {Object} obj Object to deep freeze.
* @returns {Object} Object passed to the function.
*/
function deepFreeze(obj) {
const pending = [obj];
let current = null;
while ((current = pending.shift())) {
Object.freeze(current);
for (const name of Object.getOwnPropertyNames(current)) {
const value = current[name];
if (value && (typeof value === "object")) {
pending.push(value);
}
}
}
return obj;
}
module.exports.deepFreeze = deepFreeze;
/** /**
* Expands a path with a tilde to an absolute path. * Expands a path with a tilde to an absolute path.
* *
@ -1442,13 +1421,37 @@ function removeFrontMatter(content, frontMatter) {
}; };
} }
/** /**
* Annotate tokens with line/lineNumber. * Freeze all freeze-able members of a token and its children.
*
* @param {MarkdownItToken} token A markdown-it token.
* @returns {void}
*/
function freezeToken(token) {
if (token.attrs) {
for (const attr of token.attrs) {
Object.freeze(attr);
}
Object.freeze(token.attrs);
}
if (token.children) {
for (const child of token.children) {
freezeToken(child);
}
Object.freeze(token.children);
}
if (token.map) {
Object.freeze(token.map);
}
Object.freeze(token);
}
/**
* Annotate tokens with line/lineNumber and freeze them.
* *
* @param {MarkdownItToken[]} tokens Array of markdown-it tokens. * @param {MarkdownItToken[]} tokens Array of markdown-it tokens.
* @param {string[]} lines Lines of Markdown content. * @param {string[]} lines Lines of Markdown content.
* @returns {void} * @returns {void}
*/ */
function annotateTokens(tokens, lines) { function annotateAndFreezeTokens(tokens, lines) {
let trMap = null; let trMap = null;
for (const token of tokens) { for (const token of tokens) {
// Provide missing maps for table content // Provide missing maps for table content
@ -1474,13 +1477,15 @@ function annotateTokens(tokens, lines) {
while (token.map[1] && !((lines[token.map[1] - 1] || "").trim())) { while (token.map[1] && !((lines[token.map[1] - 1] || "").trim())) {
token.map[1]--; token.map[1]--;
} }
// Annotate children with lineNumber }
// Annotate children with lineNumber
if (token.children) {
let lineNumber = token.lineNumber; let lineNumber = token.lineNumber;
const codeSpanExtraLines = []; const codeSpanExtraLines = [];
helpers.forEachInlineCodeSpan(token.content, function handleInlineCodeSpan(code) { helpers.forEachInlineCodeSpan(token.content, function handleInlineCodeSpan(code) {
codeSpanExtraLines.push(code.split(helpers.newLineRe).length - 1); codeSpanExtraLines.push(code.split(helpers.newLineRe).length - 1);
}); });
for (const child of (token.children || [])) { for (const child of token.children) {
child.lineNumber = lineNumber; child.lineNumber = lineNumber;
child.line = lines[lineNumber - 1]; child.line = lines[lineNumber - 1];
if ((child.type === "softbreak") || (child.type === "hardbreak")) { if ((child.type === "softbreak") || (child.type === "hardbreak")) {
@ -1491,7 +1496,9 @@ function annotateTokens(tokens, lines) {
} }
} }
} }
freezeToken(token);
} }
Object.freeze(tokens);
} }
/** /**
* Map rule names/tags to canonical rule name. * Map rule names/tags to canonical rule name.
@ -1741,14 +1748,14 @@ function lintContent(ruleList, name, content, md, config, configParsers, frontMa
// Parse content into tokens and lines // Parse content into tokens and lines
const tokens = md.parse(content, {}); const tokens = md.parse(content, {});
const lines = content.split(helpers.newLineRe); const lines = content.split(helpers.newLineRe);
annotateTokens(tokens, lines); annotateAndFreezeTokens(tokens, lines);
// Create parameters for rules // Create (frozen) parameters for rules
const paramsBase = helpers.deepFreeze({ const paramsBase = {
name, name,
tokens, tokens,
lines, "lines": Object.freeze(lines),
frontMatterLines "frontMatterLines": Object.freeze(frontMatterLines)
}); };
const lineMetadata = helpers.getLineMetadata(paramsBase); const lineMetadata = helpers.getLineMetadata(paramsBase);
const codeBlockAndSpanRanges = helpers.codeBlockAndSpanRanges(paramsBase, lineMetadata); const codeBlockAndSpanRanges = helpers.codeBlockAndSpanRanges(paramsBase, lineMetadata);
const flattenedLists = helpers.flattenLists(paramsBase.tokens); const flattenedLists = helpers.flattenLists(paramsBase.tokens);

View file

@ -1153,28 +1153,6 @@ function getNextChildToken(parentToken, childToken, nextType, nextNextType) {
} }
module.exports.getNextChildToken = getNextChildToken; module.exports.getNextChildToken = getNextChildToken;
/**
* Calls Object.freeze() on an object and its children.
*
* @param {Object} obj Object to deep freeze.
* @returns {Object} Object passed to the function.
*/
function deepFreeze(obj) {
const pending = [ obj ];
let current = null;
while ((current = pending.shift())) {
Object.freeze(current);
for (const name of Object.getOwnPropertyNames(current)) {
const value = current[name];
if (value && (typeof value === "object")) {
pending.push(value);
}
}
}
return obj;
}
module.exports.deepFreeze = deepFreeze;
/** /**
* Expands a path with a tilde to an absolute path. * Expands a path with a tilde to an absolute path.
* *

View file

@ -191,13 +191,38 @@ function removeFrontMatter(content, frontMatter) {
} }
/** /**
* Annotate tokens with line/lineNumber. * Freeze all freeze-able members of a token and its children.
*
* @param {MarkdownItToken} token A markdown-it token.
* @returns {void}
*/
function freezeToken(token) {
if (token.attrs) {
for (const attr of token.attrs) {
Object.freeze(attr);
}
Object.freeze(token.attrs);
}
if (token.children) {
for (const child of token.children) {
freezeToken(child);
}
Object.freeze(token.children);
}
if (token.map) {
Object.freeze(token.map);
}
Object.freeze(token);
}
/**
* Annotate tokens with line/lineNumber and freeze them.
* *
* @param {MarkdownItToken[]} tokens Array of markdown-it tokens. * @param {MarkdownItToken[]} tokens Array of markdown-it tokens.
* @param {string[]} lines Lines of Markdown content. * @param {string[]} lines Lines of Markdown content.
* @returns {void} * @returns {void}
*/ */
function annotateTokens(tokens, lines) { function annotateAndFreezeTokens(tokens, lines) {
let trMap = null; let trMap = null;
for (const token of tokens) { for (const token of tokens) {
// Provide missing maps for table content // Provide missing maps for table content
@ -222,7 +247,9 @@ function annotateTokens(tokens, lines) {
while (token.map[1] && !((lines[token.map[1] - 1] || "").trim())) { while (token.map[1] && !((lines[token.map[1] - 1] || "").trim())) {
token.map[1]--; token.map[1]--;
} }
// Annotate children with lineNumber }
// Annotate children with lineNumber
if (token.children) {
let lineNumber = token.lineNumber; let lineNumber = token.lineNumber;
const codeSpanExtraLines = []; const codeSpanExtraLines = [];
helpers.forEachInlineCodeSpan( helpers.forEachInlineCodeSpan(
@ -231,7 +258,7 @@ function annotateTokens(tokens, lines) {
codeSpanExtraLines.push(code.split(helpers.newLineRe).length - 1); codeSpanExtraLines.push(code.split(helpers.newLineRe).length - 1);
} }
); );
for (const child of (token.children || [])) { for (const child of token.children) {
child.lineNumber = lineNumber; child.lineNumber = lineNumber;
child.line = lines[lineNumber - 1]; child.line = lines[lineNumber - 1];
if ((child.type === "softbreak") || (child.type === "hardbreak")) { if ((child.type === "softbreak") || (child.type === "hardbreak")) {
@ -241,7 +268,9 @@ function annotateTokens(tokens, lines) {
} }
} }
} }
freezeToken(token);
} }
Object.freeze(tokens);
} }
/** /**
@ -532,14 +561,14 @@ function lintContent(
// Parse content into tokens and lines // Parse content into tokens and lines
const tokens = md.parse(content, {}); const tokens = md.parse(content, {});
const lines = content.split(helpers.newLineRe); const lines = content.split(helpers.newLineRe);
annotateTokens(tokens, lines); annotateAndFreezeTokens(tokens, lines);
// Create parameters for rules // Create (frozen) parameters for rules
const paramsBase = helpers.deepFreeze({ const paramsBase = {
name, name,
tokens, tokens,
lines, "lines": Object.freeze(lines),
frontMatterLines "frontMatterLines": Object.freeze(frontMatterLines)
}); };
const lineMetadata = const lineMetadata =
helpers.getLineMetadata(paramsBase); helpers.getLineMetadata(paramsBase);
const codeBlockAndSpanRanges = const codeBlockAndSpanRanges =

View file

@ -1200,6 +1200,39 @@ test("customRulesAsyncThrowsInSyncContext", (t) => {
); );
}); });
test("customRulesParamsAreFrozen", (t) => {
const options = {
"customRules": [
{
"names": [ "name" ],
"description": "description",
"tags": [ "tag" ],
"function":
(params) => {
const pending = [ params ];
let current = null;
while ((current = pending.shift())) {
t.true(Object.isFrozen(current) || (current === params));
for (const name of Object.getOwnPropertyNames(current)) {
const value = current[name];
if (value && (typeof value === "object")) {
pending.push(value);
}
}
}
}
}
],
"files": [
"CONTRIBUTING.md",
"README.md",
"doc/CustomRules.md",
"doc/Rules.md"
]
};
return markdownlint.promises.markdownlint(options).then(() => null);
});
test("customRulesParamsAreStable", (t) => { test("customRulesParamsAreStable", (t) => {
t.plan(4); t.plan(4);
const config1 = { "value1": 10 }; const config1 = { "value1": 10 };

View file

@ -953,40 +953,6 @@ test("applyFixes", (t) => {
} }
}); });
test("deepFreeze", (t) => {
t.plan(6);
const obj = {
"prop": true,
"func": () => true,
"sub": {
"prop": [ 1 ],
"sub": {
"prop": "one"
}
}
};
t.is(helpers.deepFreeze(obj), obj, "Did not return object.");
for (const scenario of [
() => {
obj.prop = false;
},
() => {
obj.func = () => false;
},
() => {
obj.sub.prop = [];
},
() => {
obj.sub.prop[0] = 0;
},
() => {
obj.sub.sub.prop = "zero";
}
]) {
t.throws(scenario, null, "Assigned to frozen object.");
}
});
test("forEachLink", (t) => { test("forEachLink", (t) => {
t.plan(291); t.plan(291);
const testCases = [ const testCases = [