|
1 | 1 | namespace ts { |
2 | | - const ignoreDiagnosticCommentRegEx = /(^\s*$)|(^\s*\/\/\/?\s*(@ts-ignore)?)/; |
3 | | - |
4 | 2 | export function findConfigFile(searchPath: string, fileExists: (fileName: string) => boolean, configName = "tsconfig.json"): string | undefined { |
5 | 3 | return forEachAncestorDirectory(searchPath, ancestor => { |
6 | 4 | const fileName = combinePaths(ancestor, configName); |
@@ -1661,17 +1659,16 @@ namespace ts { |
1661 | 1659 | const fileProcessingDiagnosticsInFile = fileProcessingDiagnostics.getDiagnostics(sourceFile.fileName); |
1662 | 1660 | const programDiagnosticsInFile = programDiagnostics.getDiagnostics(sourceFile.fileName); |
1663 | 1661 |
|
1664 | | - let diagnostics: Diagnostic[] | undefined; |
1665 | | - for (const diags of [fileProcessingDiagnosticsInFile, programDiagnosticsInFile]) { |
1666 | | - if (diags) { |
1667 | | - for (const diag of diags) { |
1668 | | - if (shouldReportDiagnostic(diag)) { |
1669 | | - diagnostics = append(diagnostics, diag); |
1670 | | - } |
1671 | | - } |
1672 | | - } |
| 1662 | + return getMergedProgramDiagnostics(sourceFile, fileProcessingDiagnosticsInFile, programDiagnosticsInFile); |
| 1663 | + } |
| 1664 | + |
| 1665 | + function getMergedProgramDiagnostics(sourceFile: SourceFile, ...allDiagnostics: (readonly Diagnostic[] | undefined)[]) { |
| 1666 | + const flatDiagnostics = flatten(allDiagnostics); |
| 1667 | + if (!sourceFile.commentDirectives?.length) { |
| 1668 | + return flatDiagnostics; |
1673 | 1669 | } |
1674 | | - return diagnostics || emptyArray; |
| 1670 | + |
| 1671 | + return getDiagnosticsWithPrecedingDirectives(sourceFile, sourceFile.commentDirectives, flatDiagnostics).diagnostics; |
1675 | 1672 | } |
1676 | 1673 |
|
1677 | 1674 | function getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { |
@@ -1749,49 +1746,72 @@ namespace ts { |
1749 | 1746 | const bindDiagnostics: readonly Diagnostic[] = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray; |
1750 | 1747 | const checkDiagnostics = includeBindAndCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray; |
1751 | 1748 |
|
1752 | | - let diagnostics: Diagnostic[] | undefined; |
1753 | | - for (const diags of [bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined]) { |
1754 | | - if (diags) { |
1755 | | - for (const diag of diags) { |
1756 | | - if (shouldReportDiagnostic(diag)) { |
1757 | | - diagnostics = append(diagnostics, diag); |
1758 | | - } |
1759 | | - } |
1760 | | - } |
1761 | | - } |
1762 | | - return diagnostics || emptyArray; |
| 1749 | + return getMergedBindAndCheckDiagnostics(sourceFile, bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined); |
1763 | 1750 | }); |
1764 | 1751 | } |
1765 | 1752 |
|
| 1753 | + function getMergedBindAndCheckDiagnostics(sourceFile: SourceFile, ...allDiagnostics: (readonly Diagnostic[] | undefined)[]) { |
| 1754 | + const flatDiagnostics = flatten(allDiagnostics); |
| 1755 | + if (!sourceFile.commentDirectives?.length) { |
| 1756 | + return flatDiagnostics; |
| 1757 | + } |
| 1758 | + |
| 1759 | + const { diagnostics, directives } = getDiagnosticsWithPrecedingDirectives(sourceFile, sourceFile.commentDirectives, flatDiagnostics); |
| 1760 | + |
| 1761 | + for (const errorExpectation of directives.getUnusedExpectations()) { |
| 1762 | + diagnostics.push(createDiagnosticForRange(sourceFile, errorExpectation.range, Diagnostics.Unused_ts_expect_error_directive)); |
| 1763 | + } |
| 1764 | + |
| 1765 | + return diagnostics; |
| 1766 | + } |
| 1767 | + |
| 1768 | + /** |
| 1769 | + * Creates a map of comment directives along with the diagnostics immediately preceded by one of them. |
| 1770 | + * Comments that match to any of those diagnostics are marked as used. |
| 1771 | + */ |
| 1772 | + function getDiagnosticsWithPrecedingDirectives(sourceFile: SourceFile, commentDirectives: CommentDirective[], flatDiagnostics: Diagnostic[]) { |
| 1773 | + // Diagnostics are only reported if there is no comment directive preceding them |
| 1774 | + // This will modify the directives map by marking "used" ones with a corresponding diagnostic |
| 1775 | + const directives = createCommentDirectivesMap(sourceFile, commentDirectives); |
| 1776 | + const diagnostics = flatDiagnostics.filter(diagnostic => markPrecedingCommentDirectiveLine(diagnostic, directives) === -1); |
| 1777 | + |
| 1778 | + return { diagnostics, directives }; |
| 1779 | + } |
| 1780 | + |
1766 | 1781 | function getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { |
1767 | 1782 | return runWithCancellationToken(() => { |
1768 | 1783 | return getDiagnosticsProducingTypeChecker().getSuggestionDiagnostics(sourceFile, cancellationToken); |
1769 | 1784 | }); |
1770 | 1785 | } |
1771 | 1786 |
|
1772 | 1787 | /** |
1773 | | - * Skip errors if previous line start with '// @ts-ignore' comment, not counting non-empty non-comment lines |
| 1788 | + * @returns The line index marked as preceding the diagnostic, or -1 if none was. |
1774 | 1789 | */ |
1775 | | - function shouldReportDiagnostic(diagnostic: Diagnostic) { |
| 1790 | + function markPrecedingCommentDirectiveLine(diagnostic: Diagnostic, directives: CommentDirectivesMap) { |
1776 | 1791 | const { file, start } = diagnostic; |
1777 | | - if (file) { |
1778 | | - const lineStarts = getLineStarts(file); |
1779 | | - let { line } = computeLineAndCharacterOfPosition(lineStarts, start!); // TODO: GH#18217 |
1780 | | - while (line > 0) { |
1781 | | - const previousLineText = file.text.slice(lineStarts[line - 1], lineStarts[line]); |
1782 | | - const result = ignoreDiagnosticCommentRegEx.exec(previousLineText); |
1783 | | - if (!result) { |
1784 | | - // non-empty line |
1785 | | - return true; |
1786 | | - } |
1787 | | - if (result[3]) { |
1788 | | - // @ts-ignore |
1789 | | - return false; |
1790 | | - } |
1791 | | - line--; |
| 1792 | + if (!file) { |
| 1793 | + return -1; |
| 1794 | + } |
| 1795 | + |
| 1796 | + // Start out with the line just before the text |
| 1797 | + const lineStarts = getLineStarts(file); |
| 1798 | + let line = computeLineAndCharacterOfPosition(lineStarts, start!).line - 1; // TODO: GH#18217 |
| 1799 | + while (line >= 0) { |
| 1800 | + // As soon as that line is known to have a comment directive, use that |
| 1801 | + if (directives.markUsed(line)) { |
| 1802 | + return line; |
1792 | 1803 | } |
| 1804 | + |
| 1805 | + // Stop searching if the line is not empty and not a comment |
| 1806 | + const lineText = file.text.slice(lineStarts[line - 1], lineStarts[line]).trim(); |
| 1807 | + if (lineText !== "" && !/^(\s*)\/\/(.*)$/.test(lineText)) { |
| 1808 | + return -1; |
| 1809 | + } |
| 1810 | + |
| 1811 | + line--; |
1793 | 1812 | } |
1794 | | - return true; |
| 1813 | + |
| 1814 | + return -1; |
1795 | 1815 | } |
1796 | 1816 |
|
1797 | 1817 | function getJSSyntacticDiagnosticsForFile(sourceFile: SourceFile): DiagnosticWithLocation[] { |
|
0 commit comments