|
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