Регулярное выражение для ключевого слова TODO при прохождении списка каталогов для получения списка файлов с ключевым словом TODO (например, //TODO), но не в виде переменной/строки

Я пытаюсь написать приложение, которое просматривает каталог и помечает все файлы (будь то в каталоге или подкаталогах), которые имеют ключевое слово TODO (тот, который мигает/подсвечивается цветом всякий раз, когда мы кодируем в нашем редакторе кода [я используя код визуальной студии]

Я запустил большую часть кода, это только последний бит, который меня озадачивает: поскольку мой RegEx принимает «TODO» как блок слов, он подбирает даже файлы, которые имеют TODO в качестве имени переменной/содержимого строки, например.

var todo = 'TODO' or var TODO = 'abcdefg'

так что это портит мои тестовые примеры. Как нам написать надежное регулярное выражение/выражение TODO, которое может подобрать только ключевое слово TODO (например, //TODO или // TODO) и игнорировать другие варианты использования (в переменных/строках и т. д.) Я не хочу жестко кодировать // или что-либо в также регулярное выражение, так как я бы предпочел, чтобы оно было как можно более кросс-языковым (например, // (однострочный) или /* (многострочный) для javascript, # для python и т. д.)

Вот мой код:

import * as fs from 'fs'; 
import * as path from 'path';

const args = process.argv.slice(2);
const directory = args[0];

// Using recursion, we find every file with the desired extention, even if its deeply nested in subfolders.
// Returns a list of file paths
const getFilesInDirectory = (dir, ext) => {
  if (!fs.existsSync(dir)) {
    console.log(`Specified directory: ${dir} does not exist`);
    return;
  }

  let files = [];
  fs.readdirSync(dir).forEach(file => {
    const filePath = path.join(dir, file);
    const stat = fs.lstatSync(filePath); // Getting details of a symbolic link of file

    // If we hit a directory, recurse our fx to subdir. If we hit a file (basecase), add it to the array of files
    if (stat.isDirectory()) {
      const nestedFiles = getFilesInDirectory(filePath, ext);
      files = files.concat(nestedFiles);
    } else {
      if (path.extname(file) === ext) {
        files.push(filePath);
      }
    }
  });

  return files;
};



const checkFilesWithKeyword = (dir, keyword, ext) => {
  if (!fs.existsSync(dir)) {
    console.log(`Specified directory: ${dir} does not exist`);
    return;
  }

  const allFiles = getFilesInDirectory(dir, ext);
  const checkedFiles = [];

  allFiles.forEach(file => {
    const fileContent = fs.readFileSync(file);

    // We want full words, so we use full word boundary in regex.
    const regex = new RegExp('\\b' + keyword + '\\b');
    if (regex.test(fileContent)) {
      // console.log(`Your word was found in file: ${file}`);
      checkedFiles.push(file);
    }
  });

  console.log(checkedFiles);
  return checkedFiles;
};

checkFilesWithKeyword(directory, 'TODO', '.js');



Помощь приветствуется!!


person Isabella Chan    schedule 06.02.2021    source источник


Ответы (1)


Я не думаю, что есть надежный способ исключить TODO в именах переменных или строковых значениях на разных языках. Вам нужно будет правильно проанализировать каждый язык и найти TODO в комментариях.

Вы можете сделать приближение, которое вы можете настроить с течением времени:

  • для имен переменных вам нужно исключить TODO = присвоения и любой тип использования, например TODO.length
  • для строкового значения вы можете исключить 'TODO' и "TODO" и даже "Something TODO today" при поиске совпадающих кавычек. Как насчет многострочной строки с обратными кавычками?

Это начало использования набора негативных прогнозов:

const input = `Test Case:
// TODO blah
// TODO do "stuff"
/* stuff
 * TODO
 */
let a = 'TODO';
let b = 'Something TODO today';
let c = "TODO";
let d = "More stuff TODO today";
let TODO = 'stuff';
let l = TODO.length;
let e = "Even more " + TODO + " to do today";
let f = 'Nothing to do';
`;
let keyword = 'TODO';
const regex = new RegExp(
  // exclude TODO in string value with matching quotes:
  '^(?!.*([\'"]).*\\b' + keyword + '\\b.*\\1)' +
  // exclude TODO.property access:
  '(?!.*\\b' + keyword + '\\.\\w)' +
  // exclude TODO = assignment
  '(?!.*\\b' + keyword + '\\s*=)' +
  // final TODO match
  '.*\\b' + keyword + '\\b'
);
input.split('\n').forEach((line) => {
  let m = regex.test(line);
  console.log(m + ': ' + line);
});

Выход:

false: Test Case:
true: // TODO blah
true: // TODO do "stuff"
false: /* stuff
true:  * TODO
false:  */
false: let a = 'TODO';
false: let b = 'Something TODO today';
false: let c = "TODO";
false: let d = "More stuff TODO today";
false: let TODO = 'stuff';
false: let l = TODO.length;
false: let e = "Even more " + TODO + " to do today";
false: let f = 'Nothing to do';
false: 

Объяснение состава регулярного выражения:

  • ^ - начало строки (в нашем случае начало строки из-за разделения)
  • exclude TODO in string value with matching quotes:
    • (?! - negative lookahead start
    • .* - жадное сканирование (сканирует все символы, но все еще соответствует тому, что следует)
    • (['"]) - группа захвата для одинарной или двойной кавычки
    • .* - жадное сканирование
    • \b - слово ранит перед ключевым словом (ожидайте, что ключевое слово заключено в символы, не являющиеся словами)
    • добавьте сюда ключевое слово
    • \b - слово ранит после ключевого слова
    • .* - жадное сканирование
    • \1 — обратная ссылка на группу захвата (либо одинарная, либо двойная кавычка, но та, что захвачена выше)
    • ) - конец отрицательного прогноза
  • exclude TODO.property access:
    • (?! - negative lookahead start
    • .* - жадное сканирование
    • \b - слово ранит перед ключевым словом
    • добавьте сюда ключевое слово
    • \.\w - точка, за которой следует слово char, например .x
    • ) - конец отрицательного прогноза
  • exclude TODO = assignment
    • (?! - negative lookahead start
    • .* - жадное сканирование
    • \b - слово ранит перед ключевым словом
    • добавьте сюда ключевое слово
    • \s*= - необязательные пробелы, за которыми следует =
    • ) - конец отрицательного прогноза
  • final TODO match
    • .* - greedy scan
    • \b - слово ранение (ожидайте, что ключевое слово заключено в символы, не являющиеся словами)
    • добавьте сюда ключевое слово
    • \b - слово ранимый

Узнайте больше о регулярных выражениях: https://twiki.org/cgi-bin/view/Codev/TWikiPresentation2018x10x14Regex

person Peter Thoeny    schedule 06.02.2021
comment
@Isabella Chan: Это ответ на твой вопрос? Любые вопросы? - person Peter Thoeny; 08.02.2021
comment
Да, это очень помогает, я вроде понимаю, о чем вы говорите, это приближение по-прежнему будет выполнять большую часть работы, но оно не решит ее полностью! Я просто трачу некоторое время, чтобы понять выражения регулярных выражений, которые вы дали, так как я только баловался с основными выражениями регулярных выражений, а не с прогнозами +ve/-ve... могу я спросить, не могли бы вы объяснить в деталях для каждого из выражений? - person Isabella Chan; 14.02.2021
comment
особенно '1' в конце 1-го выражения, это меня немного смущает - person Isabella Chan; 14.02.2021
comment
Я добавил пояснение к конструкции регулярного выражения. Дополнительные вопросы? Хотите принять и проголосовать за ответ? - person Peter Thoeny; 15.02.2021
comment
Помощь приветствуется!! Это работает в обоих направлениях. - person Peter Thoeny; 25.02.2021
comment
@Isabella Chan: Смотрите галочку рядом с ответом, спасибо - person Peter Thoeny; 05.03.2021