// ***************************************************************************** // Copyright (C) 2019 Ericsson and others. // // This program and the accompanying materials are made available under the // terms of the Eclipse Public License v. 2.0 which is available at // http://www.eclipse.org/legal/epl-2.0. // // This Source Code may also be made available under the following Secondary // Licenses when the conditions for such availability set forth in the Eclipse // Public License v. 2.0 are satisfied: GNU General Public License, version 2 // with the GNU Classpath Exception which is available at // https://www.gnu.org/software/classpath/license.html. // // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 // ***************************************************************************** import { Severity } from '@theia/core/lib/common/severity'; import { DiagnosticSeverity } from '@theia/core/shared/vscode-languageserver-protocol'; import { expect } from 'chai'; import { ApplyToKind, FileLocationKind, ProblemLocationKind, ProblemMatch, ProblemMatchData, ProblemMatcher } from '../common/problem-matcher-protocol'; import { ProblemCollector } from './task-problem-collector'; const startStopMatcher1: ProblemMatcher = { owner: 'test1', source: 'test1', applyTo: ApplyToKind.allDocuments, fileLocation: FileLocationKind.Absolute, pattern: { regexp: /^([^:]*: )?((.:)?[^:]*):(\d+)(:(\d+))?: (.*)$/.source, kind: ProblemLocationKind.Location, file: 2, line: 4, character: 6, message: 7 }, severity: Severity.Error }; const startStopMatcher2: ProblemMatcher = { owner: 'test2', source: 'test2', applyTo: ApplyToKind.allDocuments, fileLocation: FileLocationKind.Absolute, pattern: [ { regexp: /^([^\s].*)$/.source, kind: ProblemLocationKind.Location, file: 1 }, { regexp: /^\s+(\d+):(\d+)\s+(error|warning|info)\s+(.+?)(?:\s\s+(.*))?$/.source, line: 1, character: 2, severity: 3, message: 4, code: 5, loop: true } ], severity: Severity.Error }; const startStopMatcher3: ProblemMatcher = { owner: 'test2', source: 'test2', applyTo: ApplyToKind.allDocuments, fileLocation: FileLocationKind.Absolute, pattern: [ { regexp: /^([^\s].*)$/.source, kind: ProblemLocationKind.File, file: 1 }, { regexp: /^\s+(\d+):(\d+)\s+(error|warning|info)\s+(.+?)(?:\s\s+(.*))?$/.source, severity: 3, message: 4, code: 5, loop: true } ], severity: Severity.Error }; const watchMatcher: ProblemMatcher = { owner: 'test3', applyTo: ApplyToKind.closedDocuments, fileLocation: FileLocationKind.Absolute, pattern: { regexp: /Error: ([^(]+)\((\d+|\d+,\d+|\d+,\d+,\d+,\d+)\): (.*)$/.source, file: 1, location: 2, message: 3 }, watching: { activeOnStart: false, beginsPattern: { regexp: /Starting compilation/.source }, endsPattern: { regexp: /Finished compilation/.source } } }; describe('ProblemCollector', () => { let collector: ProblemCollector; const allMatches: ProblemMatch[] = []; const collectMatches = (lines: string[]) => { lines.forEach(line => { const matches = collector.processLine(line); if (matches.length > 0) { allMatches.push(...matches); } }); }; beforeEach(() => { allMatches.length = 0; }); it('should find problems from start-stop task when problem matcher is associated with one problem pattern', () => { collector = new ProblemCollector([startStopMatcher1]); collectMatches([ 'npm WARN lifecycle The node binary used for scripts is /tmp/yarn--1557403301319-0.5645247996849125/node but npm is using /usr/local/bin/node itself.', 'Use the `--scripts-prepend-node-path` option to include the path for the node binary npm was executed with.', '', '# command-line-arguments', '/home/test/hello.go:9:2: undefined: fmt.Pntln', '/home/test/hello.go:10:6: undefined: numb', '/home/test/hello.go:15:9: undefined: stri' ]); expect(allMatches.length).to.eq(3); expect((allMatches[0] as ProblemMatchData).resource!.path.toString()).eq('/home/test/hello.go'); expect((allMatches[0] as ProblemMatchData).marker).deep.equal({ range: { start: { line: 8, character: 1 }, end: { line: 8, character: 1 } }, severity: DiagnosticSeverity.Error, source: 'test1', message: 'undefined: fmt.Pntln' }); expect((allMatches[1] as ProblemMatchData).resource!.path.toString()).eq('/home/test/hello.go'); expect((allMatches[1] as ProblemMatchData).marker).deep.equal({ range: { start: { line: 9, character: 5 }, end: { line: 9, character: 5 } }, severity: DiagnosticSeverity.Error, source: 'test1', message: 'undefined: numb' }); expect((allMatches[2] as ProblemMatchData).resource!.path.toString()).eq('/home/test/hello.go'); expect((allMatches[2] as ProblemMatchData).marker).deep.equal({ range: { start: { line: 14, character: 8 }, end: { line: 14, character: 8 } }, severity: DiagnosticSeverity.Error, source: 'test1', message: 'undefined: stri' }); }); it('should find problems from start-stop task when problem matcher is associated with more than one problem pattern', () => { collector = new ProblemCollector([startStopMatcher2]); collectMatches([ '> test@0.1.0 lint /home/test', '> eslint .', '', '', '/home/test/test-dir.js', ' 14:21 warning Missing semicolon semi', ' 15:23 warning Missing semicolon semi', ' 103:9 error Parsing error: Unexpected token inte', '', '/home/test/more-test.js', ' 13:9 error Parsing error: Unexpected token 1000', '', '✖ 3 problems (1 error, 2 warnings)', ' 0 errors and 2 warnings potentially fixable with the `--fix` option.' ]); expect(allMatches.length).to.eq(4); expect((allMatches[0] as ProblemMatchData).resource!.path.toString()).eq('/home/test/test-dir.js'); expect((allMatches[0] as ProblemMatchData).marker).deep.equal({ range: { start: { line: 13, character: 20 }, end: { line: 13, character: 20 } }, severity: DiagnosticSeverity.Warning, source: 'test2', message: 'Missing semicolon', code: 'semi' }); expect((allMatches[1] as ProblemMatchData).resource!.path.toString()).eq('/home/test/test-dir.js'); expect((allMatches[1] as ProblemMatchData).marker).deep.equal({ range: { start: { line: 14, character: 22 }, end: { line: 14, character: 22 } }, severity: DiagnosticSeverity.Warning, source: 'test2', message: 'Missing semicolon', code: 'semi' }); expect((allMatches[2] as ProblemMatchData).resource!.path.toString()).eq('/home/test/test-dir.js'); expect((allMatches[2] as ProblemMatchData).marker).deep.equal({ range: { start: { line: 102, character: 8 }, end: { line: 102, character: 8 } }, severity: DiagnosticSeverity.Error, source: 'test2', message: 'Parsing error: Unexpected token inte' }); expect((allMatches[3] as ProblemMatchData).resource!.path.toString()).eq('/home/test/more-test.js'); expect((allMatches[3] as ProblemMatchData).marker).deep.equal({ range: { start: { line: 12, character: 8 }, end: { line: 12, character: 8 } }, severity: DiagnosticSeverity.Error, source: 'test2', message: 'Parsing error: Unexpected token 1000' }); }); it('should find problems from start-stop task when problem matcher is associated with more than one problem pattern and kind is file', () => { collector = new ProblemCollector([startStopMatcher3]); collectMatches([ '> test@0.1.0 lint /home/test', '> eslint .', '', '', '/home/test/test-dir.js', ' 14:21 warning Missing semicolon semi', ' 15:23 warning Missing semicolon semi', ' 103:9 error Parsing error: Unexpected token inte', '', '/home/test/more-test.js', ' 13:9 error Parsing error: Unexpected token 1000', '', '✖ 3 problems (1 error, 2 warnings)', ' 0 errors and 2 warnings potentially fixable with the `--fix` option.' ]); expect(allMatches.length).to.eq(4); expect((allMatches[0] as ProblemMatchData).resource?.path.toString()).eq('/home/test/test-dir.js'); expect((allMatches[0] as ProblemMatchData).marker).deep.equal({ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, severity: DiagnosticSeverity.Warning, source: 'test2', message: 'Missing semicolon', code: 'semi' }); expect((allMatches[1] as ProblemMatchData).resource?.path.toString()).eq('/home/test/test-dir.js'); expect((allMatches[1] as ProblemMatchData).marker).deep.equal({ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, severity: DiagnosticSeverity.Warning, source: 'test2', message: 'Missing semicolon', code: 'semi' }); expect((allMatches[2] as ProblemMatchData).resource?.path.toString()).eq('/home/test/test-dir.js'); expect((allMatches[2] as ProblemMatchData).marker).deep.equal({ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, severity: DiagnosticSeverity.Error, source: 'test2', message: 'Parsing error: Unexpected token inte' }); expect((allMatches[3] as ProblemMatchData).resource?.path.toString()).eq('/home/test/more-test.js'); expect((allMatches[3] as ProblemMatchData).marker).deep.equal({ range: { start: { line: 0, character: 0 }, end: { line: 0, character: 0 } }, severity: DiagnosticSeverity.Error, source: 'test2', message: 'Parsing error: Unexpected token 1000' }); }); it('should search and find defined problems from watch task\'s output', () => { collector = new ProblemCollector([watchMatcher]); collectMatches([ '> code-oss-dev@1.33.1 watch /home/test/vscode', '> gulp watch --max_old_space_size=4095', '', '[09:15:37] Node flags detected: --max_old_space_size=4095', '[09:15:37] Respawned to PID: 14560', '[09:15:40] Using gulpfile ~/dev/vscode/gulpfile.js', "[09:15:40] Starting 'watch'...", '[09:15:40] Starting clean-out ...', '[09:15:41] Starting clean-extension-configuration-editing ...', '[09:15:41] Starting clean-extension-css-language-features-client ...', '[09:15:41] Starting clean-extension-css-language-features-server ...', '[09:15:41] Starting clean-extension-debug-auto-launch ...', '[09:15:41] Starting watch-extension:markdown-language-features-preview-src ...', '[09:15:41] Starting compilation...', // begin pattern 1 '[09:15:41] Finished clean-extension-typescript-basics-test-colorize-fixtures after 49 ms', '[09:15:41] Starting watch-extension:typescript-basics-test-colorize-fixtures ...', '[09:15:41] Finished compilation with 0 errors after 30 ms', '[09:15:41] Finished clean-extension-css-language-features-client after 119 ms', '[09:15:41] Starting watch-extension:css-language-features-client ...', '[09:15:41] Starting compilation...', // begin pattern 2 '[09:15:41] Finished clean-extension-configuration-editing after 128 ms', '[09:15:41] Starting watch-extension:configuration-editing ...', '[09:15:41] Finished clean-extension-debug-auto-launch after 133 ms', '[09:15:41] Starting watch-extension:debug-auto-launch ...', '[09:15:41] Finished clean-extension-debug-server-ready after 138 ms', '[09:15:52] Starting watch-extension:html-language-features-server ...', '[09:15:58] Finished clean-out after 17196 ms', '[09:15:58] Starting watch-client ...', '[09:17:25] Finished compilation with 0 errors after 104209 ms', '[09:19:22] Starting compilation...', // begin pattern 3 "[09:19:23] Error: /home/test/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts(517,19): ')' expected.", // problem 1 "[09:19:23] Error: /home/test/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts(517,57): ';' expected.", // problem 2 '[09:19:23] Finished compilation with 2 errors after 1051 ms', '[09:20:21] Starting compilation...', // begin pattern 4 "[09:20:24] Error: /home/test/src/vs/workbench/contrib/tasks/common/problemCollectors.ts(15,30): Cannot find module 'n/uuid'.", // problem 3 "[09:20:24] Error: /home/test/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts(517,19): ')' expected.", // problem 4 "[09:20:24] Error: /home/test/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts(517,57): ';' expected.", // problem 5 '[09:20:24] Finished compilation with 3 errors after 2586 ms', '[09:20:24] Starting compilation...', // begin pattern 5 '[09:20:25] Error: /home/test/src/vs/workbench/contrib/tasks/common/taskTemplates.ts(12,14): Type expected.', // problem 6 "[09:20:25] Error: /home/test/src/vs/workbench/contrib/tasks/common/problemCollectors.ts(15,30): Cannot find module 'n/uuid'.", // problem 7 "[09:20:25] Error: /home/test/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts(517,19): ')' expected.", // problem 8 "[09:20:25] Error: /home/test/src/vs/workbench/contrib/tasks/common/taskConfiguration.ts(517,57): ';' expected.", // problem 9 '[09:20:25] Finished compilation with 4 errors after 441 ms' ]); expect(allMatches.length).to.eq(14); // 9 events for problems + 5 events for beginner pattern }); it('should return an empty array if no problems are found', () => { collector = new ProblemCollector([startStopMatcher2]); collectMatches([]); expect(allMatches.length).to.eq(0); collectMatches([ '> test@0.1.0 lint /home/test', '> eslint .', '', '', '0 problems (0 error, 0 warnings)', ]); expect(allMatches.length).to.eq(0); }); });