diff --git a/__tests__/formatter.test.ts b/__tests__/formatter.test.ts index 461f75ab..3b03c348 100644 --- a/__tests__/formatter.test.ts +++ b/__tests__/formatter.test.ts @@ -4824,4 +4824,171 @@ describe('formatter', () => { endOfLine: 'CRLF', }); }); + + test('fix shufo/prettier-plugin-blade#166', async () => { + const content = [ + `@php`, + ` /**`, + ` * @var \App\Models\User $user`, + ` * @var \App\Models\Post $post`, + ` */`, + `@endphp`, + `{{ $post->title }} by {{ $user->name }}`, + ].join('\n'); + + const expected = [ + `@php`, + ` /**`, + ` * @var \App\Models\User $user`, + ` * @var \App\Models\Post $post`, + ` */`, + `@endphp`, + `{{ $post->title }} by {{ $user->name }}`, + ``, + ].join('\n'); + + await util.doubleFormatCheck(content, expected); + }); + + test('raw php comment block', async () => { + const content = [ + `
`, + ` `, + ` `, + ` `, + ` `, + `
`, + ].join('\n'); + + const expected = [ + `
`, + ` `, + ` `, + ` `, + ` `, + `
`, + ``, + ].join('\n'); + + await util.doubleFormatCheck(content, expected); + }); + + test('php directive comment block', async () => { + const content = [ + `
`, + ` @php`, + ` /**`, + ` * @var \App\Models\User $user`, + ` * @var \App\Models\Post $post`, + ` */`, + ` @endphp`, + ` @php`, + ` /**`, + ` * @var \App\Models\User $user`, + ` * @var \App\Models\Post $post`, + ` */`, + ` /**`, + ` * @var \App\Models\User $user`, + ` * @var \App\Models\Post $post`, + ` */ echo 1;`, + ` @endphp`, + ` @php`, + ` /**`, + ` * @var \App\Models\User $user`, + ` * @var \App\Models\Post $post`, + ` */`, + ` @endphp`, + ` @php`, + ` /**`, + ` \App\Models\User $user`, + ` \App\Models\Post $post`, + ` */`, + ` @endphp`, + `
`, + ].join('\n'); + + const expected = [ + `
`, + ` @php`, + ` /**`, + ` * @var \App\Models\User $user`, + ` * @var \App\Models\Post $post`, + ` */`, + ` @endphp`, + ` @php`, + ` /**`, + ` * @var \App\Models\User $user`, + ` * @var \App\Models\Post $post`, + ` */`, + ` /**`, + ` * @var \App\Models\User $user`, + ` * @var \App\Models\Post $post`, + ` */ echo 1;`, + ` @endphp`, + ` @php`, + ` /**`, + ` * @var \App\Models\User $user`, + ` * @var \App\Models\Post $post`, + ` */`, + ` @endphp`, + ` @php`, + ` /**`, + ` \App\Models\User $user`, + ` \App\Models\Post $post`, + ` */`, + ` @endphp`, + `
`, + ``, + ].join('\n'); + + await util.doubleFormatCheck(content, expected); + }); }); diff --git a/src/comment.ts b/src/comment.ts new file mode 100644 index 00000000..b82cb7e5 --- /dev/null +++ b/src/comment.ts @@ -0,0 +1,54 @@ +/** + * Formats php comment + * + * @param comment + * @returns string + */ +export function formatPhpComment(comment: string): string { + const lines = splitByLines(comment); + + if (!isMultiline(lines)) { + return comment; + } + + let nonCommentLineExists = false; + + const mapped = lines.map((line: string, row: number) => { + if (row === 0) { + return line; + } + + if (nonCommentLineExists) { + return line; + } + + if (!isCommentedLine(line)) { + nonCommentLineExists = true; + return line; + } + + const trimmedLine = line.trim(); + + return addPrefixToLine(trimmedLine); + }); + + return mapped.join('\n'); +} + +function splitByLines(content: string): Array { + return content.split('\n'); +} + +function isCommentedLine(line: string): boolean { + return line.trim().startsWith('*'); +} + +function isMultiline(lines: Array): boolean { + return lines.length > 1; +} + +function addPrefixToLine(line: string): string { + const prefix = ' '; + + return `${prefix}${line}`; +} diff --git a/src/formatter.ts b/src/formatter.ts index d7f88674..887bdbf7 100644 --- a/src/formatter.ts +++ b/src/formatter.ts @@ -32,6 +32,7 @@ import { } from './indent'; import { nestedParenthesisRegex } from './regex'; import { SortHtmlAttributes } from './runtimeConfig'; +import { formatPhpComment } from './comment'; export default class Formatter { argumentCheck: any; @@ -717,7 +718,9 @@ export default class Formatter { } preservePhpComment(content: string) { - return _.replace(content, /\/\*+([^\*]*?)\*\//gi, (match: string) => this.storePhpComment(match)); + return _.replace(content, /\/\*(?:[^*]|[\r\n]|(?:\*+(?:[^*\/]|[\r\n])))*\*+\//gi, (match: string) => + this.storePhpComment(match), + ); } async preserveBladeBrace(content: any) { @@ -1212,8 +1215,10 @@ export default class Formatter { return `@php${q2}@endphp`; } - const preserved = this.preserveStringLiteralInPhp(q2); - const indented = this.indentRawBlock(indent, preserved); + let preserved = this.preserveStringLiteralInPhp(q2); + preserved = this.preservePhpComment(preserved); + let indented = this.indentRawBlock(indent, preserved); + indented = this.restorePhpComment(indented); const restored = this.restoreStringLiteralInPhp(indented); return `@php${restored}@endphp`; @@ -1268,7 +1273,8 @@ export default class Formatter { return prefix + line; }) - .join('\n'); + .join('\n') + .value(); } indentBladeDirectiveBlock(indent: detectIndent.Indent, content: any) { @@ -1397,6 +1403,43 @@ export default class Formatter { .join('\n'); } + indentPhpComment(indent: detectIndent.Indent, content: string) { + if (_.isEmpty(indent.indent)) { + return content; + } + + if (this.isInline(content)) { + return `${content}`; + } + + const leftIndentAmount = indent.amount; + const indentLevel = leftIndentAmount / this.indentSize; + const prefixSpaces = this.indentCharacter.repeat(indentLevel < 0 ? 0 : indentLevel * this.indentSize); + + const lines = content.split('\n'); + let withoutCommentLine = false; + + return _.chain(lines) + .map((line: string, index: number) => { + if (index === 0) { + return line.trim(); + } + + if (!line.trim().startsWith('*')) { + withoutCommentLine = true; + return line; + } + + if (line.trim().endsWith('*/') && withoutCommentLine) { + return line; + } + + return prefixSpaces + line; + }) + .join('\n') + .value(); + } + restoreBladeDirectivesInScripts(content: any) { const regex = new RegExp(`${this.getBladeDirectivePlaceholder('(\\d+)')}`, 'gm'); @@ -1522,8 +1565,15 @@ export default class Formatter { restorePhpComment(content: string) { return _.replace( content, - new RegExp(`${this.getPhpCommentPlaceholder('(\\d+)')}`, 'gms'), - (_match: string, p1: number) => this.phpComments[p1], + new RegExp(`${this.getPhpCommentPlaceholder('(\\d+)')};{0,1}`, 'gms'), + (_match: string, p1: number) => { + const placeholder = this.getPhpCommentPlaceholder(p1.toString()); + const matchedLine = content.match(new RegExp(`^(.*?)${placeholder}`, 'gmi')) ?? ['']; + const indent = detectIndent(matchedLine[0]); + const formatted = formatPhpComment(this.phpComments[p1]); + + return this.indentPhpComment(indent, formatted); + }, ); }