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

fix formatting of block quotes with **/ closing characters #748

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
34 changes: 20 additions & 14 deletions src/lexer/NestedComment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
import { RegExpLike } from './TokenizerEngine.js';

const START = /\/\*/uy; // matches: /*
const MIDDLE = /([^/*]|\*[^/]|\/[^*])+/uy; // matches text NOT containing /* or */
const END = /\*\//uy; // matches: */

/**
* An object mimicking a regular expression,
Expand All @@ -15,29 +13,37 @@ export class NestedComment implements RegExpLike {
public exec(input: string): string[] | null {
let result = '';
let match: string | null;
let nestLevel = 0;

if ((match = this.matchSection(START, input))) {
result += match;
nestLevel++;
} else {
return null;
}

while (nestLevel > 0) {
if ((match = this.matchSection(START, input))) {
result += match;
nestLevel++;
} else if ((match = this.matchSection(END, input))) {
result += match;
let nestLevel = 1;
// start at the last index, break if we find a closing */ that matches
for (let i = this.lastIndex; i < input.length; i++) {
if (input[i] === '*' && input[i + 1] === '/') {
nestLevel--;
} else if ((match = this.matchSection(MIDDLE, input))) {
result += match;
result += '*/';
i++;
Copy link
Collaborator

Choose a reason for hiding this comment

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

Modifying the loop variable of a for-loop breaks my expectations. With a for-loop my expectation is that the i++ at the top means that at every step of the loop we always increment the index by one. When more complex behavior is needed, I think a while-loop would be a better choice.

if (nestLevel === 0) {
this.lastIndex = i;
return [result];
} else if (nestLevel < 0) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

When will it happen that nestLevel becomes negative?

I don't think this ever happens.

return null;
}
} else if (input[i] === '/' && input[i + 1] === '*') {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here we match the start of a comment with one kind of code. But above we use another kind of code this.matchSection(START, input) to do the same thing.

If the code does the same thing, it should better look the same as well. Or there should be some explanation as to why one needs to do the same thing differently.

Here, with the majority of the logic rewritten from regexes to plain char comparisons, that matchSection() method is really a leftover from old implementation and its existence will confuse the future reader.

nestLevel++;
result += '/*';
i++;
} else {
return null;
result += input[i];
}
}

if (nestLevel > 0) {
return null;
}
return [result];
}

Expand Down
6 changes: 6 additions & 0 deletions test/features/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,12 @@ export default function supportsComments(format: FormatFn, opts: CommentsConfig
`);
});

// Regression test for https://github.com/sql-formatter-org/sql-formatter/issues/747
it('handles block comments with /** and **/ patterns', () => {
const sql = `/** This is a block comment **/`;
expect(format(sql)).toBe(sql);
});

if (opts.hashComments) {
it('supports # line comment', () => {
const result = format('SELECT alpha # commment\nFROM beta');
Expand Down