Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions lint-rules/validate-jsdoc-codeblocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ function normalizeUnions(type) {
.map(t => [print(t), t])
.sort(([a], [b]) =>
// Numbers are sorted only wrt other numbers
isNumeric(a) && isNumeric(b) ? Number(a) - Number(b) : 0,
isNumeric(a) && isNumeric(b) ? Number(a) - Number(b) : a.localeCompare(b),
)
.map(t => t[1]);

Expand Down Expand Up @@ -276,6 +276,18 @@ function normalizeUnions(type) {
});
}

function flattenObjectExpression(maybeObjectExpression) {
return maybeObjectExpression
.replaceAll(/\r?\n\s*/g, ' ') // Collapse into single line
.replaceAll(/{\s+/g, '{') // Remove spaces after `{`
.replaceAll(/\s+}/g, '}') // Remove spaces before `}`
.replaceAll(/;(?=})/g, ''); // Remove semicolons before `}`
}

function toArrowCommentExpression(typeExpression) {
return TWOSLASH_COMMENT + ' ' + typeExpression.replaceAll('\n', '\n// ');
}

function validateTwoslashTypes(context, env, code, codeStartIndex) {
const sourceFile = env.languageService.getProgram().getSourceFile(FILENAME);
const lines = code.split('\n');
Expand Down Expand Up @@ -311,19 +323,25 @@ function validateTwoslashTypes(context, env, code, codeStartIndex) {
const quickInfo = env.languageService.getQuickInfoAtPosition(FILENAME, previousLineOffset + i);

if (quickInfo?.displayParts) {
let expectedType = normalizeUnions(extractTypeFromQuickInfo(quickInfo));

if (expectedType.length < 80) {
expectedType = expectedType
.replaceAll(/\r?\n\s*/g, ' ') // Collapse into single line
.replaceAll(/{\s+/g, '{') // Remove spaces after `{`
.replaceAll(/\s+}/g, '}') // Remove spaces before `}`
.replaceAll(/;(?=})/g, ''); // Remove semicolons before `}`
const expectedType = normalizeUnions(extractTypeFromQuickInfo(quickInfo));

// Save both `expectedComment` and `actualComment` to pass them to report.
let saveExpectedType = expectedType;
if (saveExpectedType.length < 80) {
saveExpectedType = flattenObjectExpression(saveExpectedType);
}

const expectedComment = TWOSLASH_COMMENT + ' ' + expectedType.replaceAll('\n', '\n// ');
const saveExpectedComment = toArrowCommentExpression(saveExpectedType);
const saveActualComment = actualComment;
// To compare with `actualComment`.
const expectedComment = toArrowCommentExpression(flattenObjectExpression(expectedType));

const isPassed = (/\s*\/\/=>\s\S+/g).test(actualComment);
const isNotPassed = (/\/\/}/g.test(actualComment)) || (actualComment.length < 80 && (/\[{\n/g.test(actualComment)));

actualComment = flattenObjectExpression(TWOSLASH_COMMENT + ' ' + normalizeUnions(actualComment.replaceAll(/(\/\/=>)|(\/\/)/g, '')));

if (actualComment !== expectedComment) {
if (!isPassed || isNotPassed || actualComment !== expectedComment) {
const actualCommentIndex = line.indexOf(TWOSLASH_COMMENT);

const actualCommentStartOffset = sourceFile.getPositionOfLineAndCharacter(index, actualCommentIndex);
Expand All @@ -339,15 +357,15 @@ function validateTwoslashTypes(context, env, code, codeStartIndex) {
},
messageId: 'typeMismatch',
data: {
expectedComment,
actualComment,
expectedComment: saveExpectedComment,
actualComment: saveActualComment,
},
fix(fixer) {
const indent = line.slice(0, actualCommentIndex);

return fixer.replaceTextRange(
[start, end],
expectedComment.replaceAll('\n', `\n${indent}`),
saveExpectedComment.replaceAll('\n', `\n${indent}`),
);
},
});
Expand Down
20 changes: 20 additions & 0 deletions lint-rules/validate-jsdoc-codeblocks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,26 @@ ruleTester.run('validate-jsdoc-codeblocks', validateJSDocCodeblocksRule, {
//=> '🦄' | '🐶' | '🐇'
`))),

// Union symbols are sorted.
exportTypeAndOption(jsdoc(fence(dedenter`
namespace AAA {
export type aaa = {a: string};
}
namespace BBB {
export type aaa = {a: string};
}
namespace CCC {
export type aaa = {a: string};
export type bbb = {a: string};
}

type Symbols_0 = AAA.aaa | CCC.bbb | CCC.aaa | BBB.aaa;
//=> AAA.aaa | BBB.aaa | CCC.aaa | CCC.bbb

type Symbols_1 = AAA.aaa | CCC.bbb | CCC.aaa | BBB.aaa;
//=> BBB.aaa | AAA.aaa | CCC.aaa | CCC.bbb
Comment on lines +810 to +825

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Umm...didn't get this. This example already works, and the result is already sorted.

namespace AAA {
	export type aaa = {a: string};
}
namespace BBB {
	export type aaa = {a: string};
}
namespace CCC {
	export type aaa = {a: string};
	export type bbb = {a: string};
}

type Symbols_0 = AAA.aaa | CCC.bbb | CCC.aaa | BBB.aaa;
//   ^? type Symbols_0 = AAA.aaa | BBB.aaa | CCC.aaa | CCC.bbb

type Symbols_1 = AAA.aaa | CCC.bbb | CCC.aaa | BBB.aaa;
//   ^? type Symbols_1 = AAA.aaa | BBB.aaa | CCC.aaa | CCC.bbb

Playground: https://tsplay.dev/W4oPAm

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry.
The goal was to verify that both actual and expected are sorted consistently even if they are Union types, allowing them to be compared via actualComment !== expectedComment regardless of their order.
However, I'm concerned that my test case might not be reproducing this scenario correctly.

This is what I want to fix:
https://github.com/sindresorhus/type-fest/actions/runs/21571234433/job/62150630102?pr=1338

If you pull this PR #1338 and run npm test, can you reproduce the same error?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

`))),

// === Different types of quick info ===
// Function
exportTypeAndOption(jsdoc(fence(dedenter`
Expand Down