From 23f2e28c176815b3428e38db627b36d7e8997ae0 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:12:07 +0200 Subject: [PATCH 1/5] #34796: Fixed possible undefined toString calls --- dist/index.js | 977 +++++++++++---------------- src/action/decorate/summary.ts | 12 +- src/helper/interfaces.d.ts | 9 +- src/tics/analyzer.ts | 16 +- src/viewer/annotations.ts | 1 + test/unit/viewer/annotations.test.ts | 4 +- 6 files changed, 422 insertions(+), 597 deletions(-) diff --git a/dist/index.js b/dist/index.js index 37f5612b..b04b360d 100644 --- a/dist/index.js +++ b/dist/index.js @@ -428,8 +428,15 @@ function createBody(annotation, displayCount) { else if (annotation.blocking?.state === 'after' && annotation.blocking.after) { body += `Blocking after: ${(0, date_fns_1.format)(annotation.blocking.after, 'yyyy-MM-dd')}${os_1.EOL}`; } + const secondLine = []; + if (annotation.level) { + secondLine.push(`Level: ${annotation.level.toString()}`); + } + if (annotation.category) { + secondLine.push(`Category: ${annotation.category}`); + } body += `Line: ${annotation.line.toString()}: ${displayCount}${annotation.msg}`; - body += `${os_1.EOL}Level: ${annotation.level.toString()}, Category: ${annotation.category}`; + body += secondLine.length > 0 ? `${os_1.EOL}${secondLine.join(', ')}` : ''; body += annotation.ruleHelp ? `${os_1.EOL}Rule-help: ${annotation.ruleHelp}` : ''; return body; } @@ -525,7 +532,7 @@ function createUnpostableAnnotationsDetails(unpostableReviewComments) { else if (previousPath !== path) { body += ``; } - body += ``; + body += ``; previousPath = reviewComment.path ? reviewComment.path : ''; }); body += '
${path}
${icon}${blocking}Line: ${reviewComment.line.toString()} Level: ${reviewComment.level.toString()}
Category: ${reviewComment.category}
${reviewComment.type} violation: ${reviewComment.rule} ${displayCount}
${reviewComment.msg}
${icon}${blocking}Line: ${reviewComment.line.toString()} Level: ${reviewComment.level?.toString() ?? ''}
Category: ${reviewComment.category ?? ''}
${reviewComment.type} violation: ${reviewComment.rule} ${displayCount}
${reviewComment.msg}
'; @@ -2254,17 +2261,17 @@ async function buildRunCommand(fileListPath) { * @param data stdout or stderr */ function findInStdOutOrErr(data) { - const error = data.toString().match(/\[ERROR.*/g); - if (error && !errorList.find(e => e === error.toString())) { - errorList.push(error.toString()); + const error = data.match(/\[ERROR.*/g)?.toString(); + if (error && !errorList.find(e => e === error)) { + errorList.push(error); } - const warning = data.toString().match(/\[WARNING.*/g); - if (warning && !warningList.find(w => w === warning.toString())) { - warningList.push(warning.toString()); + const warning = data.match(/\[WARNING.*/g)?.toString(); + if (warning && !warningList.find(w => w === warning)) { + warningList.push(warning); } - const noFilesToAnalyze = data.toString().match(/No files to analyze with option '-changed':/g); + const noFilesToAnalyze = data.match(/No files to analyze with option '-changed':/g); if (noFilesToAnalyze) { - warningList.push(`[WARNING 5057] ${data.toString()}`); + warningList.push(`[WARNING 5057] ${data}`); } const findExplorerUrl = data.match(/\/Explorer.*/g); if (findExplorerUrl) { @@ -2459,6 +2466,7 @@ async function getAnnotations(apiLinks) { response.data.data.forEach((annotation) => { const extendedAnnotation = { ...annotation, + count: annotation.count ?? 1, instanceName: response.data.annotationTypes ? response.data.annotationTypes[annotation.type].instanceName : annotation.type }; extendedAnnotation.gateId = index; @@ -13493,16 +13501,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.InstallTics = void 0; -exports.getBaseUrl = getBaseUrl; +exports.InstallTics = exports.getBaseUrl = void 0; const os = __importStar(__nccwpck_require__(22037)); -const http_client_1 = __importDefault(__nccwpck_require__(5098)); +const http_client_1 = __importDefault(__nccwpck_require__(14824)); var Platform; (function (Platform) { Platform["aix"] = "aix"; Platform["android"] = "android"; Platform["cygwin"] = "cygwin"; - Platform["darwin"] = "macos"; + Platform["darwin"] = "darwin"; Platform["freebsd"] = "freebsd"; Platform["haiku"] = "haiku"; Platform["linux"] = "linux"; @@ -13530,6 +13537,7 @@ function getBaseUrl(url) { } throw Error('Incorrect TICS Viewer url was given.'); } +exports.getBaseUrl = getBaseUrl; class InstallTics { /** * @param ci If this http client is used in a ci environment. @@ -13553,9 +13561,9 @@ class InstallTics { const platform = Platform[os.platform()]; const installTicsUrl = yield this.getInstallTicsUrl(url, platform); switch (platform) { - case Platform.linux: + case 'linux': return this.linuxInstall(installTicsUrl); - case Platform.win32: + case 'windows': return this.windowsInstall(installTicsUrl); default: throw Error(`No install command found for platform: ${platform}.`); @@ -13617,199 +13625,6 @@ exports.InstallTics = InstallTics; /***/ }), -/***/ 5098: -/***/ (function(__unused_webpack_module, exports, __nccwpck_require__) { - -"use strict"; - -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - var desc = Object.getOwnPropertyDescriptor(m, k); - if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { - desc = { enumerable: true, get: function() { return m[k]; } }; - } - Object.defineProperty(o, k2, desc); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -}; -Object.defineProperty(exports, "__esModule", ({ value: true })); -const node_fetch_1 = __importStar(__nccwpck_require__(80467)); -const retry_1 = __importStar(__nccwpck_require__(38480)); -class HttpClient { - /** - * @param ci If this http client is used in a ci environment. - * @param options (Optional) options to use for the HttpClient. - * @param agent (Optional) agent to be set when using proxy. - */ - constructor(ci, options, agent) { - this.ci = ci; - this.agent = agent; - this.defaultHeaders = new node_fetch_1.Headers(); - if (options === null || options === void 0 ? void 0 : options.authToken) { - this.defaultHeaders.set('authorization', `Basic ${options.authToken}`); - } - if (options === null || options === void 0 ? void 0 : options.xRequestWithTics) { - this.defaultHeaders.set('x-requested-with', 'TICS'); - } - this.customFetch = (0, retry_1.default)(node_fetch_1.default, options === null || options === void 0 ? void 0 : options.retry); - } - /** - * Executes a GET request to the given url. - * @param url api url to perform a GET request for. - */ - get(url, headers) { - return __awaiter(this, void 0, void 0, function* () { - let fixedHeaders = this.defaultHeaders; - headers === null || headers === void 0 ? void 0 : headers.forEach((value, key) => { - fixedHeaders.append(key, value); - }); - const requestInit = { - agent: this.agent, - headers: fixedHeaders - }; - const response = yield this.customFetch(url, requestInit); - switch (response.status) { - case 200: - const text = yield response.text(); - try { - const result = { - status: response.status, - retryCount: response.retryCount, - data: JSON.parse(text) - }; - return result; - } - catch (error) { - throw new retry_1.RequestError(`${error}: ${text}`, response.status, response.retryCount); - } - case 302: - throw new retry_1.RequestError(`HTTP request failed with status ${response.status}. Please check if the given ticsConfiguration is correct.`, response.status, response.retryCount); - case 400: - throw new retry_1.RequestError(`HTTP request failed with status ${response.status}. ${(yield response.json()).alertMessages[0].header}`, response.status, response.retryCount); - case 401: - let authUrl = url.split('/api/')[0]; - authUrl += this.ci ? '/Administration.html#page=authToken' : '/UserSettings.html#page=authToken'; - throw new retry_1.RequestError(`HTTP request failed with status ${response.status}. Please provide a valid TICS authentication token. See ${authUrl}`, response.status, response.retryCount); - case 403: - throw new retry_1.RequestError(`HTTP request failed with status ${response.status}. Forbidden call: ${url}`, response.status, response.retryCount); - case 404: - throw new retry_1.RequestError(`HTTP request failed with status ${response.status}. Please check if the given ticsConfiguration is correct.`, response.status, response.retryCount); - default: - throw new retry_1.RequestError(`HTTP request failed with status ${response.status}: ${response.statusText}`, response.status, response.retryCount); - } - }); - } -} -exports["default"] = HttpClient; -//# sourceMappingURL=index.js.map - -/***/ }), - -/***/ 38480: -/***/ ((__unused_webpack_module, exports) => { - -"use strict"; - -Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.RequestError = void 0; -exports.fetchBuilder = fetchBuilder; -class RequestError extends Error { - constructor(message, status, retryCount) { - super(message); - this.name = 'RequestError'; - this.status = status; - this.retryCount = retryCount; - } -} -exports.RequestError = RequestError; -function sanitize(params, defaults) { - const result = Object.assign(Object.assign({}, defaults), params); - if (typeof result.retries === 'undefined') { - result.retries = defaults.retries; - } - if (typeof result.retryDelay === 'undefined') { - result.retryDelay = defaults.retryDelay; - } - if (typeof result.retryOn === 'undefined') { - result.retryOn = defaults.retryOn; - } - return result; -} -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function fetchBuilder(fetchFunc, params = {}) { - const defaults = sanitize(params, { retries: 3, retryDelay: 500, retryOn: [419, 503, 504] }); - return function (input, init) { - const frp = sanitize({ - // TICS -no-unsafe-assignment - retries: init === null || init === void 0 ? void 0 : init.retries, - retryDelay: init === null || init === void 0 ? void 0 : init.retryDelay, - retryOn: init === null || init === void 0 ? void 0 : init.retryOn - // TICS +no-unsafe-assignment - }, defaults); - const retryDelayFn = typeof frp.retryDelay === 'function' ? frp.retryDelay : () => frp.retryDelay; - const retryOnFn = typeof frp.retryOn === 'function' - ? frp.retryOn - : (attempt, retries, error, response) => (!!error || !response || frp.retryOn.indexOf(response.status) !== -1) && attempt < retries; - return new Promise(function (resolve, reject) { - const extendedFetch = function (attempt) { - fetchFunc(input, init) - .then(function (response) { - if (retryOnFn(attempt, frp.retries, null, response)) { - // eslint-disable-next-line @typescript-eslint/no-use-before-define - retry(attempt, null, response); - } - else { - const responseWithRetryCount = response; - responseWithRetryCount.retryCount = attempt; - resolve(responseWithRetryCount); - } - }) - .catch(function (error) { - if (retryOnFn(attempt, frp.retries, error, null)) { - // eslint-disable-next-line @typescript-eslint/no-use-before-define - retry(attempt, error, null); - } - else { - reject(new RequestError(error.message, 0, attempt)); - } - }); - }; - function retry(attempt, error, response) { - setTimeout(function () { - extendedFetch(++attempt); - }, retryDelayFn(attempt, error, response)); - } - extendedFetch(0); - }); - }; -} -exports["default"] = fetchBuilder; -//# sourceMappingURL=retry.js.map - -/***/ }), - /***/ 40068: /***/ ((__unused_webpack_module, exports) => { @@ -41269,6 +41084,378 @@ function cleanEscapedString(input) { } +/***/ }), + +/***/ 93822: +/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { + +"use strict"; + +exports.parseISO = parseISO; +var _index = __nccwpck_require__(17818); + +/** + * The {@link parseISO} function options. + */ + +/** + * @name parseISO + * @category Common Helpers + * @summary Parse ISO string + * + * @description + * Parse the given string in ISO 8601 format and return an instance of Date. + * + * Function accepts complete ISO 8601 formats as well as partial implementations. + * ISO 8601: http://en.wikipedia.org/wiki/ISO_8601 + * + * If the argument isn't a string, the function cannot parse the string or + * the values are invalid, it returns Invalid Date. + * + * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc). + * + * @param argument - The value to convert + * @param options - An object with options + * + * @returns The parsed date in the local time zone + * + * @example + * // Convert string '2014-02-11T11:30:30' to date: + * const result = parseISO('2014-02-11T11:30:30') + * //=> Tue Feb 11 2014 11:30:30 + * + * @example + * // Convert string '+02014101' to date, + * // if the additional number of digits in the extended year format is 1: + * const result = parseISO('+02014101', { additionalDigits: 1 }) + * //=> Fri Apr 11 2014 00:00:00 + */ +function parseISO(argument, options) { + const additionalDigits = options?.additionalDigits ?? 2; + const dateStrings = splitDateString(argument); + + let date; + if (dateStrings.date) { + const parseYearResult = parseYear(dateStrings.date, additionalDigits); + date = parseDate(parseYearResult.restDateString, parseYearResult.year); + } + + if (!date || isNaN(date.getTime())) { + return new Date(NaN); + } + + const timestamp = date.getTime(); + let time = 0; + let offset; + + if (dateStrings.time) { + time = parseTime(dateStrings.time); + if (isNaN(time)) { + return new Date(NaN); + } + } + + if (dateStrings.timezone) { + offset = parseTimezone(dateStrings.timezone); + if (isNaN(offset)) { + return new Date(NaN); + } + } else { + const dirtyDate = new Date(timestamp + time); + // JS parsed string assuming it's in UTC timezone + // but we need it to be parsed in our timezone + // so we use utc values to build date in our timezone. + // Year values from 0 to 99 map to the years 1900 to 1999 + // so set year explicitly with setFullYear. + const result = new Date(0); + result.setFullYear( + dirtyDate.getUTCFullYear(), + dirtyDate.getUTCMonth(), + dirtyDate.getUTCDate(), + ); + result.setHours( + dirtyDate.getUTCHours(), + dirtyDate.getUTCMinutes(), + dirtyDate.getUTCSeconds(), + dirtyDate.getUTCMilliseconds(), + ); + return result; + } + + return new Date(timestamp + time + offset); +} + +const patterns = { + dateTimeDelimiter: /[T ]/, + timeZoneDelimiter: /[Z ]/i, + timezone: /([Z+-].*)$/, +}; + +const dateRegex = + /^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/; +const timeRegex = + /^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/; +const timezoneRegex = /^([+-])(\d{2})(?::?(\d{2}))?$/; + +function splitDateString(dateString) { + const dateStrings = {}; + const array = dateString.split(patterns.dateTimeDelimiter); + let timeString; + + // The regex match should only return at maximum two array elements. + // [date], [time], or [date, time]. + if (array.length > 2) { + return dateStrings; + } + + if (/:/.test(array[0])) { + timeString = array[0]; + } else { + dateStrings.date = array[0]; + timeString = array[1]; + if (patterns.timeZoneDelimiter.test(dateStrings.date)) { + dateStrings.date = dateString.split(patterns.timeZoneDelimiter)[0]; + timeString = dateString.substr( + dateStrings.date.length, + dateString.length, + ); + } + } + + if (timeString) { + const token = patterns.timezone.exec(timeString); + if (token) { + dateStrings.time = timeString.replace(token[1], ""); + dateStrings.timezone = token[1]; + } else { + dateStrings.time = timeString; + } + } + + return dateStrings; +} + +function parseYear(dateString, additionalDigits) { + const regex = new RegExp( + "^(?:(\\d{4}|[+-]\\d{" + + (4 + additionalDigits) + + "})|(\\d{2}|[+-]\\d{" + + (2 + additionalDigits) + + "})$)", + ); + + const captures = dateString.match(regex); + // Invalid ISO-formatted year + if (!captures) return { year: NaN, restDateString: "" }; + + const year = captures[1] ? parseInt(captures[1]) : null; + const century = captures[2] ? parseInt(captures[2]) : null; + + // either year or century is null, not both + return { + year: century === null ? year : century * 100, + restDateString: dateString.slice((captures[1] || captures[2]).length), + }; +} + +function parseDate(dateString, year) { + // Invalid ISO-formatted year + if (year === null) return new Date(NaN); + + const captures = dateString.match(dateRegex); + // Invalid ISO-formatted string + if (!captures) return new Date(NaN); + + const isWeekDate = !!captures[4]; + const dayOfYear = parseDateUnit(captures[1]); + const month = parseDateUnit(captures[2]) - 1; + const day = parseDateUnit(captures[3]); + const week = parseDateUnit(captures[4]); + const dayOfWeek = parseDateUnit(captures[5]) - 1; + + if (isWeekDate) { + if (!validateWeekDate(year, week, dayOfWeek)) { + return new Date(NaN); + } + return dayOfISOWeekYear(year, week, dayOfWeek); + } else { + const date = new Date(0); + if ( + !validateDate(year, month, day) || + !validateDayOfYearDate(year, dayOfYear) + ) { + return new Date(NaN); + } + date.setUTCFullYear(year, month, Math.max(dayOfYear, day)); + return date; + } +} + +function parseDateUnit(value) { + return value ? parseInt(value) : 1; +} + +function parseTime(timeString) { + const captures = timeString.match(timeRegex); + if (!captures) return NaN; // Invalid ISO-formatted time + + const hours = parseTimeUnit(captures[1]); + const minutes = parseTimeUnit(captures[2]); + const seconds = parseTimeUnit(captures[3]); + + if (!validateTime(hours, minutes, seconds)) { + return NaN; + } + + return ( + hours * _index.millisecondsInHour + + minutes * _index.millisecondsInMinute + + seconds * 1000 + ); +} + +function parseTimeUnit(value) { + return (value && parseFloat(value.replace(",", "."))) || 0; +} + +function parseTimezone(timezoneString) { + if (timezoneString === "Z") return 0; + + const captures = timezoneString.match(timezoneRegex); + if (!captures) return 0; + + const sign = captures[1] === "+" ? -1 : 1; + const hours = parseInt(captures[2]); + const minutes = (captures[3] && parseInt(captures[3])) || 0; + + if (!validateTimezone(hours, minutes)) { + return NaN; + } + + return ( + sign * + (hours * _index.millisecondsInHour + minutes * _index.millisecondsInMinute) + ); +} + +function dayOfISOWeekYear(isoWeekYear, week, day) { + const date = new Date(0); + date.setUTCFullYear(isoWeekYear, 0, 4); + const fourthOfJanuaryDay = date.getUTCDay() || 7; + const diff = (week - 1) * 7 + day + 1 - fourthOfJanuaryDay; + date.setUTCDate(date.getUTCDate() + diff); + return date; +} + +// Validation functions + +// February is null to handle the leap year (using ||) +const daysInMonths = [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; + +function isLeapYearIndex(year) { + return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0); +} + +function validateDate(year, month, date) { + return ( + month >= 0 && + month <= 11 && + date >= 1 && + date <= (daysInMonths[month] || (isLeapYearIndex(year) ? 29 : 28)) + ); +} + +function validateDayOfYearDate(year, dayOfYear) { + return dayOfYear >= 1 && dayOfYear <= (isLeapYearIndex(year) ? 366 : 365); +} + +function validateWeekDate(_year, week, day) { + return week >= 1 && week <= 53 && day >= 0 && day <= 6; +} + +function validateTime(hours, minutes, seconds) { + if (hours === 24) { + return minutes === 0 && seconds === 0; + } + + return ( + seconds >= 0 && + seconds < 60 && + minutes >= 0 && + minutes < 60 && + hours >= 0 && + hours < 25 + ); +} + +function validateTimezone(_hours, minutes) { + return minutes >= 0 && minutes <= 59; +} + + +/***/ }), + +/***/ 50135: +/***/ ((__unused_webpack_module, exports) => { + +"use strict"; + +exports.parseJSON = parseJSON; /** + * @name parseJSON + * @category Common Helpers + * @summary Parse a JSON date string + * + * @description + * Converts a complete ISO date string in UTC time, the typical format for transmitting + * a date in JSON, to a JavaScript `Date` instance. + * + * This is a minimal implementation for converting dates retrieved from a JSON API to + * a `Date` instance which can be used with other functions in the `date-fns` library. + * The following formats are supported: + * + * - `2000-03-15T05:20:10.123Z`: The output of `.toISOString()` and `JSON.stringify(new Date())` + * - `2000-03-15T05:20:10Z`: Without milliseconds + * - `2000-03-15T05:20:10+00:00`: With a zero offset, the default JSON encoded format in some other languages + * - `2000-03-15T05:20:10+05:45`: With a positive or negative offset, the default JSON encoded format in some other languages + * - `2000-03-15T05:20:10+0000`: With a zero offset without a colon + * - `2000-03-15T05:20:10`: Without a trailing 'Z' symbol + * - `2000-03-15T05:20:10.1234567`: Up to 7 digits in milliseconds field. Only first 3 are taken into account since JS does not allow fractional milliseconds + * - `2000-03-15 05:20:10`: With a space instead of a 'T' separator for APIs returning a SQL date without reformatting + * + * For convenience and ease of use these other input types are also supported + * via [toDate](https://date-fns.org/docs/toDate): + * + * - A `Date` instance will be cloned + * - A `number` will be treated as a timestamp + * + * Any other input type or invalid date strings will return an `Invalid Date`. + * + * @param dateStr - A fully formed ISO8601 date string to convert + * + * @returns The parsed date in the local time zone + */ +function parseJSON(dateStr) { + const parts = dateStr.match( + /(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d{0,7}))?(?:Z|(.)(\d{2}):?(\d{2})?)?/, + ); + if (parts) { + // Group 8 matches the sign + return new Date( + Date.UTC( + +parts[1], + +parts[2] - 1, + +parts[3], + +parts[4] - (+parts[9] || 0) * (parts[8] == "-" ? -1 : 1), + +parts[5] - (+parts[10] || 0) * (parts[8] == "-" ? -1 : 1), + +parts[6], + +((parts[7] || "0") + "00").substring(0, 3), + ), + ); + } + return new Date(NaN); +} + + /***/ }), /***/ 5619: @@ -43795,378 +43982,6 @@ function isLeapYearIndex(year) { } -/***/ }), - -/***/ 93822: -/***/ ((__unused_webpack_module, exports, __nccwpck_require__) => { - -"use strict"; - -exports.parseISO = parseISO; -var _index = __nccwpck_require__(17818); - -/** - * The {@link parseISO} function options. - */ - -/** - * @name parseISO - * @category Common Helpers - * @summary Parse ISO string - * - * @description - * Parse the given string in ISO 8601 format and return an instance of Date. - * - * Function accepts complete ISO 8601 formats as well as partial implementations. - * ISO 8601: http://en.wikipedia.org/wiki/ISO_8601 - * - * If the argument isn't a string, the function cannot parse the string or - * the values are invalid, it returns Invalid Date. - * - * @typeParam DateType - The `Date` type, the function operates on. Gets inferred from passed arguments. Allows to use extensions like [`UTCDate`](https://github.com/date-fns/utc). - * - * @param argument - The value to convert - * @param options - An object with options - * - * @returns The parsed date in the local time zone - * - * @example - * // Convert string '2014-02-11T11:30:30' to date: - * const result = parseISO('2014-02-11T11:30:30') - * //=> Tue Feb 11 2014 11:30:30 - * - * @example - * // Convert string '+02014101' to date, - * // if the additional number of digits in the extended year format is 1: - * const result = parseISO('+02014101', { additionalDigits: 1 }) - * //=> Fri Apr 11 2014 00:00:00 - */ -function parseISO(argument, options) { - const additionalDigits = options?.additionalDigits ?? 2; - const dateStrings = splitDateString(argument); - - let date; - if (dateStrings.date) { - const parseYearResult = parseYear(dateStrings.date, additionalDigits); - date = parseDate(parseYearResult.restDateString, parseYearResult.year); - } - - if (!date || isNaN(date.getTime())) { - return new Date(NaN); - } - - const timestamp = date.getTime(); - let time = 0; - let offset; - - if (dateStrings.time) { - time = parseTime(dateStrings.time); - if (isNaN(time)) { - return new Date(NaN); - } - } - - if (dateStrings.timezone) { - offset = parseTimezone(dateStrings.timezone); - if (isNaN(offset)) { - return new Date(NaN); - } - } else { - const dirtyDate = new Date(timestamp + time); - // JS parsed string assuming it's in UTC timezone - // but we need it to be parsed in our timezone - // so we use utc values to build date in our timezone. - // Year values from 0 to 99 map to the years 1900 to 1999 - // so set year explicitly with setFullYear. - const result = new Date(0); - result.setFullYear( - dirtyDate.getUTCFullYear(), - dirtyDate.getUTCMonth(), - dirtyDate.getUTCDate(), - ); - result.setHours( - dirtyDate.getUTCHours(), - dirtyDate.getUTCMinutes(), - dirtyDate.getUTCSeconds(), - dirtyDate.getUTCMilliseconds(), - ); - return result; - } - - return new Date(timestamp + time + offset); -} - -const patterns = { - dateTimeDelimiter: /[T ]/, - timeZoneDelimiter: /[Z ]/i, - timezone: /([Z+-].*)$/, -}; - -const dateRegex = - /^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/; -const timeRegex = - /^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/; -const timezoneRegex = /^([+-])(\d{2})(?::?(\d{2}))?$/; - -function splitDateString(dateString) { - const dateStrings = {}; - const array = dateString.split(patterns.dateTimeDelimiter); - let timeString; - - // The regex match should only return at maximum two array elements. - // [date], [time], or [date, time]. - if (array.length > 2) { - return dateStrings; - } - - if (/:/.test(array[0])) { - timeString = array[0]; - } else { - dateStrings.date = array[0]; - timeString = array[1]; - if (patterns.timeZoneDelimiter.test(dateStrings.date)) { - dateStrings.date = dateString.split(patterns.timeZoneDelimiter)[0]; - timeString = dateString.substr( - dateStrings.date.length, - dateString.length, - ); - } - } - - if (timeString) { - const token = patterns.timezone.exec(timeString); - if (token) { - dateStrings.time = timeString.replace(token[1], ""); - dateStrings.timezone = token[1]; - } else { - dateStrings.time = timeString; - } - } - - return dateStrings; -} - -function parseYear(dateString, additionalDigits) { - const regex = new RegExp( - "^(?:(\\d{4}|[+-]\\d{" + - (4 + additionalDigits) + - "})|(\\d{2}|[+-]\\d{" + - (2 + additionalDigits) + - "})$)", - ); - - const captures = dateString.match(regex); - // Invalid ISO-formatted year - if (!captures) return { year: NaN, restDateString: "" }; - - const year = captures[1] ? parseInt(captures[1]) : null; - const century = captures[2] ? parseInt(captures[2]) : null; - - // either year or century is null, not both - return { - year: century === null ? year : century * 100, - restDateString: dateString.slice((captures[1] || captures[2]).length), - }; -} - -function parseDate(dateString, year) { - // Invalid ISO-formatted year - if (year === null) return new Date(NaN); - - const captures = dateString.match(dateRegex); - // Invalid ISO-formatted string - if (!captures) return new Date(NaN); - - const isWeekDate = !!captures[4]; - const dayOfYear = parseDateUnit(captures[1]); - const month = parseDateUnit(captures[2]) - 1; - const day = parseDateUnit(captures[3]); - const week = parseDateUnit(captures[4]); - const dayOfWeek = parseDateUnit(captures[5]) - 1; - - if (isWeekDate) { - if (!validateWeekDate(year, week, dayOfWeek)) { - return new Date(NaN); - } - return dayOfISOWeekYear(year, week, dayOfWeek); - } else { - const date = new Date(0); - if ( - !validateDate(year, month, day) || - !validateDayOfYearDate(year, dayOfYear) - ) { - return new Date(NaN); - } - date.setUTCFullYear(year, month, Math.max(dayOfYear, day)); - return date; - } -} - -function parseDateUnit(value) { - return value ? parseInt(value) : 1; -} - -function parseTime(timeString) { - const captures = timeString.match(timeRegex); - if (!captures) return NaN; // Invalid ISO-formatted time - - const hours = parseTimeUnit(captures[1]); - const minutes = parseTimeUnit(captures[2]); - const seconds = parseTimeUnit(captures[3]); - - if (!validateTime(hours, minutes, seconds)) { - return NaN; - } - - return ( - hours * _index.millisecondsInHour + - minutes * _index.millisecondsInMinute + - seconds * 1000 - ); -} - -function parseTimeUnit(value) { - return (value && parseFloat(value.replace(",", "."))) || 0; -} - -function parseTimezone(timezoneString) { - if (timezoneString === "Z") return 0; - - const captures = timezoneString.match(timezoneRegex); - if (!captures) return 0; - - const sign = captures[1] === "+" ? -1 : 1; - const hours = parseInt(captures[2]); - const minutes = (captures[3] && parseInt(captures[3])) || 0; - - if (!validateTimezone(hours, minutes)) { - return NaN; - } - - return ( - sign * - (hours * _index.millisecondsInHour + minutes * _index.millisecondsInMinute) - ); -} - -function dayOfISOWeekYear(isoWeekYear, week, day) { - const date = new Date(0); - date.setUTCFullYear(isoWeekYear, 0, 4); - const fourthOfJanuaryDay = date.getUTCDay() || 7; - const diff = (week - 1) * 7 + day + 1 - fourthOfJanuaryDay; - date.setUTCDate(date.getUTCDate() + diff); - return date; -} - -// Validation functions - -// February is null to handle the leap year (using ||) -const daysInMonths = [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; - -function isLeapYearIndex(year) { - return year % 400 === 0 || (year % 4 === 0 && year % 100 !== 0); -} - -function validateDate(year, month, date) { - return ( - month >= 0 && - month <= 11 && - date >= 1 && - date <= (daysInMonths[month] || (isLeapYearIndex(year) ? 29 : 28)) - ); -} - -function validateDayOfYearDate(year, dayOfYear) { - return dayOfYear >= 1 && dayOfYear <= (isLeapYearIndex(year) ? 366 : 365); -} - -function validateWeekDate(_year, week, day) { - return week >= 1 && week <= 53 && day >= 0 && day <= 6; -} - -function validateTime(hours, minutes, seconds) { - if (hours === 24) { - return minutes === 0 && seconds === 0; - } - - return ( - seconds >= 0 && - seconds < 60 && - minutes >= 0 && - minutes < 60 && - hours >= 0 && - hours < 25 - ); -} - -function validateTimezone(_hours, minutes) { - return minutes >= 0 && minutes <= 59; -} - - -/***/ }), - -/***/ 50135: -/***/ ((__unused_webpack_module, exports) => { - -"use strict"; - -exports.parseJSON = parseJSON; /** - * @name parseJSON - * @category Common Helpers - * @summary Parse a JSON date string - * - * @description - * Converts a complete ISO date string in UTC time, the typical format for transmitting - * a date in JSON, to a JavaScript `Date` instance. - * - * This is a minimal implementation for converting dates retrieved from a JSON API to - * a `Date` instance which can be used with other functions in the `date-fns` library. - * The following formats are supported: - * - * - `2000-03-15T05:20:10.123Z`: The output of `.toISOString()` and `JSON.stringify(new Date())` - * - `2000-03-15T05:20:10Z`: Without milliseconds - * - `2000-03-15T05:20:10+00:00`: With a zero offset, the default JSON encoded format in some other languages - * - `2000-03-15T05:20:10+05:45`: With a positive or negative offset, the default JSON encoded format in some other languages - * - `2000-03-15T05:20:10+0000`: With a zero offset without a colon - * - `2000-03-15T05:20:10`: Without a trailing 'Z' symbol - * - `2000-03-15T05:20:10.1234567`: Up to 7 digits in milliseconds field. Only first 3 are taken into account since JS does not allow fractional milliseconds - * - `2000-03-15 05:20:10`: With a space instead of a 'T' separator for APIs returning a SQL date without reformatting - * - * For convenience and ease of use these other input types are also supported - * via [toDate](https://date-fns.org/docs/toDate): - * - * - A `Date` instance will be cloned - * - A `number` will be treated as a timestamp - * - * Any other input type or invalid date strings will return an `Invalid Date`. - * - * @param dateStr - A fully formed ISO8601 date string to convert - * - * @returns The parsed date in the local time zone - */ -function parseJSON(dateStr) { - const parts = dateStr.match( - /(\d{4})-(\d{2})-(\d{2})[T ](\d{2}):(\d{2}):(\d{2})(?:\.(\d{0,7}))?(?:Z|(.)(\d{2}):?(\d{2})?)?/, - ); - if (parts) { - // Group 8 matches the sign - return new Date( - Date.UTC( - +parts[1], - +parts[2] - 1, - +parts[3], - +parts[4] - (+parts[9] || 0) * (parts[8] == "-" ? -1 : 1), - +parts[5] - (+parts[10] || 0) * (parts[8] == "-" ? -1 : 1), - +parts[6], - +((parts[7] || "0") + "00").substring(0, 3), - ), - ); - } - return new Date(NaN); -} - - /***/ }), /***/ 456: diff --git a/src/action/decorate/summary.ts b/src/action/decorate/summary.ts index 535c464d..e8d90922 100644 --- a/src/action/decorate/summary.ts +++ b/src/action/decorate/summary.ts @@ -278,8 +278,16 @@ function createBody(annotation: Annotation, displayCount: string) { body += `Blocking after: ${format(annotation.blocking.after, 'yyyy-MM-dd')}${EOL}`; } + const secondLine: string[] = []; + if (annotation.level) { + secondLine.push(`Level: ${annotation.level.toString()}`); + } + if (annotation.category) { + secondLine.push(`Category: ${annotation.category}`); + } + body += `Line: ${annotation.line.toString()}: ${displayCount}${annotation.msg}`; - body += `${EOL}Level: ${annotation.level.toString()}, Category: ${annotation.category}`; + body += secondLine.length > 0 ? `${EOL}${secondLine.join(', ')}` : ''; body += annotation.ruleHelp ? `${EOL}Rule-help: ${annotation.ruleHelp}` : ''; return body; @@ -385,7 +393,7 @@ export function createUnpostableAnnotationsDetails(unpostableReviewComments: Ext } else if (previousPath !== path) { body += ``; } - body += ``; + body += ``; previousPath = reviewComment.path ? reviewComment.path : ''; }); body += '
${path}
${icon}${blocking}Line: ${reviewComment.line.toString()} Level: ${reviewComment.level.toString()}
Category: ${reviewComment.category}
${reviewComment.type} violation: ${reviewComment.rule} ${displayCount}
${reviewComment.msg}
${icon}${blocking}Line: ${reviewComment.line.toString()} Level: ${reviewComment.level?.toString() ?? ''}
Category: ${reviewComment.category ?? ''}
${reviewComment.type} violation: ${reviewComment.rule} ${displayCount}
${reviewComment.msg}
'; diff --git a/src/helper/interfaces.d.ts b/src/helper/interfaces.d.ts index 8d10af12..e18a343a 100644 --- a/src/helper/interfaces.d.ts +++ b/src/helper/interfaces.d.ts @@ -151,15 +151,14 @@ export interface AnnotationType { export interface Annotation { fullPath: string; line: number; - level: number; - category: string; + level?: number; + category?: string; rule: string; msg: string; supp: boolean; type: string; - count: number; + count?: number; gateId?: number; - displayCount?: string; path?: string; diffLines?: number[]; ruleHelp?: string; @@ -171,6 +170,8 @@ export interface Annotation { } export interface ExtendedAnnotation extends Annotation { + count: number; + displayCount?: string; instanceName: string; } diff --git a/src/tics/analyzer.ts b/src/tics/analyzer.ts index 3ffce544..35268f26 100644 --- a/src/tics/analyzer.ts +++ b/src/tics/analyzer.ts @@ -97,19 +97,19 @@ async function buildRunCommand(fileListPath: string): Promise { * @param data stdout or stderr */ function findInStdOutOrErr(data: string): void { - const error = data.toString().match(/\[ERROR.*/g); - if (error && !errorList.find(e => e === error.toString())) { - errorList.push(error.toString()); + const error = data.match(/\[ERROR.*/g)?.toString(); + if (error && !errorList.find(e => e === error)) { + errorList.push(error); } - const warning = data.toString().match(/\[WARNING.*/g); - if (warning && !warningList.find(w => w === warning.toString())) { - warningList.push(warning.toString()); + const warning = data.match(/\[WARNING.*/g)?.toString(); + if (warning && !warningList.find(w => w === warning)) { + warningList.push(warning); } - const noFilesToAnalyze = data.toString().match(/No files to analyze with option '-changed':/g); + const noFilesToAnalyze = data.match(/No files to analyze with option '-changed':/g); if (noFilesToAnalyze) { - warningList.push(`[WARNING 5057] ${data.toString()}`); + warningList.push(`[WARNING 5057] ${data}`); } const findExplorerUrl = data.match(/\/Explorer.*/g); diff --git a/src/viewer/annotations.ts b/src/viewer/annotations.ts index 875e05e1..8eac7f21 100644 --- a/src/viewer/annotations.ts +++ b/src/viewer/annotations.ts @@ -32,6 +32,7 @@ export async function getAnnotations(apiLinks: AnnotationApiLink[]): Promise { const extendedAnnotation: ExtendedAnnotation = { ...annotation, + count: annotation.count ?? 1, instanceName: response.data.annotationTypes ? response.data.annotationTypes[annotation.type].instanceName : annotation.type }; extendedAnnotation.gateId = index; diff --git a/test/unit/viewer/annotations.test.ts b/test/unit/viewer/annotations.test.ts index 208a5b7f..e8241566 100644 --- a/test/unit/viewer/annotations.test.ts +++ b/test/unit/viewer/annotations.test.ts @@ -17,8 +17,8 @@ describe('getAnnotations', () => { const response = await getAnnotations([{ url: 'url?fields=default,blocking' }, { url: 'url' }]); expect(response).toEqual([ - { type: 'CS', gateId: 0, instanceName: 'CS' }, - { type: 'CS', gateId: 1, instanceName: 'Coding Standard Violations' } + { type: 'CS', gateId: 0, count: 1, instanceName: 'CS' }, + { type: 'CS', gateId: 1, count: 1, instanceName: 'Coding Standard Violations' } ]); }); From 2cffd43315d489f5979fb7385c51a21dc6e0086a Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Tue, 27 Aug 2024 15:29:17 +0200 Subject: [PATCH 2/5] #34796: Added case for when line is undefined (with notice) --- dist/index.js | 5 +++++ src/action/decorate/summary.ts | 2 +- src/helper/interfaces.d.ts | 3 ++- src/viewer/annotations.ts | 6 ++++++ test/unit/viewer/annotations.test.ts | 4 ++-- 5 files changed, 16 insertions(+), 4 deletions(-) diff --git a/dist/index.js b/dist/index.js index b04b360d..dd6f3fae 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2464,8 +2464,13 @@ async function getAnnotations(apiLinks) { try { const response = await http_client_1.httpClient.get(annotationsUrl.href); response.data.data.forEach((annotation) => { + if (!annotation.line) { + annotation.line = 1; + logger_1.logger.notice(`No line number reported for ${annotation.rule} in file ${annotation.fullPath}. Reporting the annotation on line 1.`); + } const extendedAnnotation = { ...annotation, + line: annotation.line, count: annotation.count ?? 1, instanceName: response.data.annotationTypes ? response.data.annotationTypes[annotation.type].instanceName : annotation.type }; diff --git a/src/action/decorate/summary.ts b/src/action/decorate/summary.ts index e8d90922..1792ee7e 100644 --- a/src/action/decorate/summary.ts +++ b/src/action/decorate/summary.ts @@ -270,7 +270,7 @@ export function createReviewComments(annotations: ExtendedAnnotation[], changedF return { postable: postable, unpostable: unpostable }; } -function createBody(annotation: Annotation, displayCount: string) { +function createBody(annotation: ExtendedAnnotation, displayCount: string) { let body = ''; if (annotation.blocking?.state === 'yes') { body += `Blocking${EOL}`; diff --git a/src/helper/interfaces.d.ts b/src/helper/interfaces.d.ts index e18a343a..77eb255b 100644 --- a/src/helper/interfaces.d.ts +++ b/src/helper/interfaces.d.ts @@ -150,7 +150,7 @@ export interface AnnotationType { export interface Annotation { fullPath: string; - line: number; + line?: number; level?: number; category?: string; rule: string; @@ -170,6 +170,7 @@ export interface Annotation { } export interface ExtendedAnnotation extends Annotation { + line: number; count: number; displayCount?: string; instanceName: string; diff --git a/src/viewer/annotations.ts b/src/viewer/annotations.ts index 8eac7f21..a0b4dfa8 100644 --- a/src/viewer/annotations.ts +++ b/src/viewer/annotations.ts @@ -30,8 +30,14 @@ export async function getAnnotations(apiLinks: AnnotationApiLink[]): Promise(annotationsUrl.href); response.data.data.forEach((annotation: Annotation) => { + if (!annotation.line) { + annotation.line = 1; + logger.notice(`No line number reported for ${annotation.rule} in file ${annotation.fullPath}. Reporting the annotation on line 1.`); + } + const extendedAnnotation: ExtendedAnnotation = { ...annotation, + line: annotation.line, count: annotation.count ?? 1, instanceName: response.data.annotationTypes ? response.data.annotationTypes[annotation.type].instanceName : annotation.type }; diff --git a/test/unit/viewer/annotations.test.ts b/test/unit/viewer/annotations.test.ts index e8241566..70ac6d7c 100644 --- a/test/unit/viewer/annotations.test.ts +++ b/test/unit/viewer/annotations.test.ts @@ -17,8 +17,8 @@ describe('getAnnotations', () => { const response = await getAnnotations([{ url: 'url?fields=default,blocking' }, { url: 'url' }]); expect(response).toEqual([ - { type: 'CS', gateId: 0, count: 1, instanceName: 'CS' }, - { type: 'CS', gateId: 1, count: 1, instanceName: 'Coding Standard Violations' } + { type: 'CS', gateId: 0, line: 1, count: 1, instanceName: 'CS' }, + { type: 'CS', gateId: 1, line: 1, count: 1, instanceName: 'Coding Standard Violations' } ]); }); From da01e24d0758977f566ec17b135c0d8d5ab904c5 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Tue, 27 Aug 2024 17:32:45 +0200 Subject: [PATCH 3/5] #34796: Fixed coding standards violation --- src/action/decorate/summary.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/action/decorate/summary.ts b/src/action/decorate/summary.ts index 1792ee7e..8a586552 100644 --- a/src/action/decorate/summary.ts +++ b/src/action/decorate/summary.ts @@ -10,7 +10,6 @@ import { logger } from '../../helper/logger'; import { joinUrl } from '../../helper/url'; import { AnalysisResult, - Annotation, Condition, ConditionDetails, ExtendedAnnotation, From 0f3348ceeb3b762a8f1edc3d97495d5b9b9c1684 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:43:34 +0200 Subject: [PATCH 4/5] #34794: Added fix for missing message in case of complexity Also removed displaying rule if undefined --- dist/index.js | 12 +++-- src/action/decorate/summary.ts | 5 +- src/helper/interfaces.d.ts | 5 +- src/viewer/annotations.ts | 8 ++- test/unit/action/decorate/summary.test.ts | 4 +- test/unit/viewer/annotations.test.ts | 60 +++++++++++++++++++---- 6 files changed, 76 insertions(+), 18 deletions(-) diff --git a/dist/index.js b/dist/index.js index dd6f3fae..d262921c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -382,11 +382,12 @@ function createReviewComments(annotations, changedFiles) { .filter(a => a.blocking?.state !== 'no') .forEach(annotation => { const displayCount = annotation.count === 1 ? '' : `(${annotation.count.toString()}x) `; + const title = annotation.instanceName + (annotation.rule ? `: ${annotation.rule}` : ''); if (config_1.githubConfig.event.isPullRequest) { if (changedFiles.find(c => annotation.fullPath.includes(c.filename))) { const reviewComment = { blocking: annotation.blocking?.state, - title: `${annotation.instanceName}: ${annotation.rule}`, + title: title, body: createBody(annotation, displayCount), path: annotation.path, line: annotation.line @@ -403,7 +404,7 @@ function createReviewComments(annotations, changedFiles) { else if (annotation.diffLines?.includes(annotation.line)) { const reviewComment = { blocking: annotation.blocking?.state, - title: `${annotation.instanceName}: ${annotation.rule}`, + title: title, body: createBody(annotation, displayCount), path: annotation.path, line: annotation.line @@ -2468,13 +2469,18 @@ async function getAnnotations(apiLinks) { annotation.line = 1; logger_1.logger.notice(`No line number reported for ${annotation.rule} in file ${annotation.fullPath}. Reporting the annotation on line 1.`); } + // In case complexity is given, the annotation does not have a message (should be fixed in newer Viewers ). + // Present in Viewers <= 2024.2.0 + if (annotation.complexity && !annotation.msg) { + annotation.msg = `Function ${annotation.functionName} has a complexity of ${annotation.complexity.toString()}`; + } const extendedAnnotation = { ...annotation, + gateId: index, line: annotation.line, count: annotation.count ?? 1, instanceName: response.data.annotationTypes ? response.data.annotationTypes[annotation.type].instanceName : annotation.type }; - extendedAnnotation.gateId = index; logger_1.logger.debug(JSON.stringify(extendedAnnotation)); annotations.push(extendedAnnotation); }); diff --git a/src/action/decorate/summary.ts b/src/action/decorate/summary.ts index 8a586552..fd0eb7c5 100644 --- a/src/action/decorate/summary.ts +++ b/src/action/decorate/summary.ts @@ -232,12 +232,13 @@ export function createReviewComments(annotations: ExtendedAnnotation[], changedF .filter(a => a.blocking?.state !== 'no') .forEach(annotation => { const displayCount = annotation.count === 1 ? '' : `(${annotation.count.toString()}x) `; + const title = annotation.instanceName + (annotation.rule ? `: ${annotation.rule}` : ''); if (githubConfig.event.isPullRequest) { if (changedFiles.find(c => annotation.fullPath.includes(c.filename))) { const reviewComment = { blocking: annotation.blocking?.state, - title: `${annotation.instanceName}: ${annotation.rule}`, + title: title, body: createBody(annotation, displayCount), path: annotation.path, line: annotation.line @@ -252,7 +253,7 @@ export function createReviewComments(annotations: ExtendedAnnotation[], changedF } else if (annotation.diffLines?.includes(annotation.line)) { const reviewComment = { blocking: annotation.blocking?.state, - title: `${annotation.instanceName}: ${annotation.rule}`, + title: title, body: createBody(annotation, displayCount), path: annotation.path, line: annotation.line diff --git a/src/helper/interfaces.d.ts b/src/helper/interfaces.d.ts index 77eb255b..958a2017 100644 --- a/src/helper/interfaces.d.ts +++ b/src/helper/interfaces.d.ts @@ -153,7 +153,7 @@ export interface Annotation { line?: number; level?: number; category?: string; - rule: string; + rule?: string; msg: string; supp: boolean; type: string; @@ -167,9 +167,12 @@ export interface Annotation { state: 'yes' | 'no' | 'after'; after?: number; }; + complexity?: number; + functionName?: string; } export interface ExtendedAnnotation extends Annotation { + msg: string; line: number; count: number; displayCount?: string; diff --git a/src/viewer/annotations.ts b/src/viewer/annotations.ts index a0b4dfa8..b0e41fba 100644 --- a/src/viewer/annotations.ts +++ b/src/viewer/annotations.ts @@ -35,13 +35,19 @@ export async function getAnnotations(apiLinks: AnnotationApiLink[]): Promise { } }, { + // testing one without a rule fullPath: 'c:/src/test.js', line: 0, level: 1, category: 'test', type: 'test', - rule: 'rule-after', msg: 'message-after', count: 1, supp: false, @@ -338,7 +338,7 @@ describe('createReviewComments', () => { }, { blocking: 'after', - title: 'test: rule-after', + title: 'test', path: 'src/test.js', line: 0, body: `Blocking after: 2024-02-19${EOL}Line: 0: message-after${EOL}Level: 1, Category: test` diff --git a/test/unit/viewer/annotations.test.ts b/test/unit/viewer/annotations.test.ts index 70ac6d7c..d5eb45c6 100644 --- a/test/unit/viewer/annotations.test.ts +++ b/test/unit/viewer/annotations.test.ts @@ -5,24 +5,66 @@ import { ticsConfigMock } from '../../.setup/mock'; describe('getAnnotations', () => { ticsConfigMock.baseUrl = 'http://base.url'; - test('Should return analyzed files from viewer', async () => { + test('Should return annotations from viewer', async () => { jest.spyOn(httpClient, 'get').mockImplementationOnce((): Promise => Promise.resolve({ data: { data: [{ type: 'CS' }] } })); - jest - .spyOn(httpClient, 'get') - .mockImplementationOnce( - (): Promise => - Promise.resolve({ data: { data: [{ type: 'CS' }], annotationTypes: { CS: { instanceName: 'Coding Standard Violations' } } } }) - ); + jest.spyOn(httpClient, 'get').mockImplementationOnce( + (): Promise => + Promise.resolve({ + data: { + data: [{ type: 'CS', line: 10, count: 2 }], + annotationTypes: { CS: { instanceName: 'Coding Standard Violations' } } + } + }) + ); const response = await getAnnotations([{ url: 'url?fields=default,blocking' }, { url: 'url' }]); expect(response).toEqual([ { type: 'CS', gateId: 0, line: 1, count: 1, instanceName: 'CS' }, - { type: 'CS', gateId: 1, line: 1, count: 1, instanceName: 'Coding Standard Violations' } + { type: 'CS', gateId: 1, line: 10, count: 2, instanceName: 'Coding Standard Violations' } ]); }); - test('Should return no analyzed files when no urls are given', async () => { + test('Should return complexity annotations from the viewer', async () => { + jest.spyOn(httpClient, 'get').mockImplementationOnce( + (): Promise => + Promise.resolve({ + data: { + data: [ + { type: 'COMPLEXITY', line: 10, complexity: 3, functionName: 'main' }, + { type: 'COMPLEXITY', line: 2, complexity: 2, functionName: 'test', msg: 'testing' } + ] + } + }) + ); + + const response = await getAnnotations([{ url: 'url' }]); + + expect(response).toEqual([ + { + type: 'COMPLEXITY', + complexity: 3, + functionName: 'main', + gateId: 0, + line: 10, + count: 1, + instanceName: 'COMPLEXITY', + msg: 'Function main has a complexity of 3' + }, + { + type: 'COMPLEXITY', + complexity: 2, + functionName: 'test', + gateId: 0, + line: 2, + count: 1, + instanceName: 'COMPLEXITY', + msg: 'testing' + } + ]); + }); + + test('Should return no annotations when no urls are given', async () => { const response = await getAnnotations([]); expect(response).toEqual([]); From 17368cd3e8834a088ca97313c5e1025337c30f97 Mon Sep 17 00:00:00 2001 From: janssen <118828444+janssen-tiobe@users.noreply.github.com> Date: Wed, 28 Aug 2024 10:59:31 +0200 Subject: [PATCH 5/5] #34794: Added ticket number for message fix, qg fixes --- dist/index.js | 9 +++++---- src/action/decorate/summary.ts | 2 +- src/viewer/annotations.ts | 9 ++++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/dist/index.js b/dist/index.js index d262921c..888344a5 100644 --- a/dist/index.js +++ b/dist/index.js @@ -533,7 +533,7 @@ function createUnpostableAnnotationsDetails(unpostableReviewComments) { else if (previousPath !== path) { body += ``; } - body += ``; + body += ``; previousPath = reviewComment.path ? reviewComment.path : ''; }); body += '
${path}
${icon}${blocking}Line: ${reviewComment.line.toString()} Level: ${reviewComment.level?.toString() ?? ''}
Category: ${reviewComment.category ?? ''}
${reviewComment.type} violation: ${reviewComment.rule} ${displayCount}
${reviewComment.msg}
${icon}${blocking}Line: ${reviewComment.line.toString()} Level: ${reviewComment.level?.toString() ?? ''}
Category: ${reviewComment.category ?? ''}
${reviewComment.type} violation: ${reviewComment.rule ?? ''} ${displayCount}
${reviewComment.msg}
'; @@ -2467,12 +2467,13 @@ async function getAnnotations(apiLinks) { response.data.data.forEach((annotation) => { if (!annotation.line) { annotation.line = 1; - logger_1.logger.notice(`No line number reported for ${annotation.rule} in file ${annotation.fullPath}. Reporting the annotation on line 1.`); + const rule = annotation.rule ? ` ${annotation.rule} ` : ' '; + logger_1.logger.notice(`No line number reported for ${annotation.type} violation${rule}in file ${annotation.fullPath}. Reporting the annotation on line 1.`); } - // In case complexity is given, the annotation does not have a message (should be fixed in newer Viewers ). + // In case complexity is given, the annotation does not have a message (should be fixed in newer Viewers #34866). // Present in Viewers <= 2024.2.0 if (annotation.complexity && !annotation.msg) { - annotation.msg = `Function ${annotation.functionName} has a complexity of ${annotation.complexity.toString()}`; + annotation.msg = `Function ${annotation.functionName ?? ''} has a complexity of ${annotation.complexity.toString()}`; } const extendedAnnotation = { ...annotation, diff --git a/src/action/decorate/summary.ts b/src/action/decorate/summary.ts index fd0eb7c5..95ba7181 100644 --- a/src/action/decorate/summary.ts +++ b/src/action/decorate/summary.ts @@ -393,7 +393,7 @@ export function createUnpostableAnnotationsDetails(unpostableReviewComments: Ext } else if (previousPath !== path) { body += ``; } - body += ``; + body += ``; previousPath = reviewComment.path ? reviewComment.path : ''; }); body += '
${path}
${icon}${blocking}Line: ${reviewComment.line.toString()} Level: ${reviewComment.level?.toString() ?? ''}
Category: ${reviewComment.category ?? ''}
${reviewComment.type} violation: ${reviewComment.rule} ${displayCount}
${reviewComment.msg}
${icon}${blocking}Line: ${reviewComment.line.toString()} Level: ${reviewComment.level?.toString() ?? ''}
Category: ${reviewComment.category ?? ''}
${reviewComment.type} violation: ${reviewComment.rule ?? ''} ${displayCount}
${reviewComment.msg}
'; diff --git a/src/viewer/annotations.ts b/src/viewer/annotations.ts index b0e41fba..81c72aa3 100644 --- a/src/viewer/annotations.ts +++ b/src/viewer/annotations.ts @@ -32,13 +32,16 @@ export async function getAnnotations(apiLinks: AnnotationApiLink[]): Promise { if (!annotation.line) { annotation.line = 1; - logger.notice(`No line number reported for ${annotation.rule} in file ${annotation.fullPath}. Reporting the annotation on line 1.`); + const rule = annotation.rule ? ` ${annotation.rule} ` : ' '; + logger.notice( + `No line number reported for ${annotation.type} violation${rule}in file ${annotation.fullPath}. Reporting the annotation on line 1.` + ); } - // In case complexity is given, the annotation does not have a message (should be fixed in newer Viewers ). + // In case complexity is given, the annotation does not have a message (should be fixed in newer Viewers #34866). // Present in Viewers <= 2024.2.0 if (annotation.complexity && !annotation.msg) { - annotation.msg = `Function ${annotation.functionName} has a complexity of ${annotation.complexity.toString()}`; + annotation.msg = `Function ${annotation.functionName ?? ''} has a complexity of ${annotation.complexity.toString()}`; } const extendedAnnotation: ExtendedAnnotation = {