diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts
index c021b87d1c470..475216f6e1c75 100644
--- a/src/compiler/parser.ts
+++ b/src/compiler/parser.ts
@@ -1026,7 +1026,10 @@ namespace ts {
         let hasDeprecatedTag = false;
         function addJSDocComment<T extends HasJSDoc>(node: T): T {
             Debug.assert(!node.jsDoc); // Should only be called once per node
-            const jsDoc = mapDefined(getJSDocCommentRanges(node, sourceText), comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos));
+            const ranges = getJSDocCommentRanges(node, sourceText);
+            const jsDoc = ranges?.every(r => isTripleSlashComment(r, sourceText))
+                ? JSDocParser.parseTripleSlashes(node, ranges)
+                : mapDefined(ranges, comment => JSDocParser.parseJSDocComment(node, comment.pos, comment.end - comment.pos));
             if (jsDoc.length) node.jsDoc = jsDoc;
             if (hasDeprecatedTag) {
                 hasDeprecatedTag = false;
@@ -7158,6 +7161,26 @@ namespace ts {
                 return jsDoc ? { jsDoc, diagnostics } : undefined;
             }
 
+            export function parseTripleSlashes(parent: HasJSDoc, comments: CommentRange[]) {
+                const saveToken = currentToken;
+                const saveParseDiagnosticsLength = parseDiagnostics.length;
+                const saveParseErrorBeforeNextFinishedNode = parseErrorBeforeNextFinishedNode;
+
+                const comment = doInsideOfContext(NodeFlags.JSDoc, () => parseJSDocCommentWorker(comments, /*length*/ undefined));
+                setParent(comment, parent);
+
+                if (contextFlags & NodeFlags.JavaScriptFile) {
+                    if (!jsDocDiagnostics) {
+                        jsDocDiagnostics = [];
+                    }
+                    jsDocDiagnostics.push(...parseDiagnostics);
+                }
+                currentToken = saveToken;
+                parseDiagnostics.length = saveParseDiagnosticsLength;
+                parseErrorBeforeNextFinishedNode = saveParseErrorBeforeNextFinishedNode;
+                return comment ? [comment] : [];
+            }
+
             export function parseJSDocComment(parent: HasJSDoc, start: number, length: number): JSDoc | undefined {
                 const saveToken = currentToken;
                 const saveParseDiagnosticsLength = parseDiagnostics.length;
@@ -7191,8 +7214,43 @@ namespace ts {
                 CallbackParameter = 1 << 2,
             }
 
-            function parseJSDocCommentWorker(start = 0, length: number | undefined): JSDoc | undefined {
-                const content = sourceText;
+            function parseJSDocCommentWorker(startOrRanges: number | CommentRange[] = 0, length: number | undefined): JSDoc | undefined {
+                // const isTripleSlash = Array.isArray(startOrRanges);
+                // TODO: Probably should save a boolean isTripleSlash at the beginning and make all the nested functions change their behaviour.
+                const content = sourceText; // TODO: Why alias this?
+                const comments: string[] = [];
+                let tags: JSDocTag[];
+                let tagsPos: number;
+                let tagsEnd: number;
+                if (Array.isArray(startOrRanges)) {
+                    if (!startOrRanges.length) return undefined;
+                    const ranges = startOrRanges;
+                    let currentTag: JSDocTag | undefined; // TODO: Probably can use tags
+                    for (const { pos: start, end } of ranges) {
+                        const length = end - start;
+                        scanner.scanRange(start + 3, length - 3, () => {
+                            while (nextTokenJSDoc() !== SyntaxKind.EndOfFileToken) {
+                                if (token() === SyntaxKind.AtToken) {
+                                    addTag(currentTag);
+                                    currentTag = parseTag(0);
+                                }
+                                else {
+                                    if (currentTag) {
+                                        // this doesn't update currentTag.end, which will cause problems later
+                                        // I think that parseXTag will have to return unfinished tags or something.
+                                        (currentTag as any).comment = (currentTag.comment || "") + scanner.getTokenText()
+                                    }
+                                    else {
+                                        comments.push(scanner.getTokenText());
+                                    }
+                                }
+                            }
+                        });
+                    }
+                    return createJSDocComment(ranges[0].pos, ranges[ranges.length - 1].end);
+                }
+
+                const start = startOrRanges;
                 const end = length === undefined ? content.length : start + length;
                 length = end - start;
 
@@ -7205,10 +7263,6 @@ namespace ts {
                     return undefined;
                 }
 
-                let tags: JSDocTag[];
-                let tagsPos: number;
-                let tagsEnd: number;
-                const comments: string[] = [];
 
                 // + 3 for leading /**, - 5 in total for /** */
                 return scanner.scanRange(start + 3, length - 5, () => {
@@ -7292,7 +7346,7 @@ namespace ts {
                     }
                     removeLeadingNewlines(comments);
                     removeTrailingWhitespace(comments);
-                    return createJSDocComment();
+                    return createJSDocComment(start, end);
                 });
 
                 function removeLeadingNewlines(comments: string[]) {
@@ -7307,7 +7361,7 @@ namespace ts {
                     }
                 }
 
-                function createJSDocComment(): JSDoc {
+                function createJSDocComment(start: number, end: number): JSDoc {
                     const comment = comments.length ? comments.join("") : undefined;
                     const tagsArray = tags && createNodeArray(tags, tagsPos, tagsEnd);
                     return finishNode(factory.createJSDocComment(comment, tagsArray), start, end);
diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts
index 77ec82ffef760..2cd3ca63454c6 100644
--- a/src/compiler/scanner.ts
+++ b/src/compiler/scanner.ts
@@ -1745,6 +1745,9 @@ namespace ts {
                     case CharacterCodes.slash:
                         // Single-line comment
                         if (text.charCodeAt(pos + 1) === CharacterCodes.slash) {
+                            if (text.charCodeAt(pos + 2) === CharacterCodes.slash) {
+                                tokenFlags |= TokenFlags.PrecedingJSDocComment;
+                            }
                             pos += 2;
 
                             while (pos < end) {
@@ -1770,10 +1773,10 @@ namespace ts {
                         }
                         // Multi-line comment
                         if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) {
-                            pos += 2;
-                            if (text.charCodeAt(pos) === CharacterCodes.asterisk && text.charCodeAt(pos + 1) !== CharacterCodes.slash) {
+                            if (text.charCodeAt(pos + 2) === CharacterCodes.asterisk && text.charCodeAt(pos + 3) !== CharacterCodes.slash) {
                                 tokenFlags |= TokenFlags.PrecedingJSDocComment;
                             }
+                            pos += 2;
 
                             let commentClosed = false;
                             let lastLineStart = tokenPos;
diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts
index d7c00dbcc14c9..4a783ebd6e4e2 100644
--- a/src/compiler/utilities.ts
+++ b/src/compiler/utilities.ts
@@ -1094,6 +1094,10 @@ namespace ts {
         return node.kind !== SyntaxKind.JsxText ? getLeadingCommentRanges(sourceFileOfNode.text, node.pos) : undefined;
     }
 
+    export function isTripleSlashComment(comment: CommentRange, text: string) {
+        return text.charCodeAt(comment.pos + 1) === CharacterCodes.slash && text.charCodeAt(comment.pos + 2) === CharacterCodes.slash;
+    }
+
     export function getJSDocCommentRanges(node: Node, text: string) {
         const commentRanges = (node.kind === SyntaxKind.Parameter ||
             node.kind === SyntaxKind.TypeParameter ||
@@ -1102,11 +1106,12 @@ namespace ts {
             node.kind === SyntaxKind.ParenthesizedExpression) ?
             concatenate(getTrailingCommentRanges(text, node.pos), getLeadingCommentRanges(text, node.pos)) :
             getLeadingCommentRanges(text, node.pos);
-        // True if the comment starts with '/**' but not if it is '/**/'
+        // True if the comment starts with '///' or '/**' but not if it is '/**/'
         return filter(commentRanges, comment =>
             text.charCodeAt(comment.pos + 1) === CharacterCodes.asterisk &&
             text.charCodeAt(comment.pos + 2) === CharacterCodes.asterisk &&
-            text.charCodeAt(comment.pos + 3) !== CharacterCodes.slash);
+            text.charCodeAt(comment.pos + 3) !== CharacterCodes.slash
+            || isTripleSlashComment(comment, text) && !isRecognizedTripleSlashComment(text, comment.pos, comment.end));
     }
 
     export const fullTripleSlashReferencePathRegEx = /^(\/\/\/\s*<reference\s+path\s*=\s*)('|")(.+?)\2.*?\/>/;
diff --git a/tests/baselines/reference/tripleSlashJsdoc.symbols b/tests/baselines/reference/tripleSlashJsdoc.symbols
new file mode 100644
index 0000000000000..8f5dca5a63b61
--- /dev/null
+++ b/tests/baselines/reference/tripleSlashJsdoc.symbols
@@ -0,0 +1,40 @@
+=== tests/cases/conformance/jsdoc/tripleSlash.js ===
+/// @type {number} - TODO this is still skipped for some reason
+var x;
+>x : Symbol(x, Decl(tripleSlash.js, 1, 3))
+
+/// Adds one
+/// @param {number} n - this is a long,
+/// multiline comment
+///
+/// @return {number}
+function add1(n) {
+>add1 : Symbol(add1, Decl(tripleSlash.js, 1, 6))
+>n : Symbol(n, Decl(tripleSlash.js, 8, 14))
+
+    return n + 1
+>n : Symbol(n, Decl(tripleSlash.js, 8, 14))
+}
+
+// Should be the same
+
+/**  Adds one
+ * @param {number} n - this is a long,
+ * multiline comment
+ *
+ * @return {number}
+*/
+function add2(n) {
+>add2 : Symbol(add2, Decl(tripleSlash.js, 10, 1))
+>n : Symbol(n, Decl(tripleSlash.js, 20, 14))
+
+    return n + 1
+>n : Symbol(n, Decl(tripleSlash.js, 20, 14))
+}
+
+/// I documented this const
+const documented = ""
+>documented : Symbol(documented, Decl(tripleSlash.js, 25, 5))
+
+
+
diff --git a/tests/baselines/reference/tripleSlashJsdoc.types b/tests/baselines/reference/tripleSlashJsdoc.types
new file mode 100644
index 0000000000000..8a58c3170104b
--- /dev/null
+++ b/tests/baselines/reference/tripleSlashJsdoc.types
@@ -0,0 +1,45 @@
+=== tests/cases/conformance/jsdoc/tripleSlash.js ===
+/// @type {number} - TODO this is still skipped for some reason
+var x;
+>x : number
+
+/// Adds one
+/// @param {number} n - this is a long,
+/// multiline comment
+///
+/// @return {number}
+function add1(n) {
+>add1 : (n: number) => number
+>n : number
+
+    return n + 1
+>n + 1 : number
+>n : number
+>1 : 1
+}
+
+// Should be the same
+
+/**  Adds one
+ * @param {number} n - this is a long,
+ * multiline comment
+ *
+ * @return {number}
+*/
+function add2(n) {
+>add2 : (n: number) => number
+>n : number
+
+    return n + 1
+>n + 1 : number
+>n : number
+>1 : 1
+}
+
+/// I documented this const
+const documented = ""
+>documented : ""
+>"" : ""
+
+
+
diff --git a/tests/cases/conformance/jsdoc/tripleSlashJsdoc.ts b/tests/cases/conformance/jsdoc/tripleSlashJsdoc.ts
new file mode 100644
index 0000000000000..991afd8a483da
--- /dev/null
+++ b/tests/cases/conformance/jsdoc/tripleSlashJsdoc.ts
@@ -0,0 +1,34 @@
+// @allowJs: true
+// @checkJs: true
+// @noImplicitAny: true
+// @noEmit: true
+// @filename: tripleSlash.js
+
+/// @type {number} - TODO this is still skipped for some reason
+var x;
+
+/// Adds one
+/// @param {number} n - this is a long,
+/// multiline comment
+///
+/// @return {number}
+function add1(n) {
+    return n + 1
+}
+
+// Should be the same
+
+/**  Adds one
+ * @param {number} n - this is a long,
+ * multiline comment
+ *
+ * @return {number}
+*/
+function add2(n) {
+    return n + 1
+}
+
+/// I documented this const
+const documented = ""
+
+
diff --git a/tests/cases/fourslash/commentsCommentParsing.ts b/tests/cases/fourslash/commentsCommentParsing.ts
index 4731194ba697c..755a1d1cc6332 100644
--- a/tests/cases/fourslash/commentsCommentParsing.ts
+++ b/tests/cases/fourslash/commentsCommentParsing.ts
@@ -202,7 +202,7 @@
 ////class NoQuic/*50*/kInfoClass {
 ////}
 
-verify.signatureHelp({ marker: "1", docComment: "" });
+verify.signatureHelp({ marker: "1", docComment: " This is simple /// comments" });
 verify.quickInfoAt("1q", "function simple(): void");
 
 verify.signatureHelp({ marker: "2", docComment: "" });
diff --git a/tests/cases/fourslash/signatureHelpTripleSlashJSDoc.ts b/tests/cases/fourslash/signatureHelpTripleSlashJSDoc.ts
new file mode 100644
index 0000000000000..23feb8652dc76
--- /dev/null
+++ b/tests/cases/fourslash/signatureHelpTripleSlashJSDoc.ts
@@ -0,0 +1,18 @@
+/// <reference path="fourslash.ts" />
+
+// @allowJs: true
+// @checkJs: true
+
+// @Filename: test.js
+//// /// Adds one
+//// /// @param {number} n - this is a long,
+//// /// multiline comment
+//// ///
+//// /// @return {number}
+//// function add1(/*2*/n) {
+////     return n + 1
+//// }
+//// add1/*1*/(12)
+
+verify.quickInfoAt('1', 'function add1(n: number): number', ' Adds one ')
+verify.quickInfoAt('2', '(parameter) n: number', '- this is a long, multiline comment ')