Repository: nestjs/swagger (ts)

Issue:
introspectComments does not play nice with tab indentation
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Current behavior

If tab-based indentation is used in the codebase (e.g. prettier config `"useTabs": true`), `introspectComments` does not accurately parse comments. For example, descriptions have leading `*` characters that are interpreted as bullets, and tsdoc tags are not stripped away. 

### Minimum reproduction code

I will attach a PR with a test case shortly, let me know if you need anything else.

### Steps to reproduce

see current behavior

### Expected behavior

introspectComments should work identical for space and tab-based indentation

### Package version

5.1.5

### NestJS version

8.2.3

### Node.js version

14.15.1

### In which operating systems have you tested?

- [X] macOS
- [ ] Windows
- [ ] Linux

### Other

_No response_

Pull request:
fix: make introspectComments work with tab indentation
introspectComments did not properly parse comments when source files used tab indentation

fixes #1705

## PR Checklist
Please check if your PR fulfills the following requirements:

- [X] The commit message follows our guidelines: https://github.com/nestjs/nest/blob/master/CONTRIBUTING.md
- [X] Tests for the changes have been added (for bug fixes / features)
- [ ] Docs have been added / updated (for bug fixes / features)


## PR Type
What kind of change does this PR introduce?

<!-- Please check the one that applies to this PR using "x". -->
- [X] Bugfix
- [ ] Feature
- [ ] Code style update (formatting, local variables)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] CI related changes
- [ ] Other... Please describe:

## What is the current behavior?
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->

Issue Number: #1705 

## What is the new behavior?


## Does this PR introduce a breaking change?
- [ ] Yes
- [X] No

<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->


## Other information


Patch:
diff --git a/lib/plugin/utils/ast-utils.ts b/lib/plugin/utils/ast-utils.ts
index 815e4aee..2d04674e 100644
--- a/lib/plugin/utils/ast-utils.ts
+++ b/lib/plugin/utils/ast-utils.ts
@@ -118,7 +118,7 @@ export function getMainCommentAndExamplesOfNode(
   const sourceText = sourceFile.getFullText();
   // in case we decide to include "// comments"
   const replaceRegex =
-    /^ *\** *@.*$|^ *\/\*+ *|^ *\/\/+.*|^ *\/+ *|^ *\*+ *| +$| *\**\/ *$/gim;
+    /^\s*\** *@.*$|^\s*\/\*+ *|^\s*\/\/+.*|^\s*\/+ *|^\s*\*+ *| +$| *\**\/ *$/gim;
   //const replaceRegex = /^ *\** *@.*$|^ *\/\*+ *|^ *\/+ *|^ *\*+ *| +$| *\**\/ *$/gim;
 
   const commentResult = [];


Test patch:
diff --git a/test/plugin/controller-class-visitor.spec.ts b/test/plugin/controller-class-visitor.spec.ts
index 0e0bc9bf..6772b7c6 100644
--- a/test/plugin/controller-class-visitor.spec.ts
+++ b/test/plugin/controller-class-visitor.spec.ts
@@ -4,9 +4,13 @@ import {
   appControllerText,
   appControllerTextTranspiled
 } from './fixtures/app.controller';
+import {
+  appControllerWithTabsText,
+  appControllerWithTabsTextTranspiled
+} from './fixtures/app.controller-tabs';
 
 describe('Controller methods', () => {
-  it('should add response based on the return value', () => {
+  it('should add response based on the return value (spaces)', () => {
     const options: ts.CompilerOptions = {
       module: ts.ModuleKind.CommonJS,
       target: ts.ScriptTarget.ESNext,
@@ -30,4 +34,29 @@ describe('Controller methods', () => {
     });
     expect(result.outputText).toEqual(appControllerTextTranspiled);
   });
+
+  it('should add response based on the return value (tabs)', () => {
+    const options: ts.CompilerOptions = {
+      module: ts.ModuleKind.CommonJS,
+      target: ts.ScriptTarget.ESNext,
+      newLine: ts.NewLineKind.LineFeed,
+      noEmitHelpers: true
+    };
+    const filename = 'app.controller.ts';
+    const fakeProgram = ts.createProgram([filename], options);
+
+    const result = ts.transpileModule(appControllerWithTabsText, {
+      compilerOptions: options,
+      fileName: filename,
+      transformers: {
+        before: [
+          before(
+            { controllerKeyOfComment: 'summary', introspectComments: true },
+            fakeProgram
+          )
+        ]
+      }
+    });
+    expect(result.outputText).toEqual(appControllerWithTabsTextTranspiled);
+  });
 });
diff --git a/test/plugin/fixtures/app.controller-tabs.ts b/test/plugin/fixtures/app.controller-tabs.ts
new file mode 100644
index 00000000..1533d25a
--- /dev/null
+++ b/test/plugin/fixtures/app.controller-tabs.ts
@@ -0,0 +1,91 @@
+// prettier-ignore
+export const appControllerWithTabsText = `import { Controller, Post, HttpStatus } from '@nestjs/common';
+import { ApiOperation } from '@nestjs/swagger';
+
+class Cat {}
+
+@Controller('cats')
+export class AppController {
+	onApplicationBootstrap() {}
+
+	/**
+	 * create a Cat
+	 *
+	 * @returns {Promise<Cat>}
+	 * @memberof AppController
+	 */
+	@Post()
+	async create(): Promise<Cat> {}
+
+	/**
+	 * find a Cat
+	 */
+	@ApiOperation({})
+	@Get()
+	async findOne(): Promise<Cat> {}
+
+	/**
+	 * find all Cats im comment
+	 *
+	 * @returns {Promise<Cat>}
+	 * @memberof AppController
+	 */
+	@ApiOperation({
+	description: 'find all Cats',
+	})
+	@Get()
+	@HttpCode(HttpStatus.NO_CONTENT)
+	async findAll(): Promise<Cat[]> {}
+}`;
+
+// prettier-ignore
+export const appControllerWithTabsTextTranspiled = `\"use strict\";
+Object.defineProperty(exports, \"__esModule\", { value: true });
+exports.AppController = void 0;
+const openapi = require(\"@nestjs/swagger\");
+const common_1 = require(\"@nestjs/common\");
+const swagger_1 = require("@nestjs/swagger");
+class Cat {
+}
+let AppController = class AppController {
+    onApplicationBootstrap() { }
+    /**
+     * create a Cat
+     *
+     * @returns {Promise<Cat>}
+     * @memberof AppController
+     */
+    async create() { }
+    /**
+     * find a Cat
+     */
+    async findOne() { }
+    /**
+     * find all Cats im comment
+     *
+     * @returns {Promise<Cat>}
+     * @memberof AppController
+     */
+    async findAll() { }
+};
+__decorate([
+    openapi.ApiOperation({ summary: "create a Cat" }),
+    common_1.Post(),
+    openapi.ApiResponse({ status: 201, type: Cat })
+], AppController.prototype, \"create\", null);
+__decorate([
+    swagger_1.ApiOperation({ summary: "find a Cat" }),
+    Get(),
+    openapi.ApiResponse({ status: 200, type: Cat })
+], AppController.prototype, \"findOne\", null);
+__decorate([
+    swagger_1.ApiOperation({ summary: "find all Cats im comment", description: 'find all Cats' }),
+    Get(),
+    HttpCode(common_1.HttpStatus.NO_CONTENT),
+    openapi.ApiResponse({ status: common_1.HttpStatus.NO_CONTENT, type: [Cat] })
+], AppController.prototype, \"findAll\", null);
+AppController = __decorate([
+    common_1.Controller('cats')
+], AppController);
+exports.AppController = AppController;
+`;