diff --git a/grammar.js b/grammar.js index 9c3331d6..671187ff 100644 --- a/grammar.js +++ b/grammar.js @@ -116,7 +116,7 @@ module.exports = grammar({ )), ), - php_tag: $ => /<\?([pP][hH][pP]|=)?/, + php_tag: _ => /<\?([pP][hH][pP]|=)?/, text_interpolation: $ => seq( '?>', @@ -124,7 +124,7 @@ module.exports = grammar({ choice($.php_tag, $._eof), ), - text: $ => repeat1(choice( + text: _ => repeat1(choice( token(prec(-1, / prec(-1, ';'), + empty_statement: _ => prec(-1, ';'), - reference_modifier: $ => token('&'), + reference_modifier: _ => '&', function_static_declaration: $ => seq( keyword('static'), @@ -315,9 +315,9 @@ module.exports = grammar({ '}', ), - final_modifier: $ => keyword('final'), - abstract_modifier: $ => keyword('abstract'), - readonly_modifier: $ => keyword('readonly'), + final_modifier: _ => keyword('final'), + abstract_modifier: _ => keyword('abstract'), + readonly_modifier: _ => keyword('readonly'), class_interface_clause: $ => seq( keyword('implements'), @@ -381,8 +381,8 @@ module.exports = grammar({ ), ), - var_modifier: $ => keyword('var', false), - static_modifier: $ => keyword('static'), + var_modifier: _ => keyword('var', false), + static_modifier: _ => keyword('static'), use_declaration: $ => seq( keyword('use'), @@ -423,7 +423,7 @@ module.exports = grammar({ ), ), - visibility_modifier: $ => choice( + visibility_modifier: _ => choice( keyword('public'), keyword('protected'), keyword('private'), @@ -514,13 +514,13 @@ module.exports = grammar({ ), ), - bottom_type: $ => 'never', + bottom_type: _ => 'never', union_type: $ => prec.right(pipeSep1($._types)), intersection_type: $ => ampSep1($._types), - primitive_type: $ => choice( + primitive_type: _ => choice( 'array', keyword('callable'), // not legal in property types 'iterable', @@ -536,7 +536,7 @@ module.exports = grammar({ 'true', ), - cast_type: $ => choice( + cast_type: _ => choice( keyword('array', false), keyword('binary', false), keyword('bool', false), @@ -587,7 +587,7 @@ module.exports = grammar({ $.null, ), - float: $ => /\d*(_\d+)*((\.\d*(_\d+)*)?([eE][\+-]?\d+(_\d+)*)|(\.\d*(_\d+)*)([eE][\+-]?\d+(_\d+)*)?)/, + float: _ => /\d*(_\d+)*((\.\d*(_\d+)*)?([eE][\+-]?\d+(_\d+)*)|(\.\d*(_\d+)*)([eE][\+-]?\d+(_\d+)*)?)/, try_statement: $ => seq( keyword('try'), @@ -623,7 +623,7 @@ module.exports = grammar({ keyword('break'), optional($._expression), $._semicolon, ), - integer: $ => { + integer: _ => { const decimal = /[1-9]\d*(_\d+)*/; const octal = /0[oO]?[0-7]*(_[0-7]+)*/; const hex = /0[xX][0-9a-fA-F]+(_[0-9a-fA-F]+)*/; @@ -1098,8 +1098,6 @@ module.exports = grammar({ $._string, ), - parenthesized_expression: $ => seq('(', $._expression, ')'), - scoped_call_expression: $ => prec(PREC.CALL, seq( field('scope', $._scope_resolution_qualifier), '::', @@ -1115,13 +1113,13 @@ module.exports = grammar({ $._dereferencable_expression, ), - relative_scope: $ => prec(PREC.SCOPE, choice( + relative_scope: _ => prec(PREC.SCOPE, choice( 'self', 'parent', keyword('static'), )), - variadic_placeholder: $ => token('...'), + variadic_placeholder: _ => '...', arguments: $ => seq( '(', @@ -1242,7 +1240,7 @@ module.exports = grammar({ // Note: remember to also update the is_escapable_sequence method in the // external scanner whenever changing these rules - escape_sequence: $ => token.immediate(seq( + escape_sequence: _ => token.immediate(seq( '\\', choice( 'n', @@ -1332,7 +1330,8 @@ module.exports = grammar({ ), ), - _new_line: $ => choice(token(/\r?\n/), token(/\r/)), + // _new_line: _ => choice(token(/\r?\n/), token(/\r/)), + _new_line: _ => /\r?\n|\r/, nowdoc_body: $ => seq($._new_line, choice( @@ -1378,9 +1377,9 @@ module.exports = grammar({ '`', ), - boolean: $ => /[Tt][Rr][Uu][Ee]|[Ff][Aa][Ll][Ss][Ee]/, + boolean: _ => /true|false/i, - null: $ => keyword('null', false), + null: _ => keyword('null', false), _string: $ => choice($.encapsed_string, $.string, $.heredoc, $.nowdoc), @@ -1455,8 +1454,10 @@ module.exports = grammar({ ['*', PREC.TIMES], ['/', PREC.TIMES], ['%', PREC.TIMES], + // @ts-ignore ].map(([op, p]) => prec.left(p, seq( field('left', $._expression), + // @ts-ignore field('operator', op), field('right', $._expression), ))), @@ -1482,15 +1483,15 @@ module.exports = grammar({ $._expression, ), - name: $ => /[_a-zA-Z\u00A1-\u00ff][_a-zA-Z\u00A1-\u00ff\d]*/, + name: _ => /[_a-zA-Z\u00A1-\u00ff][_a-zA-Z\u00A1-\u00ff\d]*/, - _reserved_identifier: $ => choice( + _reserved_identifier: _ => choice( 'self', 'parent', keyword('static'), ), - comment: $ => token(choice( + comment: _ => token(choice( seq( choice('//', /#[^?\[?\r?\n]/), repeat(/[^?\r?\n]|\?[^>\r\n]/), @@ -1508,24 +1509,64 @@ module.exports = grammar({ }, }); +/** + * Creates a regex that matches the given word case-insensitively, + * and will alias the regex to the word if aliasAsWord is true + * + * @param {string} word + * @param {boolean} aliasAsWord? + * + * @return {RegExp|AliasRule} + */ function keyword(word, aliasAsWord = true) { + /** @type {RegExp|AliasRule} */ let result = new RegExp(word, 'i'); if (aliasAsWord) result = alias(result, word); return result; } +/** + * Creates a rule to match one or more of the rules separated by a comma + * + * @param {Rule} rule + * + * @return {SeqRule} + * + */ function commaSep1(rule) { return seq(rule, repeat(seq(',', rule))); } +/** + * Creates a rule to optionally match one or more of the rules separated by a comma + * + * @param {Rule} rule + * + * @return {ChoiceRule} + * + */ function commaSep(rule) { return optional(commaSep1(rule)); } +/** + * Creates a rule to match one or more of the rules separated by a pipe + * + * @param {Rule} rule + * + * @return {SeqRule} + */ function pipeSep1(rule) { return seq(rule, repeat(seq('|', rule))); } +/** + * Creates a rule to match one or more of the rules separated by an ampersand + * + * @param {Rule} rule + * + * @return {SeqRule} + */ function ampSep1(rule) { return seq(rule, repeat(seq(token('&'), rule))); } diff --git a/script/parse-examples b/script/parse-examples index 7e39d6cb..6b26121b 100755 --- a/script/parse-examples +++ b/script/parse-examples @@ -1,39 +1,47 @@ -#!/bin/bash +#!/usr/bin/env bash -set -e +set -eu cd "$(dirname "$0")/.." -function checkout_at() { - path="examples/$1" - url=$2 - sha=$3 - - if [ ! -d "$path" ]; then - git clone "https://github.com/$url" "$path" - fi - - pushd "$path" - git fetch && git reset --hard "$sha" - popd +function clone_repo { + owner=$1 + name=$2 + sha=$3 + + path=examples/$name + if [ ! -d "$path" ]; then + echo "Cloning $owner/$name" + git clone "https://github.com/$owner/$name" "$path" + fi + + pushd "$path" >/dev/null + actual_sha=$(git rev-parse HEAD) + if [ "$actual_sha" != "$sha" ]; then + echo "Updating $owner/$name to $sha" + git fetch + git reset --hard "$sha" + fi + popd >/dev/null } -checkout_at "laravel" "laravel/laravel" "9d0862b3340c8243ee072afc181e315ffa35e110" -checkout_at "phabricator" "phacility/phabricator" "d0b01a41f2498fb2a6487c2d6704dc7acfd4675f" -checkout_at "phpunit" "sebastianbergmann/phpunit" "5e523bdc7dd4d90fed9fb29d1df05347b3e7eaba" -checkout_at "WordPress" "WordPress/WordPress" "45286c5bb3f6fe5005567903ec858d87077eae2c" +clone_repo laravel laravel 9d0862b3340c8243ee072afc181e315ffa35e110 +clone_repo phacility phabricator d0b01a41f2498fb2a6487c2d6704dc7acfd4675f +clone_repo sebastianbergmann phpunit 5e523bdc7dd4d90fed9fb29d1df05347b3e7eaba +clone_repo WordPress WordPress 45286c5bb3f6fe5005567903ec858d87077eae2c -known_failures="$(cat script/known-failures.txt)" +known_failures=$(cat script/known-failures.txt) +# shellcheck disable=2046 tree-sitter parse -q \ - 'examples/**/*.php' \ - $(for file in $known_failures; do echo "!${file}"; done) + examples/**/*.php \ + $(for file in $known_failures; do echo "!${file}"; done) example_count=$(find examples -name '*.php' | wc -l) -failure_count=$(wc -w <<< "$known_failures") -success_count=$(( $example_count - $failure_count )) -success_percent=$(bc -l <<< "100*${success_count}/${example_count}") +failure_count=$(wc -w <<<"$known_failures") +success_count=$(("$example_count" - "$failure_count")) +success_percent=$(bc -l <<<"100*${success_count}/${example_count}") printf \ - "Successfully parsed %d of %d example files (%.1f%%)\n" \ - $success_count $example_count $success_percent + "Successfully parsed %d of %d example files (%.1f%%)\n" \ + "$success_count" "$example_count" "$success_percent" diff --git a/script/parse-examples.js b/script/parse-examples.js index 007ba6b7..769bc5b1 100644 --- a/script/parse-examples.js +++ b/script/parse-examples.js @@ -1,102 +1,104 @@ -const fs = require('fs') -const os = require('os') +const fs = require('fs'); +const os = require('os'); const util = require('util'); -const { exec, execSync } = require('child_process') -const shell = require('shelljs') +const {exec, execSync} = require('child_process'); +const shell = require('shelljs'); function main() { checkoutExampleProjects([ - { dir: 'laravel', repository: 'https://github.com/laravel/laravel', sha: '9d0862b3340c8243ee072afc181e315ffa35e110' }, - { dir: 'framework', repository: 'https://github.com/laravel/framework', sha: '45d439e98a6b14afde8911f7d22a265948adbf72' }, - { dir: 'phabricator', repository: 'https://github.com/phacility/phabricator', sha: 'd0b01a41f2498fb2a6487c2d6704dc7acfd4675f' }, - { dir: 'phpunit', repository: 'https://github.com/sebastianbergmann/phpunit', sha: '5e523bdc7dd4d90fed9fb29d1df05347b3e7eaba' }, - { dir: 'WordPress', repository: 'https://github.com/WordPress/WordPress', sha: '45286c5bb3f6fe5005567903ec858d87077eae2c' }, - { dir: 'mediawiki', repository: 'https://github.com/wikimedia/mediawiki', sha: 'b6b88cbf98fb0c7891324709a85eabc290ed28b4' }, - ]) - - parseExamples() + {dir: 'laravel', repository: 'https://github.com/laravel/laravel', sha: '9d0862b3340c8243ee072afc181e315ffa35e110'}, + {dir: 'framework', repository: 'https://github.com/laravel/framework', sha: '45d439e98a6b14afde8911f7d22a265948adbf72'}, + {dir: 'phabricator', repository: 'https://github.com/phacility/phabricator', sha: 'd0b01a41f2498fb2a6487c2d6704dc7acfd4675f'}, + {dir: 'phpunit', repository: 'https://github.com/sebastianbergmann/phpunit', sha: '5e523bdc7dd4d90fed9fb29d1df05347b3e7eaba'}, + {dir: 'WordPress', repository: 'https://github.com/WordPress/WordPress', sha: '45286c5bb3f6fe5005567903ec858d87077eae2c'}, + {dir: 'mediawiki', repository: 'https://github.com/wikimedia/mediawiki', sha: 'b6b88cbf98fb0c7891324709a85eabc290ed28b4'}, + ]); + + parseExamples(); } function parseExamples() { - const knownFailures = loadKnownFailures().split(/\r\n|\n/).filter(line => line.length > 0) + const knownFailures = loadKnownFailures().split(/\r\n|\n/).filter((line) => line.length > 0); - let excludeString = knownFailures.join(" !") + let excludeString = knownFailures.join(' !'); if (knownFailures.length > 0) { - excludeString = "!" + excludeString + excludeString = '!' + excludeString; } if (os.platform == 'win32') { - excludeString = convertToWindowsPath(excludeString) + excludeString = convertToWindowsPath(excludeString); } exec('"./node_modules/.bin/tree-sitter" parse -q examples/**/*.php ' + excludeString, (err, stdout) => { - const failures = extractFailureFilePaths(stdout) - failures.forEach(failure => { + const failures = extractFailureFilePaths(stdout); + failures.forEach((failure) => { if (!knownFailures.includes(failure)) { - console.error(`Unknown failure occurred: ${failure}`) + console.error(`Unknown failure occurred: ${failure}`); } - }) + }); - const failureCount = failures.length + knownFailures.length - const exampleCount = countExampleFiles() + const failureCount = failures.length + knownFailures.length; + const exampleCount = countExampleFiles(); const successCount = exampleCount - failureCount; - const successPercent = 100 * successCount / exampleCount + const successPercent = 100 * successCount / exampleCount; - console.log(`Successfully parsed ${successCount} of ${exampleCount} example files (${successPercent.toFixed(2)}%)`) - }) + console.log(`Successfully parsed ${successCount} of ${exampleCount} example files (${successPercent.toFixed(2)}%)`); + }); } function extractFailureFilePaths(stdout) { - return stdout.split(/\r\n|\n/).filter(line => line.length > 0).map(line => convertToUnixPath(line.substring(0, line.indexOf(" ")))) + return stdout.split(/\r\n|\n/).filter((line) => line.length > 0).map((line) => convertToUnixPath(line.substring(0, line.indexOf(' ')))); } function convertToUnixPath(filePathString) { - return filePathString.split("\\").join("/") + return filePathString.split('\\').join('/'); } function convertToWindowsPath(filePathString) { - return filePathString.split("/").join("\\") + return filePathString.split('/').join('\\'); } function countExampleFiles() { - return shell.find('./examples').filter((file) => { return file.match(/\.php$/) }).length + return shell.find('./examples').filter((file) => { + return file.match(/\.php$/); + }).length; } function createExamplesDirectoryIfNotExists() { - const dir = './examples' + const dir = './examples'; if (!fs.existsSync(dir)) { - fs.mkdirSync(dir) + fs.mkdirSync(dir); } } function checkoutExampleProjects(projects) { - createExamplesDirectoryIfNotExists() + createExamplesDirectoryIfNotExists(); - checkoutPromise = util.promisify(checkoutExampleProject) + checkoutPromise = util.promisify(checkoutExampleProject); - Promise.all(projects.map((project) => checkoutPromise(project))) + Promise.all(projects.map((project) => checkoutPromise(project))); } function checkoutExampleProject(project) { - const projectDir = './examples/' + project.dir + const projectDir = './examples/' + project.dir; if (!fs.existsSync(projectDir)) { - cloneRepository(project) + cloneRepository(project); } - checkoutCommit(project) + checkoutCommit(project); } function cloneRepository(project) { - execSync('cd examples && git clone --quiet ' + project.repository + ' ' + project.dir + ' && cd ..') + execSync('cd examples && git clone --quiet ' + project.repository + ' ' + project.dir + ' && cd ..'); } function checkoutCommit(project) { - execSync('cd examples/' + project.dir + ' && git fetch --quiet && git reset --quiet --hard "' + project.sha + '" && cd ../..') + execSync('cd examples/' + project.dir + ' && git fetch --quiet && git reset --quiet --hard "' + project.sha + '" && cd ../..'); } function loadKnownFailures() { - return fs.readFileSync('./script/known-failures.txt').toString() + return fs.readFileSync('./script/known-failures.txt').toString(); } -main() \ No newline at end of file +main();