Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chore: avoid mutating report descriptors in report-translator #9189

Merged
merged 1 commit into from Sep 1, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
108 changes: 54 additions & 54 deletions lib/report-translator.js
Expand Up @@ -41,7 +41,7 @@ function normalizeMultiArgReportCall() {

// If there is one argument, it is considered to be a new-style call already.
if (arguments.length === 1) {
return Object.assign({}, arguments[0]);
return arguments[0];
}

// If the second argument is a string, the arguments are interpreted as [node, message, data, fix].
Expand All @@ -67,7 +67,7 @@ function normalizeMultiArgReportCall() {
/**
* Asserts that either a loc or a node was provided, and the node is valid if it was provided.
* @param {MessageDescriptor} descriptor A descriptor to validate
* @returns {MessageDescriptor} The same descriptor
* @returns {void}
* @throws AssertionError if neither a node nor a loc was provided, or if the node is not an object
*/
function assertValidNodeInfo(descriptor) {
Expand All @@ -76,46 +76,39 @@ function assertValidNodeInfo(descriptor) {
} else {
assert(descriptor.loc, "Node must be provided when reporting error if location is not provided");
}

return descriptor;
}

/**
* Normalizes a MessageDescriptor to always have a `loc` with `start` and `end` properties
* @param {MessageDescriptor} descriptor A descriptor for the report from a rule. This descriptor may be mutated
* by this function.
* @returns {MessageDescriptor} The updated MessageDescriptor that infers the `start` and `end` properties from
* the `node` of the original descriptor, or infers the `start` from the `loc` of the original descriptor.
* @param {MessageDescriptor} descriptor A descriptor for the report from a rule.
* @returns {{start: Location, end: (Location|null)}} An updated location that infers the `start` and `end` properties
* from the `node` of the original descriptor, or infers the `start` from the `loc` of the original descriptor.
*/
function normalizeReportLoc(descriptor) {
if (descriptor.loc) {
if (descriptor.loc.start) {
return descriptor;
return descriptor.loc;
}
return Object.assign(descriptor, { loc: { start: descriptor.loc, end: null } });
return { start: descriptor.loc, end: null };
}

return Object.assign(descriptor, { loc: descriptor.node.loc });
return descriptor.node.loc;
}

/**
* Interpolates data placeholders in report messages
* @param {MessageDescriptor} descriptor The report message descriptor. This descriptor may be mutated
* by this function.
* @returns {MessageDescriptor} An new descriptor with a message containing the interpolated data
* @param {MessageDescriptor} descriptor The report message descriptor.
* @returns {string} The interpolated message for the descriptor
*/
function normalizeMessagePlaceholders(descriptor) {
if (!descriptor.data) {
return descriptor;
return descriptor.message;
}
return Object.assign(descriptor, {
message: descriptor.message.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (fullMatch, term) => {
if (term in descriptor.data) {
return descriptor.data[term];
}

return fullMatch;
})
return descriptor.message.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (fullMatch, term) => {
if (term in descriptor.data) {
return descriptor.data[term];
}

return fullMatch;
});
}

Expand All @@ -134,7 +127,7 @@ function compareFixesByRange(a, b) {
* Merges the given fixes array into one.
* @param {Fix[]} fixes The fixes to merge.
* @param {SourceCode} sourceCode The source code object to get the text between fixes.
* @returns {void}
* @returns {{text: string, range: [number, number]}} The merged fixes
*/
function mergeFixes(fixes, sourceCode) {
if (fixes.length === 0) {
Expand Down Expand Up @@ -169,31 +162,36 @@ function mergeFixes(fixes, sourceCode) {
/**
* Gets one fix object from the given descriptor.
* If the descriptor retrieves multiple fixes, this merges those to one.
* @param {MessageDescriptor} descriptor The report descriptor. This descriptor may be mutated
* by this function.
* @param {MessageDescriptor} descriptor The report descriptor.
* @param {SourceCode} sourceCode The source code object to get text between fixes.
* @returns {MessageDescriptor} The updated descriptor.
* @returns {({text: string, range: [number, number]}|null)} The fix for the descriptor
*/
function normalizeFixes(descriptor, sourceCode) {
if (typeof descriptor.fix !== "function") {
return Object.assign(descriptor, { fix: null });
return null;
}

// @type {null | Fix | Fix[] | IterableIterator<Fix>}
const fix = descriptor.fix(ruleFixer);

// Merge to one.
if (fix && Symbol.iterator in fix) {
return Object.assign(descriptor, { fix: mergeFixes(Array.from(fix), sourceCode) });
return mergeFixes(Array.from(fix), sourceCode);
}
return Object.assign(descriptor, { fix });
return fix;
}

/**
* Creates information about the report from a descriptor
* @param {MessageDescriptor} descriptor The message descriptor
* @param {string} ruleId The rule ID of the problem
* @param {(0|1|2)} severity The severity of the problem
* @param {{
* ruleId: string,
* severity: (0|1|2),
* node: (ASTNode|null),
* message: string,
* loc: {start: SourceLocation, end: (SourceLocation|null)},
* fix: ({text: string, range: [number, number]}|null),
* sourceLines: string[]
* }} options Information about the problem
* @returns {function(...args): {
* ruleId: string,
* severity: (0|1|2),
Expand All @@ -207,23 +205,24 @@ function normalizeFixes(descriptor, sourceCode) {
* fix: ({text: string, range: [number, number]}|null)
* }} Information about the report
*/
function createProblemFromDescriptor(descriptor, ruleId, severity) {
function createProblem(options) {
const problem = {
ruleId,
severity,
message: descriptor.message,
line: descriptor.loc.start.line,
column: descriptor.loc.start.column + 1,
nodeType: descriptor.node && descriptor.node.type || null
ruleId: options.ruleId,
severity: options.severity,
message: options.message,
line: options.loc.start.line,
column: options.loc.start.column + 1,
nodeType: options.node && options.node.type || null,
source: options.sourceLines[options.loc.start.line - 1] || ""
};

if (descriptor.loc.end) {
problem.endLine = descriptor.loc.end.line;
problem.endColumn = descriptor.loc.end.column + 1;
if (options.loc.end) {
problem.endLine = options.loc.end.line;
problem.endColumn = options.loc.end.column + 1;
}

if (descriptor.fix) {
problem.fix = descriptor.fix;
if (options.fix) {
problem.fix = options.fix;
}

return problem;
Expand Down Expand Up @@ -261,14 +260,15 @@ module.exports = function createReportTranslator(metadata) {
const descriptor = normalizeMultiArgReportCall.apply(null, arguments);

assertValidNodeInfo(descriptor);
normalizeReportLoc(descriptor);
normalizeMessagePlaceholders(descriptor);
normalizeFixes(descriptor, metadata.sourceCode);

const problem = createProblemFromDescriptor(descriptor, metadata.ruleId, metadata.severity);

problem.source = metadata.sourceCode.lines[problem.line - 1] || "";

return problem;
return createProblem({
ruleId: metadata.ruleId,
severity: metadata.severity,
node: descriptor.node,
message: normalizeMessagePlaceholders(descriptor),
loc: normalizeReportLoc(descriptor),
fix: normalizeFixes(descriptor, metadata.sourceCode),
sourceLines: metadata.sourceCode.lines
});
};
};