|
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); |
@@ -1650,17 +1648,16 @@ namespace ts { |
1650 | 1648 | const fileProcessingDiagnosticsInFile = fileProcessingDiagnostics.getDiagnostics(sourceFile.fileName); |
1651 | 1649 | const programDiagnosticsInFile = programDiagnostics.getDiagnostics(sourceFile.fileName); |
1652 | 1650 |
|
1653 | | - let diagnostics: Diagnostic[] | undefined; |
1654 | | - for (const diags of [fileProcessingDiagnosticsInFile, programDiagnosticsInFile]) { |
1655 | | - if (diags) { |
1656 | | - for (const diag of diags) { |
1657 | | - if (shouldReportDiagnostic(diag)) { |
1658 | | - diagnostics = append(diagnostics, diag); |
1659 | | - } |
1660 | | - } |
1661 | | - } |
| 1651 | + return getMergedProgramDiagnostics(sourceFile, fileProcessingDiagnosticsInFile, programDiagnosticsInFile); |
| 1652 | + } |
| 1653 | + |
| 1654 | + function getMergedProgramDiagnostics(sourceFile: SourceFile, ...allDiagnostics: (readonly Diagnostic[] | undefined)[]) { |
| 1655 | + const flatDiagnostics = flatten(allDiagnostics); |
| 1656 | + if (!sourceFile.commentDirectives?.length) { |
| 1657 | + return flatDiagnostics; |
1662 | 1658 | } |
1663 | | - return diagnostics || emptyArray; |
| 1659 | + |
| 1660 | + return getDiagnosticsPastDirectives(sourceFile, sourceFile.commentDirectives, flatDiagnostics).diagnostics; |
1664 | 1661 | } |
1665 | 1662 |
|
1666 | 1663 | function getDeclarationDiagnostics(sourceFile?: SourceFile, cancellationToken?: CancellationToken): readonly DiagnosticWithLocation[] { |
@@ -1738,49 +1735,68 @@ namespace ts { |
1738 | 1735 | const bindDiagnostics: readonly Diagnostic[] = includeBindAndCheckDiagnostics ? sourceFile.bindDiagnostics : emptyArray; |
1739 | 1736 | const checkDiagnostics = includeBindAndCheckDiagnostics ? typeChecker.getDiagnostics(sourceFile, cancellationToken) : emptyArray; |
1740 | 1737 |
|
1741 | | - let diagnostics: Diagnostic[] | undefined; |
1742 | | - for (const diags of [bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined]) { |
1743 | | - if (diags) { |
1744 | | - for (const diag of diags) { |
1745 | | - if (shouldReportDiagnostic(diag)) { |
1746 | | - diagnostics = append(diagnostics, diag); |
1747 | | - } |
1748 | | - } |
1749 | | - } |
1750 | | - } |
1751 | | - return diagnostics || emptyArray; |
| 1738 | + return getMergedBindAndCheckDiagnostics(sourceFile, bindDiagnostics, checkDiagnostics, isCheckJs ? sourceFile.jsDocDiagnostics : undefined); |
1752 | 1739 | }); |
1753 | 1740 | } |
1754 | 1741 |
|
| 1742 | + function getMergedBindAndCheckDiagnostics(sourceFile: SourceFile, ...allDiagnostics: (readonly Diagnostic[] | undefined)[]) { |
| 1743 | + const flatDiagnostics = flatten(allDiagnostics); |
| 1744 | + if (!sourceFile.commentDirectives?.length) { |
| 1745 | + return flatDiagnostics; |
| 1746 | + } |
| 1747 | + |
| 1748 | + const { diagnostics, directives } = getDiagnosticsPastDirectives(sourceFile, sourceFile.commentDirectives, flatDiagnostics); |
| 1749 | + |
| 1750 | + for (const errorExpectation of directives.getUnusedExpectations()) { |
| 1751 | + diagnostics.push(createDiagnosticForRange(sourceFile, errorExpectation.range, Diagnostics.Unused_ts_expect_error_directive)); |
| 1752 | + } |
| 1753 | + |
| 1754 | + return diagnostics; |
| 1755 | + } |
| 1756 | + |
| 1757 | + function getDiagnosticsPastDirectives(sourceFile: SourceFile, commentDirectives: CommentDirective[], flatDiagnostics: Diagnostic[]) { |
| 1758 | + // Diagnostics are only reported if there is no comment directive preceding them |
| 1759 | + // This will modify the directives map by marking "used" ones with a corresponding diagnostic |
| 1760 | + const directives = createCommentDirectivesMap(sourceFile, commentDirectives); |
| 1761 | + const diagnostics = flatDiagnostics.filter(diagnostic => markPrecedingCommentDirectiveLine(diagnostic, directives) === -1); |
| 1762 | + |
| 1763 | + return { diagnostics, directives }; |
| 1764 | + } |
| 1765 | + |
1755 | 1766 | function getSuggestionDiagnostics(sourceFile: SourceFile, cancellationToken: CancellationToken): readonly DiagnosticWithLocation[] { |
1756 | 1767 | return runWithCancellationToken(() => { |
1757 | 1768 | return getDiagnosticsProducingTypeChecker().getSuggestionDiagnostics(sourceFile, cancellationToken); |
1758 | 1769 | }); |
1759 | 1770 | } |
1760 | 1771 |
|
1761 | 1772 | /** |
1762 | | - * Skip errors if previous line start with '// @ts-ignore' comment, not counting non-empty non-comment lines |
| 1773 | + * @returns The line index marked as preceding the diagnostic, or -1 if none was. |
1763 | 1774 | */ |
1764 | | - function shouldReportDiagnostic(diagnostic: Diagnostic) { |
| 1775 | + function markPrecedingCommentDirectiveLine(diagnostic: Diagnostic, directives: CommentDirectivesMap) { |
1765 | 1776 | const { file, start } = diagnostic; |
1766 | | - if (file) { |
1767 | | - const lineStarts = getLineStarts(file); |
1768 | | - let { line } = computeLineAndCharacterOfPosition(lineStarts, start!); // TODO: GH#18217 |
1769 | | - while (line > 0) { |
1770 | | - const previousLineText = file.text.slice(lineStarts[line - 1], lineStarts[line]); |
1771 | | - const result = ignoreDiagnosticCommentRegEx.exec(previousLineText); |
1772 | | - if (!result) { |
1773 | | - // non-empty line |
1774 | | - return true; |
1775 | | - } |
1776 | | - if (result[3]) { |
1777 | | - // @ts-ignore |
1778 | | - return false; |
1779 | | - } |
1780 | | - line--; |
| 1777 | + if (!file) { |
| 1778 | + return -1; |
| 1779 | + } |
| 1780 | + |
| 1781 | + // Start out with the line just before the text |
| 1782 | + const lineStarts = getLineStarts(file); |
| 1783 | + let line = computeLineAndCharacterOfPosition(lineStarts, start!).line - 1; // TODO: GH#18217 |
| 1784 | + while (line >= 0) { |
| 1785 | + // As soon as that line is known to have a comment directive, use that |
| 1786 | + if (directives.markUsed(line)) { |
| 1787 | + return line; |
1781 | 1788 | } |
| 1789 | + |
| 1790 | + // Stop searching if the line is not empty and not a comment |
| 1791 | + const lineText = file.text.slice(lineStarts[line - 1], lineStarts[line]).trim(); |
| 1792 | + if (lineText !== "" && !/^(\s*)\/\/(.*)$/.test(lineText)) { |
| 1793 | + return -1; |
| 1794 | + } |
| 1795 | + |
| 1796 | + line--; |
1782 | 1797 | } |
1783 | | - return true; |
| 1798 | + |
| 1799 | + return -1; |
1784 | 1800 | } |
1785 | 1801 |
|
1786 | 1802 | function getJSSyntacticDiagnosticsForFile(sourceFile: SourceFile): DiagnosticWithLocation[] { |
|
0 commit comments