Skip to content

Commit 925190f

Browse files
mysticateamichalsnik
authored andcommitted
Fix: improve comment indentation (fixes #514) (#676)
* Fix: improve comment indentation (fixes #514) * 📝 fix JSDoc comment
1 parent c6bbd95 commit 925190f

File tree

2 files changed

+79
-37
lines changed

2 files changed

+79
-37
lines changed

Diff for: lib/utils/indent-common.js

+71-37
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const KNOWN_NODES = new Set(['ArrayExpression', 'ArrayPattern', 'ArrowFunctionEx
1818
const LT_CHAR = /[\r\n\u2028\u2029]/
1919
const LINES = /[^\r\n\u2028\u2029]+(?:$|\r\n|[\r\n\u2028\u2029])/g
2020
const BLOCK_COMMENT_PREFIX = /^\s*\*/
21+
const ITERATION_OPTS = Object.freeze({ includeComments: true, filter: isNotWhitespace })
2122

2223
/**
2324
* Normalize options.
@@ -194,6 +195,15 @@ function isNotComment (token) {
194195
return token != null && token.type !== 'Block' && token.type !== 'Line' && token.type !== 'Shebang' && !token.type.endsWith('Comment')
195196
}
196197

198+
/**
199+
* Check whether the given node is not an empty text node.
200+
* @param {Node} node The node to check.
201+
* @returns {boolean} `false` if the token is empty text node.
202+
*/
203+
function isNotEmptyTextNode (node) {
204+
return !(node.type === 'VText' && node.value.trim() === '')
205+
}
206+
197207
/**
198208
* Get the last element.
199209
* @param {Array} xs The array to get the last element.
@@ -295,7 +305,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
295305
* @param {Token} token The token to set.
296306
* @returns {void}
297307
*/
298-
function setBaseline (token, hardTabAdditional) {
308+
function setBaseline (token) {
299309
const offsetInfo = offsets.get(token)
300310
if (offsetInfo != null) {
301311
offsetInfo.baseline = true
@@ -353,17 +363,21 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
353363
* The first node is offsetted from the given left token.
354364
* Rest nodes are adjusted to the first node.
355365
* @param {Node[]} nodeList The node to process.
356-
* @param {Node|null} leftToken The left parenthesis token.
357-
* @param {Node|null} rightToken The right parenthesis token.
366+
* @param {Node|Token|null} left The left parenthesis token.
367+
* @param {Node|Token|null} right The right parenthesis token.
358368
* @param {number} offset The offset to set.
359-
* @param {Node} [alignVertically=true] The flag to align vertically. If `false`, this doesn't align vertically even if the first node is not at beginning of line.
369+
* @param {boolean} [alignVertically=true] The flag to align vertically. If `false`, this doesn't align vertically even if the first node is not at beginning of line.
360370
* @returns {void}
361371
*/
362-
function processNodeList (nodeList, leftToken, rightToken, offset, alignVertically) {
372+
function processNodeList (nodeList, left, right, offset, alignVertically) {
363373
let t
374+
const leftToken = (left && tokenStore.getFirstToken(left)) || left
375+
const rightToken = (right && tokenStore.getFirstToken(right)) || right
364376

365377
if (nodeList.length >= 1) {
366-
let lastToken = leftToken
378+
let baseToken = null
379+
let lastToken = left
380+
const alignTokensBeforeBaseToken = []
367381
const alignTokens = []
368382

369383
for (let i = 0; i < nodeList.length; ++i) {
@@ -374,30 +388,50 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
374388
}
375389
const elementTokens = getFirstAndLastTokens(node, lastToken != null ? lastToken.range[1] : 0)
376390

377-
// Collect related tokens.
378-
// Commas between this and the previous, and the first token of this node.
391+
// Collect comma/comment tokens between the last token of the previous node and the first token of this node.
379392
if (lastToken != null) {
380393
t = lastToken
381-
while ((t = tokenStore.getTokenAfter(t)) != null && t.range[1] <= elementTokens.firstToken.range[0]) {
382-
alignTokens.push(t)
394+
while (
395+
(t = tokenStore.getTokenAfter(t, ITERATION_OPTS)) != null &&
396+
t.range[1] <= elementTokens.firstToken.range[0]
397+
) {
398+
if (baseToken == null) {
399+
alignTokensBeforeBaseToken.push(t)
400+
} else {
401+
alignTokens.push(t)
402+
}
383403
}
384404
}
385-
alignTokens.push(elementTokens.firstToken)
386405

387-
// Save the last token to find tokens between the next token.
406+
if (baseToken == null) {
407+
baseToken = elementTokens.firstToken
408+
} else {
409+
alignTokens.push(elementTokens.firstToken)
410+
}
411+
412+
// Save the last token to find tokens between this node and the next node.
388413
lastToken = elementTokens.lastToken
389414
}
390415

391-
// Check trailing commas.
416+
// Check trailing commas and comments.
392417
if (rightToken != null && lastToken != null) {
393418
t = lastToken
394-
while ((t = tokenStore.getTokenAfter(t)) != null && t.range[1] <= rightToken.range[0]) {
395-
alignTokens.push(t)
419+
while (
420+
(t = tokenStore.getTokenAfter(t, ITERATION_OPTS)) != null &&
421+
t.range[1] <= rightToken.range[0]
422+
) {
423+
if (baseToken == null) {
424+
alignTokensBeforeBaseToken.push(t)
425+
} else {
426+
alignTokens.push(t)
427+
}
396428
}
397429
}
398430

399431
// Set offsets.
400-
const baseToken = alignTokens.shift()
432+
if (leftToken != null) {
433+
setOffset(alignTokensBeforeBaseToken, offset, leftToken)
434+
}
401435
if (baseToken != null) {
402436
// Set offset to the first token.
403437
if (leftToken != null) {
@@ -409,7 +443,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
409443
setBaseline(baseToken)
410444
}
411445

412-
if (alignVertically === false) {
446+
if (alignVertically === false && leftToken != null) {
413447
// Align tokens relatively to the left token.
414448
setOffset(alignTokens, offset, leftToken)
415449
} else {
@@ -657,10 +691,10 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
657691
* Validate the given token with the pre-calculated expected indentation.
658692
* @param {Token} token The token to validate.
659693
* @param {number} expectedIndent The expected indentation.
660-
* @param {number|undefined} optionalExpectedIndent The optional expected indentation.
694+
* @param {number[]|undefined} optionalExpectedIndents The optional expected indentation.
661695
* @returns {void}
662696
*/
663-
function validateCore (token, expectedIndent, optionalExpectedIndent) {
697+
function validateCore (token, expectedIndent, optionalExpectedIndents) {
664698
const line = token.loc.start.line
665699
const indentText = getIndentText(token)
666700

@@ -692,7 +726,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
692726
}
693727
}
694728

695-
if (actualIndent !== expectedIndent && (optionalExpectedIndent === undefined || actualIndent !== optionalExpectedIndent)) {
729+
if (actualIndent !== expectedIndent && (optionalExpectedIndents == null || !optionalExpectedIndents.includes(actualIndent))) {
696730
context.report({
697731
loc: {
698732
start: { line, column: 0 },
@@ -716,7 +750,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
716750
* @param {Token|null} nextToken The next token of comments.
717751
* @param {number|undefined} nextExpectedIndent The expected indent of the next token.
718752
* @param {number|undefined} lastExpectedIndent The expected indent of the last token.
719-
* @returns {{primary:number|undefined,secondary:number|undefined}}
753+
* @returns {number[]}
720754
*/
721755
function getCommentExpectedIndents (nextToken, nextExpectedIndent, lastExpectedIndent) {
722756
if (typeof lastExpectedIndent === 'number' && isClosingToken(nextToken)) {
@@ -725,26 +759,23 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
725759
// <div>
726760
// <!-- comment -->
727761
// </div>
728-
return {
729-
primary: nextExpectedIndent + options.indentSize,
730-
secondary: undefined
731-
}
762+
return [nextExpectedIndent + options.indentSize, nextExpectedIndent]
732763
}
733764

734765
// For last comment. E.g.,
735766
// <div>
736767
// <div></div>
737768
// <!-- comment -->
738769
// </div>
739-
return { primary: lastExpectedIndent, secondary: nextExpectedIndent }
770+
return [lastExpectedIndent, nextExpectedIndent]
740771
}
741772

742773
// Adjust to next normally. E.g.,
743774
// <div>
744775
// <!-- comment -->
745776
// <div></div>
746777
// </div>
747-
return { primary: nextExpectedIndent, secondary: undefined }
778+
return [nextExpectedIndent]
748779
}
749780

750781
/**
@@ -815,11 +846,17 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
815846
// It allows the same indent level with the previous line.
816847
const lastOffsetInfo = offsets.get(lastToken)
817848
const lastExpectedIndent = lastOffsetInfo && lastOffsetInfo.expectedIndent
818-
const commentExpectedIndents = getCommentExpectedIndents(firstToken, expectedIndent, lastExpectedIndent)
849+
const commentOptionalExpectedIndents = getCommentExpectedIndents(firstToken, expectedIndent, lastExpectedIndent)
819850

820851
// Validate.
821852
for (const comment of comments) {
822-
validateCore(comment, commentExpectedIndents.primary, commentExpectedIndents.secondary)
853+
const commentExpectedIndents = getExpectedIndents([comment])
854+
const commentExpectedIndent =
855+
commentExpectedIndents
856+
? commentExpectedIndents.expectedIndent
857+
: commentOptionalExpectedIndents[0]
858+
859+
validateCore(comment, commentExpectedIndent, commentOptionalExpectedIndents)
823860
}
824861
validateCore(firstToken, expectedIndent)
825862
}
@@ -844,16 +881,14 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
844881
},
845882

846883
VElement (node) {
847-
const startTagToken = tokenStore.getFirstToken(node)
848-
const endTagToken = node.endTag && tokenStore.getFirstToken(node.endTag)
849-
850884
if (node.name !== 'pre') {
851-
const childTokens = node.children.map(n => tokenStore.getFirstToken(n))
852-
setOffset(childTokens, 1, startTagToken)
885+
processNodeList(node.children.filter(isNotEmptyTextNode), node.startTag, node.endTag, 1)
853886
} else {
887+
const startTagToken = tokenStore.getFirstToken(node)
888+
const endTagToken = node.endTag && tokenStore.getFirstToken(node.endTag)
889+
setOffset(endTagToken, 0, startTagToken)
854890
setPreformattedTokens(node)
855891
}
856-
setOffset(endTagToken, 0, startTagToken)
857892
},
858893

859894
VEndTag (node) {
@@ -1116,7 +1151,6 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
11161151

11171152
setOffset(leftParenToken, 1, forToken)
11181153
processNodeList([node.init, node.test, node.update], leftParenToken, rightParenToken, 1)
1119-
setOffset(rightParenToken, 0, leftParenToken)
11201154
processMaybeBlock(node.body, forToken)
11211155
},
11221156

@@ -1544,7 +1578,7 @@ module.exports.defineVisitor = function create (context, tokenStore, defaultOpti
15441578
let lastValidatedToken = null
15451579

15461580
// Validate indentation of tokens.
1547-
for (const token of tokenStore.getTokens(node, { includeComments: true, filter: isNotWhitespace })) {
1581+
for (const token of tokenStore.getTokens(node, ITERATION_OPTS)) {
15481582
if (tokensOnSameLine.length === 0 || tokensOnSameLine[0].loc.start.line === token.loc.start.line) {
15491583
// This is on the same line (or the first token).
15501584
tokensOnSameLine.push(token)

Diff for: tests/fixtures/html-indent/issue514.vue

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<!--{}-->
2+
<template>
3+
<div>
4+
<input type="text"
5+
value="foo">
6+
<!-- comment -->
7+
</div>
8+
</template>

0 commit comments

Comments
 (0)