Skip to content

Commit

Permalink
Update line alignment rule to align any tag (gajus#685)
Browse files Browse the repository at this point in the history
fix: refactor alignment rule to use `comment-parser` (addressing alignment issues); fixes gajus#680

Co-authored-by: Renatho De Carli Rosa <renatho@automattic.com>
  • Loading branch information
renatho and renatho committed Jan 31, 2021
1 parent 4e768aa commit 4e8d735
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 206 deletions.
27 changes: 18 additions & 9 deletions README.md
Expand Up @@ -2225,8 +2225,8 @@ const fn = ( lorem, sit ) => {}

/**
* @namespace
* @property {object} defaults Description.
* @property {int} defaults.lorem Description multi words.
* @property {object} defaults Description.
* @property {int} defaults.lorem Description multi words.
*/
const config = {
defaults: {
Expand All @@ -2238,21 +2238,21 @@ const config = {
/**
* My object.
*
* @typedef {Object} MyObject
* @typedef {Object} MyObject
*
* @property {string} lorem Description.
* @property {int} sit Description multi words.
* @property {string} lorem Description.
* @property {int} sit Description multi words.
*/
// Options: ["always"]

/**
* My object.
*
* @typedef {Object} MyObject
* @typedef {Object} MyObject
*
* @property {{a: number, b: string, c}} lorem Description.
* @property {Object.<string, Class>} sit Description multi words.
* @property {Object.<string, Class>} amet Description} weird {multi} {{words}}.
* @property {{a: number, b: string, c}} lorem Description.
* @property {Object.<string, Class>} sit Description multi words.
* @property {Object.<string, Class>} amet Description} weird {multi} {{words}}.
* @property {Object.<string, Class>} dolor
*/
// Options: ["always"]
Expand All @@ -2261,6 +2261,15 @@ const config = {
const fn = ( lorem ) => {}
// Options: ["always"]

/**
* Creates OS based shortcuts for files, folders, and applications.
*
* @param {object} options Options object for each OS.
* @return {boolean} True = success, false = failed to create the icon
*/
function quux () {}
// Options: ["always"]

/**
* Not validating without option.
*
Expand Down
220 changes: 45 additions & 175 deletions src/rules/checkLineAlignment.js
@@ -1,175 +1,13 @@
import {
set, escapeRegExp,
} from 'lodash';
transforms,
} from 'comment-parser';
import iterateJsdoc from '../iterateJsdoc';

/**
* Aux method until we consider the dev envs support `String.prototype.matchAll` (Node 12+).
*
* @param {string} string String that will be checked.
* @param {RegExp} regexp Regular expression to run.
* @param {Function} callback Function to be called each iteration.
* @param {int} limit Limit of matches that we want to exec.
*
* @todo [engine:node@>=12]: Remove function and use `String.prototype.matchAll` instead.
*/
const matchAll = (string, regexp, callback, limit) => {
let result;
let index = 0;

while ((result = regexp.exec(string)) && index <= limit - 1) {
// eslint-disable-next-line promise/prefer-await-to-callbacks
callback(result, index++);
}
};

/**
* Get the full description from a line.
*
* @param {string} lineString The line string.
*
* @returns {string} The full description.
*/
const getFullDescription = (lineString) => {
return /\S+\s+(?:{{.*?}}|{.*?})\s+\S+\s+(.*)/.exec(lineString)[1];
};

/**
* Get the expected positions for each part.
*
* @param {int[]} partsMaxLength Max length of each part.
* @param {int} indentLevel JSDoc indent level.
*
* @returns {int[]} Expected position for each part.
*/
const getExpectedPositions = (partsMaxLength, indentLevel) => {
// eslint-disable-next-line unicorn/no-reduce
return partsMaxLength.reduce(
(acc, cur, index) => {
return [...acc, cur + acc[index] + 1];
},
[indentLevel],
);
};

/**
* Check is not aligned.
*
* @param {int[]} expectedPositions Expected position for each part.
* @param {Array[]} partsMatrix Parts matrix.
*
* @returns {boolean}
*/
const isNotAligned = (expectedPositions, partsMatrix) => {
return partsMatrix.some((line) => {
return line.some(
({position}, partIndex) => {
return position !== expectedPositions[partIndex];
},
);
});
};

/**
* Fix function creator for the report. It creates a function which fix
* the JSDoc with the correct alignment.
*
* @param {object} comment Comment node.
* @param {int[]} expectedPositions Array with the expected positions.
* @param {Array[]} partsMatrix Parts matrix.
* @param {RegExp} lineRegExp Line regular expression.
* @param {string} tagIndentation Tag indentation.
*
* @returns {Function} Function which fixes the JSDoc alignment.
*/
const createFixer = (comment, expectedPositions, partsMatrix, lineRegExp, tagIndentation) => {
return (fixer) => {
let lineIndex = 0;

// Replace every line with the correct spacings.
const fixed = comment.value.replace(lineRegExp, () => {
// eslint-disable-next-line unicorn/no-reduce
return partsMatrix[lineIndex++].reduce(
(acc, {string}, index) => {
const spacings = ' '.repeat(expectedPositions[index] - acc.length);

return acc + (index === 0 ? tagIndentation : spacings) + string;
},
'',
);
});

return fixer.replaceText(comment, '/*' + fixed + '*/');
};
};

/**
* Check comment per tag.
*
* @param {object} comment Comment node.
* @param {string} tag Tag string.
* @param {string} tagIndentation Tag indentation.
* @param {Function} report Report function.
*/
const checkAlignedPerTag = (comment, tag, tagIndentation, report) => {
const lineRegExp = new RegExp(`.*@${escapeRegExp(tag)}[\\s].*`, 'gm');
const lines = comment.value.match(lineRegExp);

if (!lines) {
return;
}

/**
* A matrix containing the current position and the string of each part for each line.
* 0 - Asterisk.
* 1 - Tag.
* 2 - Type.
* 3 - Variable name.
* 4 - Description (Optional).
*/
const partsMatrix = [];

/**
* The max length of each part, comparing all the lines.
*/
const partsMaxLength = [];

// Loop (lines x parts) to populate partsMatrix and partsMaxLength.
lines.forEach((lineString, lineIndex) => {
// All line parts until the first word of the description (if description exists).
matchAll(
lineString,
/{{.*?}}|{.*?}|\S+/g,
({0: match, index: position}, partIndex) => {
set(partsMatrix, [lineIndex, partIndex], {
position,
string: partIndex === 4 ? getFullDescription(lineString) : match,
});

const partLength = match.length;
const maxLength = partsMaxLength[partIndex];

partsMaxLength[partIndex] = maxLength > partLength ? maxLength : partLength;
},
5,
);
});

const expectedPositions = getExpectedPositions(partsMaxLength, tagIndentation.length);

if (isNotAligned(expectedPositions, partsMatrix)) {
report(
'Expected JSDoc block lines to be aligned.',
createFixer(
comment,
expectedPositions,
partsMatrix,
lineRegExp,
tagIndentation,
),
);
}
};
const {
flow: commentFlow,
align: commentAlign,
indent: commentIndent,
} = transforms;

const checkNotAlignedPerTag = (utils, tag) => {
/*
Expand Down Expand Up @@ -259,11 +97,42 @@ const checkNotAlignedPerTag = (utils, tag) => {
utils.reportJSDoc('Expected JSDoc block lines to not be aligned.', tag, fix, true);
};

const checkAlignment = ({
indent,
jsdoc,
jsdocNode,
report,
utils,
}) => {
const transform = commentFlow(commentAlign(), commentIndent(indent.length));
const transformedJsdoc = transform(jsdoc);

const comment = '/*' + jsdocNode.value + '*/';
const formatted = utils.stringify(transformedJsdoc)
.trimStart()

// Temporary until comment-parser fix: https://github.com/syavorsky/comment-parser/issues/119
.replace(/\s+\n/g, '\n')

// Temporary until comment-parser fix: https://github.com/syavorsky/comment-parser/issues/120
.replace(/(\n\s+)\*\s+@/g, '$1* @');

if (comment !== formatted) {
report(
'Expected JSDoc block lines to be aligned.',
(fixer) => {
return fixer.replaceText(jsdocNode, formatted);
},
);
}
};

export default iterateJsdoc(({
indent,
jsdoc,
jsdocNode,
report,
context,
indent,
utils,
}) => {
const {
Expand All @@ -276,11 +145,12 @@ export default iterateJsdoc(({
return;
}

// `indent` is whitespace from line 1 (`/**`), so slice and account for "/".
const tagIndentation = indent + ' ';

applicableTags.forEach((tag) => {
checkAlignedPerTag(jsdocNode, tag, tagIndentation, report);
checkAlignment({
indent,
jsdoc,
jsdocNode,
report,
utils,
});

return;
Expand Down

0 comments on commit 4e8d735

Please sign in to comment.