Анализ JavaScript

В ближайшее время в инструмент Svace будет добавлена первая версия поддержки языка JavaScript.

В данной версии Svace реализует анализ JavaScript-программ на основе абстрактного синтаксического дерева, выполненном в компоненте UAST.

По аналогии с языком Python, анализ которого поддерживается в Svace, для программ на JavaScript компиляция не требуется. Поэтому на этапе сборки Svace сканирует директорию с исходным кодом, ищет файлы с расширением *.js и строит для них промежуточное представление.

Svace поддерживает анализ JavaScript-программ, реализованных в соответствии с ES6. Также возможен частичный анализ файлов, использующих JSX. Анализ TypeScript и прочих расширений JavaScript не поддерживается.

Запуск

Как и для других языков, для запуска анализа JavaScript необходимо инициализировать Svace, запустить сборку проекта и анализ. В команде сборки нужно указать ключевое слово --javascript и путь до файла или директории, содержащей файлы с исходным кодом:

svace init
svace build --javascript ${FILE_OR_DIRECTORY}
svace analyze

Детекторы

В настоящий момент для языка JavaScript поддерживаются следующие 18 детекторов:

Ограничения

На данном этапе отсутствует подсветка синтаксиса в сервере истории.

Примеры обнаруживаемых дефектов

В коде проекта ESLint v9.0.0 в lib/rules/key-spacing.js был обнаружен дефект BAD_COPY_PASTE (см. код ниже).

Использование alignmentOptions.mode внутри else должно быть заменено на multiLineOptions.mode. В ином случае, при достижении данной строки возникнет TypeError, т.к. alignmentOptions будет иметь значение null.

function verifyGroupAlignment(properties) {
    const length = properties.length,
        widths = properties.map(getKeyWidth),
        align = alignmentOptions.on;
    let targetWidth = Math.max(...widths),
        beforeColon, afterColon, mode;
    if (alignmentOptions && length > 1) {
        beforeColon = alignmentOptions.beforeColon;
        afterColon = alignmentOptions.afterColon;
        mode = alignmentOptions.mode;
    } else {
        beforeColon = multiLineOptions.beforeColon;
        afterColon = multiLineOptions.afterColon;
        mode = alignmentOptions.mode; // BAD_COPY_PASTE: 'alignmentOptions' might be 'multiLineOptions'
    }

    ...
}

В коде проекта ESLint v9.0.0 в lib/eslint/legacy-eslint.js был обнаружен дефект INAPPROPRIATE_FOR_IN (см. код ниже).

Использование цикла for ... in в данном случае бессмысленно, т.к. используются только значения свойств объекта, но не ключи. Стоит заменить данный цикл на for ... of.

function hasDefinedProperty(obj) {
    if (typeof obj === "object" && obj !== null) {
        for (const key in obj) { // INAPPROPRIATE_FOR_IN: 'for ... in' can be replaced with 'for ... of'
            if (typeof obj[key] !== "undefined") {
                return true;
            }
        }
    }
    return false;
}

В коде проекта Ghost v5.81.0 в ghost/members-api/lib/repositories/ProductRepository.js был обнаружен дефект BAD_COPY_PASTE (см. код ниже).

Судя по логике кода, использование data.monthly_price во втором условии должно быть заменено data.yearly_price.

async create(data, options = {}) {

    ...

    if (data.monthly_price) {
        validatePrice(data.monthly_price);
    }
    if (data.yearly_price) {
        validatePrice(data.monthly_price); // BAD_COPY_PASTE: 'monthly_price' might be 'yearly_price'
    }

    ...
}

В коде проекта video.js v8.11.8 в docs/legacy-docs/js/guides.js был обнаружен дефект INCORRECT_NAN_COMPARE (см. код ниже).

В соответствии cо стандартом IEEE 754, проверка x === NaN всегда будет возвращать false, даже если x принимает значение NaN. Необходимо заменить данную проверку на вызов встроенной функции isNaN(x).

function isDefined (x){
    if ( x === "" || x === null || x === undefined || x === NaN) { // INCORRECT_NAN_COMPARE
        return false;
    }
    return true;
}

В коде проекта TheAlgorithms/JavaScript в Timing-Functions/IntervalTimer.js был обнаружен дефект CONSTRUCTOR_WITH_RETURN (см. код ниже).

В соответствии с комментарием, данный класс должен быть singleton’ом. Но использование оператора return в конструкторе в данном случае никак не влияет на то, что будет из него возвращено - вызов new IntervalTimer() всегда будет возвращать новый объект.

Обсуждение данного бага в реализации можно найти в комментарии на GitHub.

/**
 * @description Singleton class that handles the timing of tests and specs.
 */
class IntervalTimer {
  /**
   * @description Constructor for Timer.
   * @param interval Sets the interval for running the timer.
   * @param callBack The callback function to be executed.
   * @return {IntervalTimer} If exists, the existing object.
   */
  constructor(interval = 10, callBack = () => {}) {
    this.prevInterval = 0
    if (this.instance == null) {
      this.interval = interval
      this.callBack = callBack
      this.instance = this
    } else {
      return this.instance // CONSTRUCTOR_WITH_RETURN
    }
  }
}

Виталий Афанасьев