File indexing completed on 2024-04-21 03:57:15

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2010 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "regexpsearch_test.h"
0009 #include "moc_regexpsearch_test.cpp"
0010 
0011 #include <katedocument.h>
0012 #include <kateglobal.h>
0013 #include <kateregexpsearch.h>
0014 
0015 #include <QRegularExpression>
0016 
0017 #include <QTest>
0018 
0019 QTEST_MAIN(RegExpSearchTest)
0020 
0021 #define testNewRow() (QTest::newRow(QStringLiteral("line %1").arg(__LINE__).toLatin1().data()))
0022 
0023 using namespace KTextEditor;
0024 
0025 RegExpSearchTest::RegExpSearchTest()
0026     : QObject()
0027 {
0028     KTextEditor::EditorPrivate::enableUnitTestMode();
0029 }
0030 
0031 RegExpSearchTest::~RegExpSearchTest()
0032 {
0033 }
0034 
0035 void RegExpSearchTest::testReplaceEscapeSequences_data()
0036 {
0037     QTest::addColumn<QString>("pattern");
0038     QTest::addColumn<QString>("expected");
0039 
0040     testNewRow() << QStringLiteral("\\") << QStringLiteral("\\");
0041     testNewRow() << QStringLiteral("\\0") << QStringLiteral("0");
0042     testNewRow() << QStringLiteral("\\00") << QStringLiteral("00");
0043     testNewRow() << QStringLiteral("\\000") << QStringLiteral("000");
0044     testNewRow() << QStringLiteral("\\0000") << QString(QChar(0));
0045     testNewRow() << QStringLiteral("\\0377") << QString(QChar(0377));
0046     testNewRow() << QStringLiteral("\\0378") << QStringLiteral("0378");
0047     testNewRow() << QStringLiteral("\\a") << QStringLiteral("\a");
0048     testNewRow() << QStringLiteral("\\f") << QStringLiteral("\f");
0049     testNewRow() << QStringLiteral("\\n") << QStringLiteral("\n");
0050     testNewRow() << QStringLiteral("\\r") << QStringLiteral("\r");
0051     testNewRow() << QStringLiteral("\\t") << QStringLiteral("\t");
0052     testNewRow() << QStringLiteral("\\v") << QStringLiteral("\v");
0053     testNewRow() << QStringLiteral("\\x") << QStringLiteral("x");
0054     testNewRow() << QStringLiteral("\\x0") << QStringLiteral("x0");
0055     testNewRow() << QStringLiteral("\\x00") << QStringLiteral("x00");
0056     testNewRow() << QStringLiteral("\\x000") << QStringLiteral("x000");
0057     testNewRow() << QStringLiteral("\\x0000") << QString(QChar(0x0000));
0058     testNewRow() << QStringLiteral("\\x00000") << QString(QChar(0x0000) + QLatin1Char('0'));
0059     testNewRow() << QStringLiteral("\\xaaaa") << QString(QChar(0xaaaa));
0060     testNewRow() << QStringLiteral("\\xFFFF") << QString(QChar(0xFFFF));
0061     testNewRow() << QStringLiteral("\\xFFFg") << QStringLiteral("xFFFg");
0062 }
0063 
0064 void RegExpSearchTest::testReplaceEscapeSequences()
0065 {
0066     QFETCH(QString, pattern);
0067     QFETCH(QString, expected);
0068 
0069     const QString result1 = KateRegExpSearch::escapePlaintext(pattern);
0070     const QString result2 = KateRegExpSearch::buildReplacement(pattern, QStringList(), 0);
0071 
0072     QCOMPARE(result1, expected);
0073     QCOMPARE(result2, expected);
0074 }
0075 
0076 void RegExpSearchTest::testReplacementReferences_data()
0077 {
0078     QTest::addColumn<QString>("pattern");
0079     QTest::addColumn<QString>("expected");
0080     QTest::addColumn<QStringList>("capturedTexts");
0081 
0082     testNewRow() << QStringLiteral("\\0") << QStringLiteral("b") << (QStringList() << QStringLiteral("b"));
0083     testNewRow() << QStringLiteral("\\00") << QStringLiteral("b0") << (QStringList() << QStringLiteral("b"));
0084     testNewRow() << QStringLiteral("\\000") << QStringLiteral("b00") << (QStringList() << QStringLiteral("b"));
0085     testNewRow() << QStringLiteral("\\0000") << QString(QChar(0)) << (QStringList() << QStringLiteral("b"));
0086     testNewRow() << QStringLiteral("\\1") << QStringLiteral("1") << (QStringList() << QStringLiteral("b"));
0087     testNewRow() << QStringLiteral("\\0") << QStringLiteral("b") << (QStringList() << QStringLiteral("b") << QStringLiteral("c"));
0088     testNewRow() << QStringLiteral("\\1") << QStringLiteral("c") << (QStringList() << QStringLiteral("b") << QStringLiteral("c"));
0089 }
0090 
0091 void RegExpSearchTest::testReplacementReferences()
0092 {
0093     QFETCH(QString, pattern);
0094     QFETCH(QString, expected);
0095     QFETCH(QStringList, capturedTexts);
0096 
0097     const QString result = KateRegExpSearch::buildReplacement(pattern, capturedTexts, 1);
0098 
0099     QCOMPARE(result, expected);
0100 }
0101 
0102 void RegExpSearchTest::testReplacementCaseConversion_data()
0103 {
0104     QTest::addColumn<QString>("pattern");
0105     QTest::addColumn<QString>("expected");
0106 
0107     testNewRow() << QStringLiteral("a\\Uaa") << QStringLiteral("aAA");
0108     testNewRow() << QStringLiteral("a\\UAa") << QStringLiteral("aAA");
0109     testNewRow() << QStringLiteral("a\\UaA") << QStringLiteral("aAA");
0110 
0111     testNewRow() << QStringLiteral("a\\Uáa") << QStringLiteral("aÁA");
0112     testNewRow() << QStringLiteral("a\\UAá") << QStringLiteral("aAÁ");
0113     testNewRow() << QStringLiteral("a\\UaÁ") << QStringLiteral("aAÁ");
0114 
0115     testNewRow() << QStringLiteral("a\\uaa") << QStringLiteral("aAa");
0116     testNewRow() << QStringLiteral("a\\uAa") << QStringLiteral("aAa");
0117     testNewRow() << QStringLiteral("a\\uaA") << QStringLiteral("aAA");
0118 
0119     testNewRow() << QStringLiteral("a\\uáa") << QStringLiteral("aÁa");
0120     testNewRow() << QStringLiteral("a\\uÁa") << QStringLiteral("aÁa");
0121     testNewRow() << QStringLiteral("a\\uáA") << QStringLiteral("aÁA");
0122 
0123     testNewRow() << QStringLiteral("A\\LAA") << QStringLiteral("Aaa");
0124     testNewRow() << QStringLiteral("A\\LaA") << QStringLiteral("Aaa");
0125     testNewRow() << QStringLiteral("A\\LAa") << QStringLiteral("Aaa");
0126 
0127     testNewRow() << QStringLiteral("A\\LÁA") << QStringLiteral("Aáa");
0128     testNewRow() << QStringLiteral("A\\LaÁ") << QStringLiteral("Aaá");
0129     testNewRow() << QStringLiteral("A\\LÁa") << QStringLiteral("Aáa");
0130 
0131     testNewRow() << QStringLiteral("A\\lAA") << QStringLiteral("AaA");
0132     testNewRow() << QStringLiteral("A\\lAa") << QStringLiteral("Aaa");
0133     testNewRow() << QStringLiteral("A\\laA") << QStringLiteral("AaA");
0134 
0135     testNewRow() << QStringLiteral("A\\lÁA") << QStringLiteral("AáA");
0136     testNewRow() << QStringLiteral("A\\lÁa") << QStringLiteral("Aáa");
0137     testNewRow() << QStringLiteral("A\\láA") << QStringLiteral("AáA");
0138 
0139     testNewRow() << QStringLiteral("a\\Ubb\\EaA") << QStringLiteral("aBBaA");
0140     testNewRow() << QStringLiteral("A\\LBB\\EAa") << QStringLiteral("AbbAa");
0141 
0142     testNewRow() << QStringLiteral("a\\Ubb\\EáA") << QStringLiteral("aBBáA");
0143     testNewRow() << QStringLiteral("A\\LBB\\EÁa") << QStringLiteral("AbbÁa");
0144 }
0145 
0146 void RegExpSearchTest::testReplacementCaseConversion()
0147 {
0148     QFETCH(QString, pattern);
0149     QFETCH(QString, expected);
0150 
0151     const QString result = KateRegExpSearch::buildReplacement(pattern, QStringList(), 1);
0152 
0153     QCOMPARE(result, expected);
0154 }
0155 
0156 void RegExpSearchTest::testReplacementCounter_data()
0157 {
0158     QTest::addColumn<QString>("pattern");
0159     QTest::addColumn<int>("counter");
0160     QTest::addColumn<QString>("expected");
0161 
0162     testNewRow() << QStringLiteral("a\\#b") << 1 << QStringLiteral("a1b");
0163     testNewRow() << QStringLiteral("a\\#b") << 10 << QStringLiteral("a10b");
0164     testNewRow() << QStringLiteral("a\\#####b") << 1 << QStringLiteral("a00001b");
0165 }
0166 
0167 void RegExpSearchTest::testReplacementCounter()
0168 {
0169     QFETCH(QString, pattern);
0170     QFETCH(int, counter);
0171     QFETCH(QString, expected);
0172 
0173     const QString result = KateRegExpSearch::buildReplacement(pattern, QStringList(), counter);
0174 
0175     QCOMPARE(result, expected);
0176 }
0177 
0178 void RegExpSearchTest::testAnchoredRegexp_data()
0179 {
0180     QTest::addColumn<QString>("pattern");
0181     QTest::addColumn<Range>("inputRange");
0182     QTest::addColumn<bool>("backwards");
0183     QTest::addColumn<Range>("expected");
0184 
0185     testNewRow() << QStringLiteral("fe") << Range(0, 0, 0, 8) << false << Range(0, 0, 0, 2);
0186     testNewRow() << QStringLiteral("fe") << Range(0, 0, 0, 8) << true << Range(0, 6, 0, 8);
0187 
0188     testNewRow() << QStringLiteral("^fe") << Range(0, 0, 0, 8) << false << Range(0, 0, 0, 2);
0189     testNewRow() << QStringLiteral("^fe") << Range(0, 0, 0, 1) << false << Range::invalid();
0190     testNewRow() << QStringLiteral("^fe") << Range(0, 0, 0, 2) << false << Range(0, 0, 0, 2);
0191     testNewRow() << QStringLiteral("^fe") << Range(0, 3, 0, 8) << false << Range::invalid(); // only match at line start
0192     testNewRow() << QStringLiteral("^fe") << Range(0, 0, 0, 2) << true << Range(0, 0, 0, 2);
0193     testNewRow() << QStringLiteral("^fe") << Range(0, 0, 0, 1) << true << Range::invalid();
0194     testNewRow() << QStringLiteral("^fe") << Range(0, 0, 0, 2) << true << Range(0, 0, 0, 2);
0195     testNewRow() << QStringLiteral("^fe") << Range(0, 3, 0, 8) << true << Range::invalid();
0196 
0197     testNewRow() << QStringLiteral("fe$") << Range(0, 0, 0, 8) << false << Range(0, 6, 0, 8);
0198     testNewRow() << QStringLiteral("fe$") << Range(0, 7, 0, 8) << false << Range::invalid();
0199     testNewRow() << QStringLiteral("fe$") << Range(0, 6, 0, 8) << false << Range(0, 6, 0, 8);
0200     testNewRow() << QStringLiteral("fe$") << Range(0, 0, 0, 5) << false << Range::invalid(); // only match at line end, fails
0201     testNewRow() << QStringLiteral("fe$") << Range(0, 0, 0, 8) << true << Range(0, 6, 0, 8);
0202     testNewRow() << QStringLiteral("fe$") << Range(0, 7, 0, 8) << true << Range::invalid();
0203     testNewRow() << QStringLiteral("fe$") << Range(0, 6, 0, 8) << true << Range(0, 6, 0, 8);
0204     testNewRow() << QStringLiteral("fe$") << Range(0, 0, 0, 5) << true << Range::invalid();
0205 
0206     testNewRow() << QStringLiteral("^fe fe fe$") << Range(0, 0, 0, 8) << false << Range(0, 0, 0, 8);
0207     testNewRow() << QStringLiteral("^fe fe fe$") << Range(0, 3, 0, 8) << false << Range::invalid();
0208     testNewRow() << QStringLiteral("^fe fe fe$") << Range(0, 0, 0, 5) << false << Range::invalid();
0209     testNewRow() << QStringLiteral("^fe fe fe$") << Range(0, 3, 0, 5) << false << Range::invalid();
0210     testNewRow() << QStringLiteral("^fe fe fe$") << Range(0, 0, 0, 8) << true << Range(0, 0, 0, 8);
0211     testNewRow() << QStringLiteral("^fe fe fe$") << Range(0, 3, 0, 8) << true << Range::invalid();
0212     testNewRow() << QStringLiteral("^fe fe fe$") << Range(0, 0, 0, 5) << true << Range::invalid();
0213     testNewRow() << QStringLiteral("^fe fe fe$") << Range(0, 3, 0, 5) << true << Range::invalid();
0214 
0215     testNewRow() << QStringLiteral("^fe( fe)*$") << Range(0, 0, 0, 8) << false << Range(0, 0, 0, 8);
0216     testNewRow() << QStringLiteral("^fe( fe)*") << Range(0, 0, 0, 8) << false << Range(0, 0, 0, 8);
0217     testNewRow() << QStringLiteral("fe( fe)*$") << Range(0, 0, 0, 8) << false << Range(0, 0, 0, 8);
0218     testNewRow() << QStringLiteral("fe( fe)*") << Range(0, 0, 0, 8) << false << Range(0, 0, 0, 8);
0219     testNewRow() << QStringLiteral("^fe( fe)*$") << Range(0, 3, 0, 8) << false << Range::invalid();
0220     testNewRow() << QStringLiteral("fe( fe)*$") << Range(0, 3, 0, 8) << false << Range(0, 3, 0, 8);
0221     testNewRow() << QStringLiteral("^fe( fe)*$") << Range(0, 0, 0, 5) << false << Range::invalid();
0222     // fails because the whole line is fed to QRegularExpression, then matches
0223     // that end beyond the search range are rejected, see KateRegExpSearch::searchText()
0224     // testNewRow() << "^fe( fe)*"  << Range(0, 0, 0, 5) << false << Range(0, 0, 0, 5);
0225 
0226     testNewRow() << QStringLiteral("^fe( fe)*$") << Range(0, 0, 0, 8) << true << Range(0, 0, 0, 8);
0227     testNewRow() << QStringLiteral("^fe( fe)*") << Range(0, 0, 0, 8) << true << Range(0, 0, 0, 8);
0228     testNewRow() << QStringLiteral("fe( fe)*$") << Range(0, 0, 0, 8) << true << Range(0, 0, 0, 8);
0229     testNewRow() << QStringLiteral("fe( fe)*") << Range(0, 0, 0, 8) << true << Range(0, 0, 0, 8);
0230     testNewRow() << QStringLiteral("^fe( fe)*$") << Range(0, 3, 0, 8) << true << Range::invalid();
0231     testNewRow() << QStringLiteral("fe( fe)*$") << Range(0, 3, 0, 8) << true << Range(0, 3, 0, 8);
0232     testNewRow() << QStringLiteral("^fe( fe)*$") << Range(0, 0, 0, 5) << true << Range::invalid();
0233 
0234     testNewRow() << QStringLiteral("^fe|fe$") << Range(0, 0, 0, 5) << false << Range(0, 0, 0, 2);
0235     testNewRow() << QStringLiteral("^fe|fe$") << Range(0, 3, 0, 8) << false << Range(0, 6, 0, 8);
0236     testNewRow() << QStringLiteral("^fe|fe$") << Range(0, 0, 0, 5) << true << Range(0, 0, 0, 2);
0237     testNewRow() << QStringLiteral("^fe|fe$") << Range(0, 3, 0, 8) << true << Range(0, 6, 0, 8);
0238 }
0239 
0240 void RegExpSearchTest::testAnchoredRegexp()
0241 {
0242     QFETCH(QString, pattern);
0243     QFETCH(Range, inputRange);
0244     QFETCH(bool, backwards);
0245     QFETCH(Range, expected);
0246 
0247     KTextEditor::DocumentPrivate doc;
0248     doc.setText(QStringLiteral("fe fe fe"));
0249 
0250     KateRegExpSearch searcher(&doc);
0251 
0252     static int i = 0;
0253     if (i == 34 || i == 36) {
0254         qDebug() << i;
0255     }
0256     i++;
0257 
0258     const Range result = searcher.search(pattern, inputRange, backwards, QRegularExpression::CaseInsensitiveOption)[0];
0259 
0260     QCOMPARE(result, expected);
0261 }
0262 
0263 void RegExpSearchTest::testSearchForward()
0264 {
0265     KTextEditor::DocumentPrivate doc;
0266     doc.setText(QStringLiteral("  \\piinfercong"));
0267 
0268     KateRegExpSearch searcher(&doc);
0269     QList<KTextEditor::Range> result = searcher.search(QStringLiteral("\\\\piinfer(\\w)"), Range(0, 2, 0, 15), false);
0270 
0271     QCOMPARE(result.at(0), Range(0, 2, 0, 11));
0272     QCOMPARE(doc.text(result.at(1)), QLatin1Char('c'));
0273 
0274     // Test Unicode
0275     doc.setText(QStringLiteral("  \\piinferćong"));
0276     result = searcher.search(QStringLiteral("\\\\piinfer(\\w)"), Range(0, 2, 0, 15), false);
0277 
0278     QCOMPARE(result.at(0), Range(0, 2, 0, 11));
0279     QCOMPARE(doc.text(result.at(1)), QStringLiteral("ć"));
0280 }
0281 
0282 void RegExpSearchTest::testSearchBackwardInSelection()
0283 {
0284     KTextEditor::DocumentPrivate doc;
0285     doc.setText(QStringLiteral("foobar foo bar foo bar foo"));
0286 
0287     KateRegExpSearch searcher(&doc);
0288     const Range result = searcher.search(QStringLiteral("foo"), Range(0, 0, 0, 15), true)[0];
0289 
0290     QCOMPARE(result, Range(0, 7, 0, 10));
0291 }
0292 
0293 void RegExpSearchTest::test()
0294 {
0295     KTextEditor::DocumentPrivate doc;
0296     doc.setText(QStringLiteral("\\newcommand{\\piReductionOut}"));
0297 
0298     KateRegExpSearch searcher(&doc);
0299     const QList<Range> result = searcher.search(QStringLiteral("\\\\piReduction(\\S)"), Range(0, 10, 0, 28), true);
0300 
0301     QCOMPARE(result.size(), 2);
0302     QCOMPARE(result[0], Range(0, 12, 0, 25));
0303     QCOMPARE(result[1], Range(0, 24, 0, 25));
0304     QCOMPARE(doc.text(result[0]), QStringLiteral("\\piReductionO"));
0305     QCOMPARE(doc.text(result[1]), QStringLiteral("O"));
0306 }
0307 
0308 void RegExpSearchTest::testUnicode()
0309 {
0310     KTextEditor::DocumentPrivate doc;
0311     doc.setText(QStringLiteral("\\newcommand{\\piReductionOÓut}"));
0312 
0313     KateRegExpSearch searcher(&doc);
0314     const QList<Range> result = searcher.search(QStringLiteral("\\\\piReduction(\\w)(\\w)"), Range(0, 10, 0, 28), true);
0315 
0316     QCOMPARE(result.size(), 3);
0317     QCOMPARE(result.at(0), Range(0, 12, 0, 26));
0318     QCOMPARE(result.at(1), Range(0, 24, 0, 25));
0319     QCOMPARE(result.at(2), Range(0, 25, 0, 26));
0320     QCOMPARE(doc.text(result.at(0)), QStringLiteral("\\piReductionOÓ"));
0321     QCOMPARE(doc.text(result.at(1)), QStringLiteral("O"));
0322     QCOMPARE(doc.text(result.at(2)), QStringLiteral("Ó"));
0323 }