File indexing completed on 2024-04-28 15:52:52

0001 /*
0002     SPDX-FileCopyrightText: 2006 Hamish Rodda <rodda@kde.org>
0003     SPDX-FileCopyrightText: 2008 Niko Sams <niko.sams@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "lexertest.h"
0009 
0010 #include <QtTest>
0011 
0012 #include "parsesession.h"
0013 #include "phplexer.h"
0014 #include "phptokentext.h"
0015 
0016 QTEST_MAIN(Php::LexerTest)
0017 namespace Php
0018 {
0019 
0020 #define COMPARE_TOKEN(tokenStream, index, tokenKind, startLine, startColumn, endLine, endColumn) \
0021     { \
0022         QVERIFY(tokenStream->at(index).kind == tokenKind); \
0023         qint64 line; qint64 column; \
0024         tokenStream->startPosition(index, &line, &column); \
0025         QCOMPARE(line, (qint64) startLine); \
0026         QCOMPARE(column, (qint64) startColumn); \
0027         tokenStream->endPosition(index, &line, &column); \
0028         QCOMPARE(line, (qint64) endLine); \
0029         QCOMPARE(column, (qint64) endColumn); \
0030     }
0031 
0032 LexerTest::LexerTest()
0033 {
0034 }
0035 
0036 void LexerTest::testOpenTagWithNewline()
0037 {
0038     TokenStream* ts = tokenize(QStringLiteral("<?php\nfoo;"));
0039     QVERIFY(ts->size() == 3);
0040 
0041     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0042     COMPARE_TOKEN(ts, 1, Parser::Token_STRING, 1, 0, 1, 2);
0043     COMPARE_TOKEN(ts, 2, Parser::Token_SEMICOLON, 1, 3, 1, 3);
0044 
0045     delete ts;
0046 }
0047 
0048 void LexerTest::testOpenTagWithSpace()
0049 {
0050     TokenStream* ts = tokenize(QStringLiteral("<?php foo;"));
0051     QVERIFY(ts->size() == 3);
0052 
0053     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0054     COMPARE_TOKEN(ts, 1, Parser::Token_STRING, 0, 6, 0, 8);
0055     COMPARE_TOKEN(ts, 2, Parser::Token_SEMICOLON, 0, 9, 0, 9);
0056     delete ts;
0057 }
0058 
0059 void LexerTest::testCommentOneLine()
0060 {
0061     TokenStream* ts = tokenize(QStringLiteral("<?php\n//comment\nfoo;"));
0062     QVERIFY(ts->size() == 4);
0063 
0064     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0065     COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 1, 0, 1, 9);
0066     COMPARE_TOKEN(ts, 2, Parser::Token_STRING, 2, 0, 2, 2);
0067     COMPARE_TOKEN(ts, 3, Parser::Token_SEMICOLON, 2, 3, 2, 3);
0068     delete ts;
0069 }
0070 
0071 void LexerTest::testCommentOneLine2()
0072 {
0073     TokenStream* ts = tokenize(QStringLiteral("<?php\n#comment\nfoo;"));
0074     QVERIFY(ts->size() == 4);
0075 
0076     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0077     COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 1, 0, 1, 8);
0078     COMPARE_TOKEN(ts, 2, Parser::Token_STRING, 2, 0, 2, 2);
0079     COMPARE_TOKEN(ts, 3, Parser::Token_SEMICOLON, 2, 3, 2, 3);
0080     delete ts;
0081 }
0082 
0083 void LexerTest::testCommentMultiLine()
0084 {
0085     TokenStream* ts = tokenize(QStringLiteral("<?php\n/*com\nment*/\nfoo;"), true);
0086     QVERIFY(ts->size() == 5);
0087 
0088     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0089     COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 1, 0, 2, 5);
0090     COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 2, 6, 2, 6);
0091     COMPARE_TOKEN(ts, 3, Parser::Token_STRING, 3, 0, 3, 2);
0092     COMPARE_TOKEN(ts, 4, Parser::Token_SEMICOLON, 3, 3, 3, 3);
0093     delete ts;
0094 }
0095 
0096 void LexerTest::testCommentMultiLine2()
0097 {
0098     TokenStream* ts = tokenize(QStringLiteral("<?php\n/*\nment*/\nfoo;"), true);
0099     QVERIFY(ts->size() == 5);
0100 
0101     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0102     COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 1, 0, 2, 5);
0103     COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 2, 6, 2, 6);
0104     COMPARE_TOKEN(ts, 3, Parser::Token_STRING, 3, 0, 3, 2);
0105     COMPARE_TOKEN(ts, 4, Parser::Token_SEMICOLON, 3, 3, 3, 3);
0106     delete ts;
0107 }
0108 
0109 void LexerTest::testEndTag()
0110 {
0111     TokenStream* ts = tokenize(QStringLiteral("<?\n':\n'?>\n>"), true, Lexer::DefaultState);
0112     //don't crash and we are fine
0113     delete ts;
0114 }
0115 
0116 void LexerTest::testNewlineInString()
0117 {
0118     //0            1
0119     //012345 6 7 890123456789
0120     TokenStream* ts = tokenize(QStringLiteral("<?php \"\n\";"), true);
0121     QVERIFY(ts->size() == 3);
0122 
0123     COMPARE_TOKEN(ts, 1, Parser::Token_CONSTANT_ENCAPSED_STRING, 0, 6, 1, 0);
0124     COMPARE_TOKEN(ts, 2, Parser::Token_SEMICOLON, 1, 1, 1, 1);
0125     delete ts;
0126 }
0127 
0128 void LexerTest::testNewlineInString2()
0129 {
0130     //0
0131     //0123 4567
0132     TokenStream* ts = tokenize(QStringLiteral("<?php '\n';"), true);
0133     QCOMPARE((int)ts->size(), 3);
0134 
0135     COMPARE_TOKEN(ts, 1, Parser::Token_CONSTANT_ENCAPSED_STRING, 0, 6, 1, 0);
0136     COMPARE_TOKEN(ts, 2, Parser::Token_SEMICOLON, 1, 1, 1, 1);
0137     delete ts;
0138 }
0139 
0140 void LexerTest::testNewlineInStringWithVar()
0141 {
0142     TokenStream* ts = tokenize(QStringLiteral("<?php \"$a\n\";"), true);
0143     QCOMPARE((int)ts->size(), 6);
0144 
0145     COMPARE_TOKEN(ts, 1, Parser::Token_DOUBLE_QUOTE, 0, 6, 0, 6);
0146     COMPARE_TOKEN(ts, 2, Parser::Token_VARIABLE, 0, 7, 0, 8);
0147     COMPARE_TOKEN(ts, 3, Parser::Token_ENCAPSED_AND_WHITESPACE, 0, 9, 0, 9);
0148     COMPARE_TOKEN(ts, 4, Parser::Token_DOUBLE_QUOTE, 1, 0, 1, 0);
0149     COMPARE_TOKEN(ts, 5, Parser::Token_SEMICOLON, 1, 1, 1, 1);
0150     delete ts;
0151 }
0152 
0153 void LexerTest::testNewlineInStringWithVar2()
0154 {
0155     //0            1
0156     //012345 6 789 0123456789
0157     TokenStream* ts = tokenize(QStringLiteral("<?php \"\n$a\n\";"), true);
0158     QCOMPARE((int)ts->size(), 7);
0159 
0160     COMPARE_TOKEN(ts, 1, Parser::Token_DOUBLE_QUOTE, 0, 6, 0, 6);
0161     COMPARE_TOKEN(ts, 2, Parser::Token_ENCAPSED_AND_WHITESPACE, 0, 7, 0, 7);
0162     COMPARE_TOKEN(ts, 3, Parser::Token_VARIABLE, 1, 0, 1, 1);
0163     COMPARE_TOKEN(ts, 4, Parser::Token_ENCAPSED_AND_WHITESPACE, 1, 2, 1, 2);
0164     COMPARE_TOKEN(ts, 5, Parser::Token_DOUBLE_QUOTE, 2, 0, 2, 0);
0165     COMPARE_TOKEN(ts, 6, Parser::Token_SEMICOLON, 2, 1, 2, 1);
0166     delete ts;
0167 }
0168 
0169 void LexerTest::testNewlineInStringWithVar3()
0170 {
0171     //0            1
0172     //012345 6 789 0123456789
0173     TokenStream* ts = tokenize(QStringLiteral("<?php \"{$$a}\";"), true);
0174     QCOMPARE((int)ts->size(), 7);
0175 
0176     COMPARE_TOKEN(ts, 1, Parser::Token_DOUBLE_QUOTE, 0, 6, 0, 6);
0177     COMPARE_TOKEN(ts, 2, Parser::Token_ENCAPSED_AND_WHITESPACE, 0, 7, 0, 8);
0178     COMPARE_TOKEN(ts, 3, Parser::Token_VARIABLE, 0, 9, 0, 10);
0179     COMPARE_TOKEN(ts, 4, Parser::Token_ENCAPSED_AND_WHITESPACE, 0, 11, 0, 11);
0180     COMPARE_TOKEN(ts, 5, Parser::Token_DOUBLE_QUOTE, 0, 12, 0, 12);
0181     COMPARE_TOKEN(ts, 6, Parser::Token_SEMICOLON, 0, 13, 0, 13);
0182     delete ts;
0183 }
0184 
0185 void LexerTest::testMultiplePhpSections()
0186 {
0187 
0188     //0            1
0189     //012345 6 789 0123456789
0190     TokenStream* ts = tokenize(QStringLiteral("<?php $a;?>\n<html>\n<?php $a;?>"), true);
0191     QCOMPARE((int)ts->size(), 9);
0192 
0193     qint64 index = 0;
0194     for (qint64 line = 0; line <= 2; ++line) {
0195         if (line == 1) {
0196             // the html stuff in the middle
0197             COMPARE_TOKEN(ts, index, Parser::Token_INLINE_HTML, 0, 11, 1, 6);
0198             ++index;
0199         } else {
0200             // the php stuff (symmetric) at the start and end
0201             COMPARE_TOKEN(ts, index, Parser::Token_OPEN_TAG, line, 0, line, 5);
0202             ++index;
0203 
0204             COMPARE_TOKEN(ts, index, Parser::Token_VARIABLE, line, 6, line, 7);
0205             ++index;
0206 
0207             COMPARE_TOKEN(ts, index, Parser::Token_SEMICOLON, line, 8, line, 8);
0208             ++index;
0209 
0210             COMPARE_TOKEN(ts, index, Parser::Token_CLOSE_TAG, line, 9, line, 10);
0211             ++index;
0212         }
0213     }
0214     delete ts;
0215 }
0216 
0217 void LexerTest::testHereDoc()
0218 {
0219     TokenStream* ts = tokenize(QStringLiteral("<?php\necho <<<EOD1\nstart $text\nend\nEOD1;\n$extern;"), true);
0220     QCOMPARE((int)ts->size(), 12);
0221 
0222     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0223     COMPARE_TOKEN(ts, 1, Parser::Token_ECHO, 1, 0, 1, 3);
0224     COMPARE_TOKEN(ts, 3, Parser::Token_START_HEREDOC, 1, 5, 1, 12);
0225     COMPARE_TOKEN(ts, 4, Parser::Token_ENCAPSED_AND_WHITESPACE, 2, 0, 2, 5);
0226     COMPARE_TOKEN(ts, 5, Parser::Token_VARIABLE, 2, 6, 2, 10);
0227     COMPARE_TOKEN(ts, 6, Parser::Token_ENCAPSED_AND_WHITESPACE, 2, 11, 3, 3);
0228     COMPARE_TOKEN(ts, 7, Parser::Token_END_HEREDOC, 4, 0, 4, 3);
0229     COMPARE_TOKEN(ts, 8, Parser::Token_SEMICOLON, 4, 4, 4, 4);
0230     COMPARE_TOKEN(ts, 10, Parser::Token_VARIABLE, 5, 0, 5, 6);
0231     COMPARE_TOKEN(ts, 11, Parser::Token_SEMICOLON, 5, 7, 5, 7);
0232     delete ts;
0233 }
0234 
0235 void LexerTest::testHereDocQuoted()
0236 {
0237     TokenStream* ts = tokenize(QStringLiteral("<?php\necho <<<\"EOD1\"\nstart $text\nend\nEOD1;\n$extern;"), true);
0238     QCOMPARE((int)ts->size(), 12);
0239 
0240     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0241     COMPARE_TOKEN(ts, 1, Parser::Token_ECHO, 1, 0, 1, 3);
0242     COMPARE_TOKEN(ts, 3, Parser::Token_START_HEREDOC, 1, 5, 1, 14);
0243     COMPARE_TOKEN(ts, 4, Parser::Token_ENCAPSED_AND_WHITESPACE, 2, 0, 2, 5);
0244     COMPARE_TOKEN(ts, 5, Parser::Token_VARIABLE, 2, 6, 2, 10);
0245     COMPARE_TOKEN(ts, 6, Parser::Token_ENCAPSED_AND_WHITESPACE, 2, 11, 3, 3);
0246     COMPARE_TOKEN(ts, 7, Parser::Token_END_HEREDOC, 4, 0, 4, 3);
0247     COMPARE_TOKEN(ts, 8, Parser::Token_SEMICOLON, 4, 4, 4, 4);
0248     COMPARE_TOKEN(ts, 10, Parser::Token_VARIABLE, 5, 0, 5, 6);
0249     COMPARE_TOKEN(ts, 11, Parser::Token_SEMICOLON, 5, 7, 5, 7);
0250     delete ts;
0251 }
0252 
0253 void LexerTest::testNowdoc()
0254 {
0255     TokenStream* ts = tokenize(QStringLiteral("<?php\necho <<<'EOD1'\nstart $text\nend\nEOD1;\n$extern;"), true);
0256     QCOMPARE((int)ts->size(), 10);
0257 
0258     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0259     COMPARE_TOKEN(ts, 1, Parser::Token_ECHO, 1, 0, 1, 3);
0260     COMPARE_TOKEN(ts, 3, Parser::Token_START_NOWDOC, 1, 5, 1, 14);
0261     COMPARE_TOKEN(ts, 4, Parser::Token_STRING, 2, 0, 3, 3);
0262     COMPARE_TOKEN(ts, 5, Parser::Token_END_NOWDOC, 4, 0, 4, 3);
0263     COMPARE_TOKEN(ts, 6, Parser::Token_SEMICOLON, 4, 4, 4, 4);
0264     COMPARE_TOKEN(ts, 8, Parser::Token_VARIABLE, 5, 0, 5, 6);
0265     COMPARE_TOKEN(ts, 9, Parser::Token_SEMICOLON, 5, 7, 5, 7);
0266     delete ts;
0267 }
0268 
0269 void LexerTest::testCommonStringTokens()
0270 {
0271     // all these should have open_tag followed by constant encapsed string
0272     foreach ( const QString& code, QStringList() << "<?php ''" << "<?php \"\"" << "<?php '" << "<?php \"" ) {
0273         qDebug() << code;
0274         TokenStream* ts = tokenize(code, true);
0275 
0276         QCOMPARE((int)ts->size(), 2);
0277 
0278         COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0279         COMPARE_TOKEN(ts, 1, Parser::Token_CONSTANT_ENCAPSED_STRING, 0, 6, 0, code.size() - 1);
0280 
0281         delete ts;
0282     }
0283 }
0284 
0285 void LexerTest::testNonTerminatedStringWithVar()
0286 {
0287     TokenStream* ts = tokenize(QStringLiteral("<?php \"$a"), true);
0288 
0289     QCOMPARE((int)ts->size(), 3);
0290 
0291     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0292     COMPARE_TOKEN(ts, 1, Parser::Token_DOUBLE_QUOTE, 0, 6, 0, 6);
0293     COMPARE_TOKEN(ts, 2, Parser::Token_VARIABLE, 0, 7, 0, 8);
0294     delete ts;
0295 }
0296 
0297 void LexerTest::testPhpBlockWithComment()
0298 {
0299     TokenStream* ts = tokenize(
0300         QStringLiteral("<?php\n"
0301         "//asdf\n"
0302         "?>\n"
0303         "<?php\n")
0304     , true);
0305 
0306     QCOMPARE((int)ts->size(), 5);
0307 
0308     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0309     COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 1, 0, 1, 6);
0310     COMPARE_TOKEN(ts, 2, Parser::Token_CLOSE_TAG, 2, 0, 2, 1);
0311     COMPARE_TOKEN(ts, 3, Parser::Token_INLINE_HTML, 2, 2, 2, 2);
0312     COMPARE_TOKEN(ts, 4, Parser::Token_OPEN_TAG, 3, 0, 3, 5);
0313     delete ts;
0314 }
0315 
0316 void LexerTest::testNamespaces()
0317 {
0318     TokenStream* ts = tokenize(
0319         QStringLiteral("<?php\n"
0320         "namespace Foo;\n"
0321         "namespace Foo\\Bar;\n"
0322         "namespace Foo\\Bar\\Asd {\n"
0323         "}\n")
0324     , true);
0325     QCOMPARE((int)ts->size(), 25);
0326 
0327     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0328 
0329     COMPARE_TOKEN(ts, 1, Parser::Token_NAMESPACE, 1, 0, 1, 8);
0330     COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 9, 1, 9);
0331     COMPARE_TOKEN(ts, 3, Parser::Token_STRING, 1, 10, 1, 12);
0332     COMPARE_TOKEN(ts, 4, Parser::Token_SEMICOLON, 1, 13, 1, 13);
0333 
0334     COMPARE_TOKEN(ts, 6, Parser::Token_NAMESPACE, 2, 0, 2, 8);
0335     COMPARE_TOKEN(ts, 7, Parser::Token_WHITESPACE, 2, 9, 2, 9);
0336     COMPARE_TOKEN(ts, 8, Parser::Token_STRING, 2, 10, 2, 12);
0337     COMPARE_TOKEN(ts, 9, Parser::Token_BACKSLASH, 2, 13, 2, 13);
0338     COMPARE_TOKEN(ts, 10, Parser::Token_STRING, 2, 14, 2, 16);
0339     COMPARE_TOKEN(ts, 11, Parser::Token_SEMICOLON, 2, 17, 2, 17);
0340 
0341     COMPARE_TOKEN(ts, 13, Parser::Token_NAMESPACE, 3, 0, 3, 8);
0342     COMPARE_TOKEN(ts, 14, Parser::Token_WHITESPACE, 3, 9, 3, 9);
0343     COMPARE_TOKEN(ts, 15, Parser::Token_STRING, 3, 10, 3, 12);
0344     COMPARE_TOKEN(ts, 16, Parser::Token_BACKSLASH, 3, 13, 3, 13);
0345     COMPARE_TOKEN(ts, 17, Parser::Token_STRING, 3, 14, 3, 16);
0346     COMPARE_TOKEN(ts, 18, Parser::Token_BACKSLASH, 3, 17, 3, 17);
0347     COMPARE_TOKEN(ts, 19, Parser::Token_STRING, 3, 18, 3, 20);
0348     COMPARE_TOKEN(ts, 20, Parser::Token_WHITESPACE, 3, 21, 3, 21);
0349     COMPARE_TOKEN(ts, 21, Parser::Token_LBRACE, 3, 22, 3, 22);
0350     COMPARE_TOKEN(ts, 23, Parser::Token_RBRACE, 4, 0, 4, 0);
0351 
0352     delete ts;
0353 }
0354 
0355 void LexerTest::testCloseTagInComment()
0356 {
0357     {
0358     TokenStream* ts = tokenize(
0359         QStringLiteral("<?php // asdf ?>")
0360     , true);
0361     QCOMPARE((int)ts->size(), 3);
0362 
0363     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0364     COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 0, 6, 0, 13);
0365     COMPARE_TOKEN(ts, 2, Parser::Token_CLOSE_TAG, 0, 14, 0, 15);
0366 
0367     delete ts;
0368     }
0369     {
0370     TokenStream* ts = tokenize(
0371         QStringLiteral("<?php #  asdf ?>")
0372     , true);
0373     QCOMPARE((int)ts->size(), 3);
0374 
0375     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0376     COMPARE_TOKEN(ts, 1, Parser::Token_COMMENT, 0, 6, 0, 13);
0377     COMPARE_TOKEN(ts, 2, Parser::Token_CLOSE_TAG, 0, 14, 0, 15);
0378 
0379     delete ts;
0380     }
0381 }
0382 
0383 void LexerTest::testBinaryNumber()
0384 {
0385     TokenStream* ts = tokenize(QStringLiteral("<?php\n0b01;\n0B01;"), true);
0386     QCOMPARE((int)ts->size(), 6);
0387 
0388     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0389     COMPARE_TOKEN(ts, 1, Parser::Token_LNUMBER, 1, 0, 1, 3);
0390     COMPARE_TOKEN(ts, 2, Parser::Token_SEMICOLON, 1, 4, 1, 4);
0391     COMPARE_TOKEN(ts, 3, Parser::Token_WHITESPACE, 1, 5, 1, 5);
0392     COMPARE_TOKEN(ts, 4, Parser::Token_LNUMBER, 2, 0, 2, 3);
0393     COMPARE_TOKEN(ts, 5, Parser::Token_SEMICOLON, 2, 4, 2, 4);
0394     delete ts;
0395 }
0396 
0397 void LexerTest::testHexadecimalNumber()
0398 {
0399     TokenStream* ts = tokenize(QStringLiteral("<?php\n0x01;\n0X01;\n0xABC12;\n0Xab10A;"), true);
0400     QCOMPARE((int)ts->size(), 12);
0401 
0402     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0403     COMPARE_TOKEN(ts, 1, Parser::Token_LNUMBER, 1, 0, 1, 3);
0404     COMPARE_TOKEN(ts, 2, Parser::Token_SEMICOLON, 1, 4, 1, 4);
0405     COMPARE_TOKEN(ts, 3, Parser::Token_WHITESPACE, 1, 5, 1, 5);
0406     COMPARE_TOKEN(ts, 4, Parser::Token_LNUMBER, 2, 0, 2, 3);
0407     COMPARE_TOKEN(ts, 5, Parser::Token_SEMICOLON, 2, 4, 2, 4);
0408     COMPARE_TOKEN(ts, 6, Parser::Token_WHITESPACE, 2, 5, 2, 5);
0409     COMPARE_TOKEN(ts, 7, Parser::Token_LNUMBER, 3, 0, 3, 6);
0410     COMPARE_TOKEN(ts, 8, Parser::Token_SEMICOLON, 3, 7, 3, 7);
0411     COMPARE_TOKEN(ts, 9, Parser::Token_WHITESPACE, 3, 8, 3, 8);
0412     COMPARE_TOKEN(ts, 10, Parser::Token_LNUMBER, 4, 0, 4, 6);
0413     COMPARE_TOKEN(ts, 11, Parser::Token_SEMICOLON, 4, 7, 4, 7);
0414     delete ts;
0415 }
0416 
0417 void LexerTest::testTypeHintsOnFunction()
0418 {
0419     QScopedPointer<TokenStream> ts(tokenize(QStringLiteral("<?php\nfunction a($a, array $b = [], callable $c) {}"), true));
0420     QCOMPARE((int)ts->size(), 25);
0421 
0422     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0423     COMPARE_TOKEN(ts, 1, Parser::Token_FUNCTION, 1, 0, 1, 7);
0424     COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 8, 1, 8);
0425     COMPARE_TOKEN(ts, 3, Parser::Token_STRING, 1, 9, 1, 9);
0426     COMPARE_TOKEN(ts, 4, Parser::Token_LPAREN, 1, 10, 1, 10);
0427     COMPARE_TOKEN(ts, 5, Parser::Token_VARIABLE,  1, 11, 1, 12);
0428     COMPARE_TOKEN(ts, 6, Parser::Token_COMMA, 1, 13, 1, 13);
0429     COMPARE_TOKEN(ts, 7, Parser::Token_WHITESPACE, 1, 14, 1, 14);
0430     COMPARE_TOKEN(ts, 8, Parser::Token_ARRAY, 1, 15, 1, 19);
0431     COMPARE_TOKEN(ts, 9, Parser::Token_WHITESPACE, 1, 20, 1, 20);
0432     COMPARE_TOKEN(ts, 10, Parser::Token_VARIABLE,  1, 21, 1, 22);
0433     COMPARE_TOKEN(ts, 11, Parser::Token_WHITESPACE, 1, 23, 1, 23);
0434     COMPARE_TOKEN(ts, 12, Parser::Token_ASSIGN, 1, 24, 1, 24);
0435     COMPARE_TOKEN(ts, 13, Parser::Token_WHITESPACE, 1, 25, 1, 25);
0436     COMPARE_TOKEN(ts, 14, Parser::Token_LBRACKET, 1, 26, 1, 26);
0437     COMPARE_TOKEN(ts, 15, Parser::Token_RBRACKET, 1, 27, 1, 27);
0438     COMPARE_TOKEN(ts, 16, Parser::Token_COMMA, 1, 28, 1, 28);
0439     COMPARE_TOKEN(ts, 17, Parser::Token_WHITESPACE, 1, 29, 1, 29);
0440     COMPARE_TOKEN(ts, 18, Parser::Token_CALLABLE, 1, 30, 1, 37);
0441     COMPARE_TOKEN(ts, 19, Parser::Token_WHITESPACE, 1, 38, 1, 38);
0442     COMPARE_TOKEN(ts, 20, Parser::Token_VARIABLE,  1, 39, 1, 40);
0443     COMPARE_TOKEN(ts, 21, Parser::Token_RPAREN, 1, 41, 1, 41);
0444     COMPARE_TOKEN(ts, 22, Parser::Token_WHITESPACE, 1, 42, 1, 42);
0445     COMPARE_TOKEN(ts, 23, Parser::Token_LBRACE, 1, 43, 1, 43);
0446     COMPARE_TOKEN(ts, 24, Parser::Token_RBRACE, 1, 44, 1, 44);
0447 }
0448 
0449 void LexerTest::testReturnTypeHints()
0450 {
0451     QScopedPointer<TokenStream> ts(tokenize(QStringLiteral("<?php\nfunction a(): string {}"), true));
0452     QCOMPARE((int)ts->size(), 12);
0453 
0454     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0455     COMPARE_TOKEN(ts, 1, Parser::Token_FUNCTION, 1, 0, 1, 7);
0456     COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 8, 1, 8);
0457     COMPARE_TOKEN(ts, 3, Parser::Token_STRING, 1, 9, 1, 9);
0458     COMPARE_TOKEN(ts, 4, Parser::Token_LPAREN, 1, 10, 1, 10);
0459     COMPARE_TOKEN(ts, 5, Parser::Token_RPAREN, 1, 11, 1, 11);
0460     COMPARE_TOKEN(ts, 6, Parser::Token_COLON, 1, 12, 1, 12);
0461     COMPARE_TOKEN(ts, 7, Parser::Token_WHITESPACE, 1, 13, 1, 13);
0462     COMPARE_TOKEN(ts, 8, Parser::Token_STRING, 1, 14, 1, 19);
0463     COMPARE_TOKEN(ts, 9, Parser::Token_WHITESPACE, 1, 20, 1, 20);
0464     COMPARE_TOKEN(ts, 10, Parser::Token_LBRACE, 1, 21, 1, 21);
0465     COMPARE_TOKEN(ts, 11, Parser::Token_RBRACE, 1, 22, 1, 22);
0466 }
0467 
0468 void LexerTest::testExponentiation()
0469 {
0470     QScopedPointer<TokenStream> ts(tokenize(QStringLiteral("<?php\n$a = 2 ** 3; $a **= 2;"), true));
0471     QCOMPARE((int)ts->size(), 18);
0472 
0473     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0474     COMPARE_TOKEN(ts, 1, Parser::Token_VARIABLE, 1, 0, 1, 1);
0475     COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 2, 1, 2);
0476     COMPARE_TOKEN(ts, 3, Parser::Token_ASSIGN, 1, 3, 1, 3);
0477     COMPARE_TOKEN(ts, 4, Parser::Token_WHITESPACE, 1, 4, 1, 4);
0478     COMPARE_TOKEN(ts, 5, Parser::Token_LNUMBER, 1, 5, 1, 5);
0479     COMPARE_TOKEN(ts, 6, Parser::Token_WHITESPACE, 1, 6, 1, 6);
0480     COMPARE_TOKEN(ts, 7, Parser::Token_EXP, 1, 7, 1, 8);
0481     COMPARE_TOKEN(ts, 8, Parser::Token_WHITESPACE, 1, 9, 1, 9);
0482     COMPARE_TOKEN(ts, 9, Parser::Token_LNUMBER, 1, 10, 1, 10);
0483     COMPARE_TOKEN(ts, 10, Parser::Token_SEMICOLON, 1, 11, 1, 11);
0484     COMPARE_TOKEN(ts, 11, Parser::Token_WHITESPACE, 1, 12, 1, 12);
0485     COMPARE_TOKEN(ts, 12, Parser::Token_VARIABLE, 1, 13, 1, 14);
0486     COMPARE_TOKEN(ts, 13, Parser::Token_WHITESPACE, 1, 15, 1, 15);
0487     COMPARE_TOKEN(ts, 14, Parser::Token_EXP_ASSIGN, 1, 16, 1, 18);
0488     COMPARE_TOKEN(ts, 15, Parser::Token_WHITESPACE, 1, 19, 1, 19);
0489     COMPARE_TOKEN(ts, 16, Parser::Token_LNUMBER, 1, 20, 1, 20);
0490     COMPARE_TOKEN(ts, 17, Parser::Token_SEMICOLON, 1, 21, 1, 21);
0491 
0492 }
0493 
0494 void LexerTest::testExceptionFinally()
0495 {
0496     QScopedPointer<TokenStream> ts(tokenize(QStringLiteral("<?php\ntry { $a = 1; } finally { }"), true));
0497     QCOMPARE((int)ts->size(), 19);
0498 
0499     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0500     COMPARE_TOKEN(ts, 1, Parser::Token_TRY, 1, 0, 1, 2);
0501     COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 3, 1, 3);
0502     COMPARE_TOKEN(ts, 3, Parser::Token_LBRACE, 1, 4, 1, 4);
0503     COMPARE_TOKEN(ts, 4, Parser::Token_WHITESPACE, 1, 5, 1, 5);
0504     COMPARE_TOKEN(ts, 5, Parser::Token_VARIABLE, 1, 6, 1, 7);
0505     COMPARE_TOKEN(ts, 6, Parser::Token_WHITESPACE, 1, 8, 1, 8);
0506     COMPARE_TOKEN(ts, 7, Parser::Token_ASSIGN, 1, 9, 1, 9);
0507     COMPARE_TOKEN(ts, 8, Parser::Token_WHITESPACE, 1, 10, 1, 10);
0508     COMPARE_TOKEN(ts, 9, Parser::Token_LNUMBER, 1, 11, 1, 11);
0509     COMPARE_TOKEN(ts, 10, Parser::Token_SEMICOLON, 1, 12, 1, 12);
0510     COMPARE_TOKEN(ts, 11, Parser::Token_WHITESPACE, 1, 13, 1, 13);
0511     COMPARE_TOKEN(ts, 12, Parser::Token_RBRACE, 1, 14, 1, 14);
0512     COMPARE_TOKEN(ts, 13, Parser::Token_WHITESPACE, 1, 15, 1, 15);
0513     COMPARE_TOKEN(ts, 14, Parser::Token_FINALLY, 1, 16, 1, 22);
0514     COMPARE_TOKEN(ts, 15, Parser::Token_WHITESPACE, 1, 23, 1, 23);
0515     COMPARE_TOKEN(ts, 16, Parser::Token_LBRACE, 1, 24, 1, 24);
0516     COMPARE_TOKEN(ts, 17, Parser::Token_WHITESPACE, 1, 25, 1, 25);
0517     COMPARE_TOKEN(ts, 18, Parser::Token_RBRACE, 1, 26, 1, 26);
0518 }
0519 
0520 void LexerTest::testEllipsis()
0521 {
0522     QScopedPointer<TokenStream> ts(tokenize(QStringLiteral("<?php\nfunction foo(...$args) {}"), true));
0523     QCOMPARE((int)ts->size(), 11);
0524 
0525     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0526     COMPARE_TOKEN(ts, 1, Parser::Token_FUNCTION, 1, 0, 1, 7);
0527     COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 8, 1, 8);
0528     COMPARE_TOKEN(ts, 3, Parser::Token_STRING, 1, 9, 1, 11);
0529     COMPARE_TOKEN(ts, 4, Parser::Token_LPAREN, 1, 12, 1, 12);
0530     COMPARE_TOKEN(ts, 5, Parser::Token_ELLIPSIS, 1, 13, 1, 15);
0531     COMPARE_TOKEN(ts, 6, Parser::Token_VARIABLE, 1, 16, 1, 20);
0532     COMPARE_TOKEN(ts, 7, Parser::Token_RPAREN, 1, 21, 1, 21);
0533     COMPARE_TOKEN(ts, 8, Parser::Token_WHITESPACE, 1, 22, 1, 22);
0534     COMPARE_TOKEN(ts, 9, Parser::Token_LBRACE, 1, 23, 1, 23);
0535     COMPARE_TOKEN(ts, 10, Parser::Token_RBRACE, 1, 24, 1, 24);
0536 }
0537 
0538 void LexerTest::testSpaceship()
0539 {
0540     QScopedPointer<TokenStream> ts(tokenize(QStringLiteral("<?php\n$a = 'ab' <=> 'b';"), true));
0541     QCOMPARE((int)ts->size(), 11);
0542 
0543     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0544     COMPARE_TOKEN(ts, 1, Parser::Token_VARIABLE, 1, 0, 1, 1);
0545     COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 2, 1, 2);
0546     COMPARE_TOKEN(ts, 3, Parser::Token_ASSIGN, 1, 3, 1, 3);
0547     COMPARE_TOKEN(ts, 4, Parser::Token_WHITESPACE, 1, 4, 1, 4);
0548     COMPARE_TOKEN(ts, 5, Parser::Token_CONSTANT_ENCAPSED_STRING, 1, 5, 1, 8);
0549     COMPARE_TOKEN(ts, 6, Parser::Token_WHITESPACE, 1, 9, 1, 9);
0550     COMPARE_TOKEN(ts, 7, Parser::Token_SPACESHIP, 1, 10, 1, 12);
0551     COMPARE_TOKEN(ts, 8, Parser::Token_WHITESPACE, 1, 13, 1, 13);
0552     COMPARE_TOKEN(ts, 9, Parser::Token_CONSTANT_ENCAPSED_STRING, 1, 14, 1, 16);
0553     COMPARE_TOKEN(ts, 10, Parser::Token_SEMICOLON, 1, 17, 1, 17);
0554 }
0555 
0556 void LexerTest::testNullCoalesce()
0557 {
0558     QScopedPointer<TokenStream> ts(tokenize(QStringLiteral("<?php\n$a = null ?? true;"), true));
0559     QCOMPARE((int)ts->size(), 11);
0560 
0561     COMPARE_TOKEN(ts, 0, Parser::Token_OPEN_TAG, 0, 0, 0, 5);
0562     COMPARE_TOKEN(ts, 1, Parser::Token_VARIABLE, 1, 0, 1, 1);
0563     COMPARE_TOKEN(ts, 2, Parser::Token_WHITESPACE, 1, 2, 1, 2);
0564     COMPARE_TOKEN(ts, 3, Parser::Token_ASSIGN, 1, 3, 1, 3);
0565     COMPARE_TOKEN(ts, 4, Parser::Token_WHITESPACE, 1, 4, 1, 4);
0566     COMPARE_TOKEN(ts, 5, Parser::Token_STRING, 1, 5, 1, 8);
0567     COMPARE_TOKEN(ts, 6, Parser::Token_WHITESPACE, 1, 9, 1, 9);
0568     COMPARE_TOKEN(ts, 7, Parser::Token_NULL_COALESCE, 1, 10, 1, 11);
0569     COMPARE_TOKEN(ts, 8, Parser::Token_WHITESPACE, 1, 12, 1, 12);
0570     COMPARE_TOKEN(ts, 9, Parser::Token_STRING, 1, 13, 1, 16);
0571     COMPARE_TOKEN(ts, 10, Parser::Token_SEMICOLON, 1, 17, 1, 17);
0572 }
0573 
0574 TokenStream* LexerTest::tokenize(const QString& unit, bool debug, int initialState)
0575 {
0576     auto* tokenStream = new TokenStream;
0577     Lexer lexer(tokenStream, unit, initialState);
0578     int token;
0579     int i = 0;
0580     QList<Parser::Token> tokens;
0581     while ((token = lexer.nextTokenKind())) {
0582         Parser::Token &t = tokenStream->push();
0583         t.begin = lexer.tokenBegin();
0584         t.end = lexer.tokenEnd();
0585         t.kind = token;
0586         tokens << t;
0587     }
0588     if (debug) {
0589         foreach(const Parser::Token &t, tokens) {
0590             qint64 beginLine;
0591             qint64 beginColumn;
0592             tokenStream->startPosition(i, &beginLine, &beginColumn);
0593             qint64 endLine;
0594             qint64 endColumn;
0595             tokenStream->endPosition(i, &endLine, &endColumn);
0596             qDebug() << tokenText(t.kind)
0597             << unit.mid(t.begin, t.end - t.begin + 1).replace('\n', QLatin1String("\\n"))
0598             << QStringLiteral("[%0-%1] - [%2-%3]").arg(beginLine).arg(beginColumn).arg(endLine).arg(endColumn);
0599             ++i;
0600         }
0601     }
0602     return tokenStream;
0603 }
0604 }
0605 
0606 #include "moc_lexertest.cpp"