diff --git a/.github/workflows/archive-docs.yml b/.github/workflows/archive-docs.yml new file mode 100644 index 00000000000..c31092b63b0 --- /dev/null +++ b/.github/workflows/archive-docs.yml @@ -0,0 +1,44 @@ +name: Archive documentation + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + archive-docs: + if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" + runs-on: ubuntu-latest + steps: + - name: Configure workflow + id: configuration + run: | + echo "BRANCH_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT + echo "DOCS_OUTPUT_DIR=${GITHUB_WORKSPACE}/skript-docs/docs/archives/${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + echo "DOCS_REPO_DIR=${GITHUB_WORKSPACE}/skript-docs" >> $GITHUB_OUTPUT + echo "SKRIPT_REPO_DIR=${GITHUB_WORKSPACE}/skript" >> $GITHUB_OUTPUT + - name: Checkout Skript + uses: actions/checkout@v4 + with: + submodules: recursive + path: skript + - name: Setup documentation environment + uses: ./skript/.github/workflows/docs/setup-docs + with: + docs_deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} + docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} + - name: Generate documentation + uses: ./skript/.github/workflows/docs/generate-docs + with: + docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} + docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} + skript_repo_dir: ${{ steps.configuration.outputs.SKRIPT_REPO_DIR }} + is_release: true + generate_javadocs: true + - name: Push archive documentation + uses: ./skript/.github/workflows/docs/push-docs + with: + docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} + git_name: Archive Docs Bot + git_email: archivedocs@skriptlang.org + git_commit_message: "Update ${{ steps.configuration.outputs.BRANCH_NAME }} archive docs" diff --git a/.github/workflows/docs/generate-docs/action.yml b/.github/workflows/docs/generate-docs/action.yml index e033996868e..2470f852450 100644 --- a/.github/workflows/docs/generate-docs/action.yml +++ b/.github/workflows/docs/generate-docs/action.yml @@ -69,7 +69,7 @@ runs: cd $SKRIPT_REPO_DIR if [[ "${IS_RELEASE}" == "true" ]]; then - ./gradlew genReleaseDocs releaseJavadoc + ./gradlew genReleaseDocs javadoc elif [[ "${GENERATE_JAVADOCS}" == "true" ]]; then ./gradlew genNightlyDocs javadoc else @@ -77,7 +77,7 @@ runs: fi if [ -d "${DOCS_OUTPUT_DIR}" ]; then - if [[ "${GENERATE_JAVADOCS}" == "true" ]]; then + if [[ "${GENERATE_JAVADOCS}" == "true" ]] || [[ "${IS_RELEASE}" == "true" ]] ; then mkdir -p "${SKRIPT_DOCS_OUTPUT_DIR}/javadocs" && cp -a "./build/docs/javadoc/." "$_" fi diff --git a/.github/workflows/java-17-builds.yml b/.github/workflows/java-17-builds.yml index 2ca6d6bde61..43d6458c946 100644 --- a/.github/workflows/java-17-builds.yml +++ b/.github/workflows/java-17-builds.yml @@ -1,4 +1,4 @@ -name: Java 17 CI (MC 1.17-1.20.4) +name: Java 17 CI (MC 1.19.4-1.20.4) on: push: diff --git a/.github/workflows/java-8-builds.yml b/.github/workflows/java-8-builds.yml deleted file mode 100644 index 0120b950f94..00000000000 --- a/.github/workflows/java-8-builds.yml +++ /dev/null @@ -1,35 +0,0 @@ -name: Java 8 CI (MC 1.13-1.16) - -on: - push: - branches: - - master - - 'dev/**' - pull_request: - -jobs: - build: - if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v2 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'adopt' - cache: gradle - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build Skript and run test scripts - run: ./gradlew clean skriptTestJava8 - - name: Upload Nightly Build - uses: actions/upload-artifact@v4 - if: success() - with: - name: skript-nightly - path: build/libs/* diff --git a/.github/workflows/junit-17-builds.yml b/.github/workflows/junit-17-builds.yml index 238bd9b132e..7bbe8952eb3 100644 --- a/.github/workflows/junit-17-builds.yml +++ b/.github/workflows/junit-17-builds.yml @@ -1,4 +1,4 @@ -name: JUnit (MC 1.17-1.20.4) +name: JUnit (MC 1.19.4-1.20.4) on: push: diff --git a/.github/workflows/junit-21-builds.disabled b/.github/workflows/junit-21-builds.disabled deleted file mode 100644 index 07bc5bebbeb..00000000000 --- a/.github/workflows/junit-21-builds.disabled +++ /dev/null @@ -1,31 +0,0 @@ -# Disabled as EasyMock 5.2.0 is required for Java 21 support -# However, we are currently using 5.0.1 (see https://github.com/SkriptLang/Skript/pull/6204#discussion_r1405302009) -name: JUnit (MC 1.20.6+) - -on: - push: - branches: - - master - - 'dev/**' - pull_request: - -jobs: - build: - if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: validate gradle wrapper - uses: gradle/wrapper-validation-action@v2 - - name: Set up JDK 21 - uses: actions/setup-java@v4 - with: - java-version: '21' - distribution: 'adopt' - cache: gradle - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build Skript and run JUnit - run: ./gradlew clean JUnitJava21 diff --git a/.github/workflows/junit-8-builds.yml b/.github/workflows/junit-21-builds.yml similarity index 91% rename from .github/workflows/junit-8-builds.yml rename to .github/workflows/junit-21-builds.yml index ec2fef19869..1eeee5846dc 100644 --- a/.github/workflows/junit-8-builds.yml +++ b/.github/workflows/junit-21-builds.yml @@ -1,4 +1,4 @@ -name: JUnit (MC 1.13-1.16) +name: JUnit (MC 1.20.6+) on: push: @@ -26,4 +26,4 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build Skript and run JUnit - run: ./gradlew clean JUnitJava8 + run: ./gradlew clean JUnitJava21 diff --git a/.github/workflows/release-docs.yml b/.github/workflows/release-docs.yml index f6f7ebc8a27..9cfe198ba93 100644 --- a/.github/workflows/release-docs.yml +++ b/.github/workflows/release-docs.yml @@ -3,6 +3,7 @@ name: Release documentation on: release: types: [published] + workflow_dispatch: jobs: release-docs: @@ -33,6 +34,7 @@ jobs: docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} skript_repo_dir: ${{ steps.configuration.outputs.SKRIPT_REPO_DIR }} is_release: true + generate_javadocs: true cleanup_pattern: "!(nightly|archives|templates)" - name: Push release documentation uses: ./skript/.github/workflows/docs/push-docs @@ -41,40 +43,3 @@ jobs: git_name: Release Docs Bot git_email: releasedocs@skriptlang.org git_commit_message: "Update release docs to ${{ steps.configuration.outputs.BRANCH_NAME }}" - - archive-docs: - if: "! contains(toJSON(github.event.commits.*.message), '[ci skip]')" - needs: release-docs - runs-on: ubuntu-latest - steps: - - name: Configure workflow - id: configuration - run: | - echo "BRANCH_NAME=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT - echo "DOCS_OUTPUT_DIR=${GITHUB_WORKSPACE}/skript-docs/docs/archives/${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT - echo "DOCS_REPO_DIR=${GITHUB_WORKSPACE}/skript-docs" >> $GITHUB_OUTPUT - echo "SKRIPT_REPO_DIR=${GITHUB_WORKSPACE}/skript" >> $GITHUB_OUTPUT - - name: Checkout Skript - uses: actions/checkout@v4 - with: - submodules: recursive - path: skript - - name: Setup documentation environment - uses: ./skript/.github/workflows/docs/setup-docs - with: - docs_deploy_key: ${{ secrets.DOCS_DEPLOY_KEY }} - docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} - - name: Generate documentation - uses: ./skript/.github/workflows/docs/generate-docs - with: - docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} - docs_output_dir: ${{ steps.configuration.outputs.DOCS_OUTPUT_DIR }} - skript_repo_dir: ${{ steps.configuration.outputs.SKRIPT_REPO_DIR }} - is_release: true - - name: Push archive documentation - uses: ./skript/.github/workflows/docs/push-docs - with: - docs_repo_dir: ${{ steps.configuration.outputs.DOCS_REPO_DIR }} - git_name: Archive Docs Bot - git_email: archivedocs@skriptlang.org - git_commit_message: "Update ${{ steps.configuration.outputs.BRANCH_NAME }} archive docs" diff --git a/README.md b/README.md index 2dba8104819..bd1e1375345 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,11 @@ Skript requires **Spigot** to work. You heard it right, **CraftBukkit** does *no **Paper**, which is a fork of Spigot, is recommended; it is required for some parts of Skript to be available. -Skript supports only the **latest** patch versions of Minecraft 1.9+. -For example, this means that 1.16.5 is supported, but 1.16.4 is *not*. +Skript supports only the **latest** patch versions of Minecraft 1.19 and newer. +For example, this means that 1.19.4 is supported, but 1.19.3 is *not*. Testing with all old patch versions is not feasible for us. -Minecraft 1.8 and earlier are not, and will not be supported. New Minecraft +Minecraft 1.12 and earlier are not, and will not be supported. New Minecraft versions will be supported as soon as possible. ## Download @@ -77,15 +77,14 @@ Skript has some tests written in Skript. Running them requires a Minecraft server, but our build script will create one for you. Running the tests is easy: ``` -./gradlew (quickTest|skriptTest|skriptTestJava8|skriptTestJava17|skriptTestJava21) +./gradlew (quickTest|skriptTest|skriptTestJava17|skriptTestJava21) ``` quickTest runs the test suite on newest supported server version. skriptTestJava21 (1.20.6+) runs the tests on Java 21 supported versions. -skriptTestJava17 (1.17-1.20.4) runs the tests on Java 17 supported versions. -skriptTestJava8 (1.13-1.16) runs the tests on Java 8 supported versions. +skriptTestJava17 (1.19.4-1.20.4) runs the tests on Java 17 supported versions. skriptTest runs the tests on all versions. -That is, it runs skriptTestJava8, skriptTestJava17, and skriptTestJava21. +That is, it runs skriptTestJava17, and skriptTestJava21. By running the tests, you agree to Mojang's End User License Agreement. @@ -164,7 +163,7 @@ dependencies { } ``` -An example of the version tag would be ```dev37c```. +An example of the version tag would be ```2.8.5```. > Note: If Gradle isn't able to resolve Skript's dependencies, just [disable the resolution of transitive dependencies](https://docs.gradle.org/current/userguide/resolution_rules.html#sec:disabling_resolution_transitive_dependencies) for Skript in your project. diff --git a/build.gradle b/build.gradle index fcd904d9863..069378c56f3 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,6 @@ import java.time.LocalTime plugins { id 'com.github.johnrengelman.shadow' version '8.1.1' - id 'com.github.hierynomus.license' version '0.16.1' id 'maven-publish' id 'java' } @@ -30,7 +29,7 @@ dependencies { shadow group: 'org.bstats', name: 'bstats-bukkit', version: '3.0.2' shadow group: 'net.kyori', name: 'adventure-text-serializer-bungeecord', version: '4.3.2' - implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.20.6-R0.1-SNAPSHOT' + implementation group: 'io.papermc.paper', name: 'paper-api', version: '1.21-R0.1-SNAPSHOT' implementation group: 'org.eclipse.jdt', name: 'org.eclipse.jdt.annotation', version: '2.2.700' implementation group: 'com.google.code.findbugs', name: 'findbugs', version: '3.0.1' implementation group: 'com.sk89q.worldguard', name: 'worldguard-legacy', version: '7.0.0-SNAPSHOT' @@ -41,7 +40,7 @@ dependencies { implementation fileTree(dir: 'lib', include: '*.jar') testShadow group: 'junit', name: 'junit', version: '4.13.2' - testShadow group: 'org.easymock', name: 'easymock', version: '5.0.1' + testShadow group: 'org.easymock', name: 'easymock', version: '5.4.0' } task checkAliases { @@ -55,7 +54,7 @@ task checkAliases { } task testJar(type: ShadowJar) { - dependsOn(compileTestJava, licenseTest) + dependsOn(compileTestJava) archiveFileName = 'Skript-JUnit.jar' from sourceSets.test.output, sourceSets.main.output, project.configurations.testShadow } @@ -71,7 +70,7 @@ task build(overwrite: true, type: ShadowJar) { from sourceSets.main.output } -// Excludes the tests for the build task. Should be using junit, junitJava17, junitJava8, skriptTest, quickTest. +// Excludes the tests for the build task. Should be using JUnitQuick, JUnitJava21, JUnitJava17, skriptTest, quickTest. // We do not want tests to run for building. That's time consuming and annoying. Especially in development. test { exclude '**/*' @@ -141,24 +140,6 @@ publishing { } } -license { - header file('licenseheader.txt') - exclude('**/Metrics.java') // Not under GPLv3 - exclude('**/BurgerHelper.java') // Not exclusively GPLv3 - exclude('**/*.sk') // Sample scripts and maybe aliases - exclude('**/*.lang') // Language files do not have headers (still under GPLv3) - exclude('**/*.json') // JSON files do not have headers -} - -task releaseJavadoc(type: Javadoc) { - title = project.name + ' ' + project.property('version') - source = sourceSets.main.allJava - classpath = configurations.compileClasspath - options.encoding = 'UTF-8' - // currently our javadoc has a lot of errors, so we need to suppress the linter - options.addStringOption('Xdoclint:none', '-quiet') -} - // Task to check that test scripts are named correctly tasks.register('testNaming') { doLast { @@ -198,13 +179,12 @@ void createTestTask(String name, String desc, String environments, int javaVersi if (junit) { artifact += 'Skript-JUnit.jar' } else if (releaseDocs) { - artifact += 'Skript-github.jar' + artifact += 'Skript-' + version + '.jar' } else { artifact += 'Skript-nightly.jar' } tasks.register(name, JavaExec) { description = desc - dependsOn licenseTest if (junit) { dependsOn testJar } else if (releaseDocs) { @@ -244,7 +224,7 @@ void createTestTask(String name, String desc, String environments, int javaVersi if (!gradle.taskGraph.hasTask(":tasks") && !gradle.startParameter.dryRun && modifiers.contains(Modifiers.PROFILE)) { if (!project.hasProperty('profiler')) throw new MissingPropertyException('Add parameter -Pprofiler=', 'profiler', String.class) - + args += '-agentpath:' + project.property('profiler') + '=port=8849,nowait' } } @@ -253,14 +233,13 @@ void createTestTask(String name, String desc, String environments, int javaVersi def java21 = 21 def java17 = 17 -def java8 = 8 -def latestEnv = 'java21/paper-1.20.6.json' +def latestEnv = 'java21/paper-1.21.0.json' def latestJava = java21 -def oldestJava = java8 +def oldestJava = java17 -def latestJUnitEnv = 'java17/paper-1.20.4.json' -def latestJUnitJava = java17 +def latestJUnitEnv = latestEnv +def latestJUnitJava = latestJava java { toolchain.languageVersion.set(JavaLanguageVersion.of(latestJava)) @@ -280,25 +259,21 @@ int envJava = project.property('testEnvJavaVersion') == null ? latestJava : Inte createTestTask('quickTest', 'Runs tests on one environment being the latest supported Java and Minecraft.', environments + latestEnv, latestJava, 0) createTestTask('skriptTestJava21', 'Runs tests on all Java 21 environments.', environments + 'java21', java21, 0) createTestTask('skriptTestJava17', 'Runs tests on all Java 17 environments.', environments + 'java17', java17, 0) -createTestTask('skriptTestJava8', 'Runs tests on all Java 8 environments.', environments + 'java8', java8, 0) createTestTask('skriptTestDev', 'Runs testing server and uses \'system.in\' for command input, stop server to finish.', environments + env, envJava, 0, Modifiers.DEV_MODE, Modifiers.DEBUG) createTestTask('skriptProfile', 'Starts the testing server with JProfiler support.', environments + latestEnv, latestJava, -1, Modifiers.PROFILE) createTestTask('genNightlyDocs', 'Generates the Skript documentation website html files.', environments + env, envJava, 0, Modifiers.GEN_NIGHTLY_DOCS) createTestTask('genReleaseDocs', 'Generates the Skript documentation website html files for a release.', environments + env, envJava, 0, Modifiers.GEN_RELEASE_DOCS) tasks.register('skriptTest') { description = 'Runs tests on all environments.' - dependsOn skriptTestJava8, skriptTestJava17, skriptTestJava21 + dependsOn skriptTestJava17, skriptTestJava21 } createTestTask('JUnitQuick', 'Runs JUnit tests on one environment being the latest supported Java and Minecraft.', environments + latestJUnitEnv, latestJUnitJava, 0, Modifiers.JUNIT) -// Disabled as EasyMock 5.2.0 is required for Java 21 support -// However, we are currently using 5.0.1 (see https://github.com/SkriptLang/Skript/pull/6204#discussion_r1405302009) -//createTestTask('JUnitJava21', 'Runs JUnit tests on all Java 21 environments.', environments + 'java21', java21, 0, Modifiers.JUNIT) +createTestTask('JUnitJava21', 'Runs JUnit tests on all Java 21 environments.', environments + 'java21', java21, 0, Modifiers.JUNIT) createTestTask('JUnitJava17', 'Runs JUnit tests on all Java 17 environments.', environments + 'java17', java17, 0, Modifiers.JUNIT) -createTestTask('JUnitJava8', 'Runs JUnit tests on all Java 8 environments.', environments + 'java8', java8, 0, Modifiers.JUNIT) tasks.register('JUnit') { description = 'Runs JUnit tests on all environments.' - dependsOn JUnitJava8, JUnitJava17//, JUnitJava21 + dependsOn JUnitJava17, JUnitJava21 } // Build flavor configurations @@ -307,7 +282,7 @@ task githubResources(type: ProcessResources) { include '**' version = project.property('version') def channel = 'stable' - if (version.contains('pre')) + if (version.contains('-')) channel = 'prerelease' filter ReplaceTokens, tokens: [ 'version' : version, @@ -340,7 +315,7 @@ task spigotResources(type: ProcessResources) { include '**' version = project.property('version') def channel = 'stable' - if (version.contains('pre')) + if (version.contains('-')) channel = 'prerelease' filter ReplaceTokens, tokens: [ 'version' : version, @@ -378,7 +353,7 @@ task nightlyResources(type: ProcessResources) { 'today' : '' + LocalTime.now(), 'release-flavor' : 'skriptlang-nightly', // SkriptLang build, automatically done by CI 'release-channel' : 'prerelease', // No update checking, but these are VERY unstable - 'release-updater' : 'ch.njol.skript.update.NoUpdateChecker', // No autoupdates for now + 'release-updater' : 'ch.njol.skript.update.NoUpdateChecker', // No auto updates for now 'release-source' : '', 'release-download': 'null' ] @@ -388,7 +363,7 @@ task nightlyResources(type: ProcessResources) { task nightlyRelease(type: ShadowJar) { from sourceSets.main.output - dependsOn nightlyResources, licenseMain + dependsOn nightlyResources archiveFileName = 'Skript-nightly.jar' manifest { attributes( @@ -400,8 +375,8 @@ task nightlyRelease(type: ShadowJar) { } javadoc { - dependsOn nightlyResources - + mustRunAfter(tasks.withType(ProcessResources)) + title = 'Skript ' + project.property('version') source = sourceSets.main.allJava exclude("ch/njol/skript/conditions/**") @@ -414,10 +389,9 @@ javadoc { exclude("ch/njol/skript/lang/function/ExprFunctionCall.java") exclude("ch/njol/skript/hooks/**") exclude("ch/njol/skript/test/**") - + classpath = configurations.compileClasspath + sourceSets.main.output options.encoding = 'UTF-8' // currently our javadoc has a lot of errors, so we need to suppress the linter options.addStringOption('Xdoclint:none', '-quiet') } - diff --git a/code-conventions.md b/code-conventions.md index f571429ddb7..c9b70f8d5a4 100644 --- a/code-conventions.md +++ b/code-conventions.md @@ -66,6 +66,9 @@ With the exception of contacting our own resources (e.g. to check for updates) c Code contributed must be licensed under GPLv3, by **you**. We expect that any code you contribute is either owned by you or you have explicit permission to provide and license it to us. +Licenses do not need to be printed in individual files (or packages) unless the licence applying to the code in +that file (or package) deviates from the licence scope of its containing package. + Third party code (under a compatible licence) _may_ be accepted in the following cases: - It is part of a public, freely-available library or resource. - It is somehow necessary to your contribution, and you have been given permission to include it. @@ -75,34 +78,61 @@ If we receive complaints regarding the licensing of a contribution we will forwa If you have questions or complaints regarding the licensing or reproduction of a contribution you may contact us (the organisation) or the contributor of that code directly. -If, in the future, we need to relicense contributed code, we will contact all contributors involved. +If, in the future, we need to re-license contributed code, we will contact all contributors involved. If we need to remove or alter contributed code due to a licensing issue we will attempt to notify its contributor. ## Code Style ### Formatting +* Imports should be grouped together by type (e.g. all `java.lang...` imports together) + * Following the style of existing imports in a class is encouraged, but not required + * Wildcard `*` imports are permitted (as long as they do not interfere with existing imports), e.g. `java.lang.*`. * Tabs, no spaces (unless in code imported from other projects) -** No tabs/spaces in empty lines + - No tabs/spaces in empty lines * No trailing whitespace * At most 120 characters per line - - In Javadoc/multiline comments, at most 80 characters per line -* When statements consume multiple lines, all lines but first have two tabs of additional indentation + - In Javadoc/multiline comments, at most 80 characters per line +* When statements consume multiple lines, all lines but the first have two tabs of additional indentation + - The exception to this is breaking up conditional statements (e.g. `if (x || y)`) where the + condition starts may be aligned * Each class begins with an empty line * No squeezing of multiple lines of code on a single line * Separate method declarations with empty lines - - Empty line after last method in a class is *not* required - - Otherwise, empty line before and after method is a good rule of thumb + - Empty line after last method in a class is *not* required + - Otherwise, empty line before and after method is a good rule of thumb * If fields have Javadoc, separate them with empty lines * Use empty lines liberally inside methods to improve readability * Use curly brackets to start and end most blocks - - Only when a conditional block (if or else) contains a single statement, they may be omitted - - When omitting brackets, still indent as if the code had brackets - - Avoid omitting brackets if it produces hard-to-read code + - When a block contains a single statement, they may be omitted + - Brackets may not be omitted in a chain of other blocks that require brackets, e.g `if ... else {}` + - When omitting brackets, still indent as if the code had brackets + - Avoid omitting brackets if it produces hard-to-read or ambiguous code +* Ternaries should be avoided where it makes the code complex or difficult to read * Annotations for methods and classes are placed in lines before their declarations, one per line -* When there are multiple annotations, place them in order: - - @Override -> @Nullable -> @SuppressWarnings - - For other annotations, doesn't matter; let your IDE decide + - Annotations for a structure go on the line before that structure + ```java + @Override + @SuppressWarnings("xyz") + public void myMethod() { + // Override goes above method because method is overriding + } + ``` + + - Annotations for the _value_ of a thing go before that value's type declaration + ```java + @Override + public @Nullable Object myMethod() { + // Nullable goes before Object because Object is Nullable + } + ``` +* When there are multiple annotations, it looks nicer to place them in length order (longest last) +but this is not strictly required: + ```java + @Override + @Deprecated + @SuppressWarnings("xyz") + ``` * When splitting Strings into multiple lines the last part of the string must be (space character included) " " + ```java String string = "example string " + @@ -111,6 +141,7 @@ If we need to remove or alter contributed code due to a licensing issue we will * When extending one of following classes: SimpleExpression, SimplePropertyExpression, Effect, Condition... - Put overridden methods in order + - Put static registration before all methods - SimpleExpression: init -> get/getAll -> acceptChange -> change -> setTime -> getTime -> isSingle -> getReturnType -> toString - SimplePropertyExpression: -> init -> convert -> acceptChange -> change -> setTime -> getTime -> getReturnType -> getPropertyName - Effect: init -> execute -> toString @@ -130,8 +161,8 @@ If we need to remove or alter contributed code due to a licensing issue we will * Use prefixes only where their use has been already established (such as `ExprSomeRandomThing`) - Otherwise, use postfixes where necessary - Common occurrences include: Struct (Structure), Sec (Section), EffSec (EffectSection), Eff (Effect), Cond (Condition), Expr (Expression) -* Ensure variable/field names are descriptive. Avoid using shorthand names like `e`, or `c`. - - e.g. Event should be `event`, not `e`. `e` is ambiguous and could mean a number of things. +* Ensure variable/field names are descriptive. Avoid using shorthand names like `e`, or `c` + - e.g. Event should be `event`, not `e`. `e` is ambiguous and could mean a number of things ### Comments * Prefer to comment *why* you're doing things instead of how you're doing them @@ -163,33 +194,78 @@ Your comments should look something like these: ## Language Features ### Compatibility -* Contributions should maintain Java 8 source/binary compatibility, even though compiling Skript requires Java 21 - - Users must not need JRE newer than version 8 +* Contributions should maintain Java 17 source/binary compatibility, even though compiling Skript requires Java 21 + - Users must not need JRE newer than version 17 * Versions up to and including Java 21 should work too - Please avoid using unsafe reflection * It is recommended to make fields final, if they are effectively final -* Local variables and method parameters should not be declared final +* Local variables and method parameters should not be declared final unless used in anonymous classes, lambdas +or try-with-resources sections where their immutability is necessary * Methods should be declared final only where necessary * Use `@Override` whenever applicable - -### Nullness + - They may be omitted to prevent compilation errors when something overrides only + on a version-dependent basis (e.g. in Library XYZ version 2 we override `getX()` but in version 3 it's + gone, and we call it ourselves) + +### null-ness +* We use **JetBrains** Annotations for specifying null-ness and method contracts. + * If editing a file using a different annotation set (e.g. Javax, Eclipse Sisu, Bukkit) + these should be replaced with their JetBrains equivalent. + * The semantics for JetBrains Annotations are strict _and should be observed!_ + * Many IDEs have built-in compiler-level support for these, and can even be set to produce strict + errors when an annotation is misused; do not misuse them. + * **`@NotNull`** + * > An element annotated with NotNull claims null value is forbidden to return (for methods), + pass to (parameters) and hold (local variables and fields). + * Something is `@NotNull` iff it is never null from its inception (new X) to its garbage collection, + i.e. there is no point in time at which the value could ever be null. + * **`@Nullable`** + * > An element annotated with Nullable claims null value is perfectly valid to return (for methods), + > pass to (parameters) or hold in (local variables and fields). + > + > By convention, this annotation applied only when the value should always be checked + > against null because the developer could do nothing to prevent null from happening. + * Something is `@Nullable` iff there is _absolutely no way of determining_ (other than checking its + value `!= null`) whether it is null. + * In other words, if there is another way of knowing (e.g. you set it yourself, an `isPresent` method, etc.) + then it should not be marked nullable. + * **`@Contract`** + * The contract annotation should be used to express other behaviour (e.g. null depending on parameters). * All fields, method parameters and their return values are non-null by default - - Exceptions: Github API JSON mappings, Metrics -* When something is nullable, mark it as so -* Only ignore nullness errors when a variable is effectively non-null - if in doubt: check - - Most common example is syntax elements, which are not initialised using a constructor + - Exceptions: GitHub API JSON mappings, Metrics +* When ignoring warnings, use the no-inspection comment rather than a blanket suppression annotation * Use assertions liberally: if you're sure something is not null, assert so to the compiler - Makes finding bugs easier for developers -* Assertions must **not** have side-effects - they may be skipped in real environments +* Assertions must **not** have side-effects in non-test packages - they may be skipped in real environments * Avoid checking non-null values for nullability - Unless working around buggy addons, it is rarely necessary - - This is why ignoring nullness errors is particularly dangerous + - This is why ignoring null-ness errors is particularly dangerous +* Annotations on array types **must** be placed properly: + * Annotations on the array itself go before the array brackets + ```java + @Nullable Object @NotNull [] + // a not-null array of nullable objects + ``` + * Annotations on values inside the array go before the value declaration + ```java + @NotNull Object @Nullable [] + // a nullable array of not-null objects + ``` + * If this is not adhered to, an IDE may provide incorrect feedback. ### Assertions Skript must run with assertations enabled; use them in your development environment. \ The JVM flag -ea is used to enable them. +## Code Complexity + +Dense, highly-complex code should be avoided to preserve readability and to help with future maintenance, +especially within a single method body. + +There are many available metrics for measuring code complexity (for different purposes); [we have our own](https://stable.skriptlang.org/Radical_Complexity.pdf). +There are no strict limits for code complexity, but you may be encouraged (or required) to reformat or break up methods +into smaller, more manageable chunks. If in doubt, keep things simple. ## Minecraft Features diff --git a/gradle.properties b/gradle.properties index 7753116b0ea..a620b360bd5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ org.gradle.parallel=true groupid=ch.njol name=skript -version=2.8.5 +version=2.9.2 jarName=Skript.jar -testEnv=java21/paper-1.20.6 +testEnv=java21/paper-1.21.0 testEnvJavaVersion=21 diff --git a/licenseheader.txt b/licenseheader.txt deleted file mode 100644 index 15be760be16..00000000000 --- a/licenseheader.txt +++ /dev/null @@ -1,16 +0,0 @@ - This file is part of Skript. - - Skript is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - Skript is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with Skript. If not, see . - -Copyright Peter Güttinger, SkriptLang team and contributors \ No newline at end of file diff --git a/src/main/java/ch/njol/skript/ScriptLoader.java b/src/main/java/ch/njol/skript/ScriptLoader.java index 3d8465dc400..afafa0b2def 100644 --- a/src/main/java/ch/njol/skript/ScriptLoader.java +++ b/src/main/java/ch/njol/skript/ScriptLoader.java @@ -964,7 +964,7 @@ public static ArrayList loadItems(SectionNode node) { if (subNode instanceof SimpleNode) { long start = System.currentTimeMillis(); - Statement stmt = Statement.parse(expr, "Can't understand this condition/effect: " + expr); + Statement stmt = Statement.parse(expr, items, "Can't understand this condition/effect: " + expr); if (stmt == null) continue; long requiredTime = SkriptConfig.longParseTimeWarningThreshold.value().getMilliSeconds(); diff --git a/src/main/java/ch/njol/skript/Skript.java b/src/main/java/ch/njol/skript/Skript.java index 9c929cff15f..b8f859c786a 100644 --- a/src/main/java/ch/njol/skript/Skript.java +++ b/src/main/java/ch/njol/skript/Skript.java @@ -1,21 +1,3 @@ -/** - * This file is part of Skript. - * - * Skript is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Skript is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Skript. If not, see . - * - * Copyright Peter Güttinger, SkriptLang team and contributors - */ package ch.njol.skript; import ch.njol.skript.aliases.Aliases; @@ -34,6 +16,7 @@ import ch.njol.skript.events.EvtSkript; import ch.njol.skript.hooks.Hook; import ch.njol.skript.lang.Condition; +import ch.njol.skript.lang.Condition.ConditionType; import ch.njol.skript.lang.Effect; import ch.njol.skript.lang.Expression; import ch.njol.skript.lang.ExpressionInfo; @@ -59,6 +42,7 @@ import ch.njol.skript.log.Verbosity; import ch.njol.skript.registrations.Classes; import ch.njol.skript.registrations.EventValues; +import ch.njol.skript.registrations.Feature; import ch.njol.skript.test.runner.EffObjectives; import ch.njol.skript.test.runner.SkriptJUnitTest; import ch.njol.skript.test.runner.SkriptTestEvent; @@ -83,13 +67,13 @@ import ch.njol.util.Kleenean; import ch.njol.util.NullableChecker; import ch.njol.util.StringUtils; -import ch.njol.util.coll.CollectionUtils; import ch.njol.util.coll.iterator.CheckedIterator; import ch.njol.util.coll.iterator.EnumerationIterable; + import com.google.common.collect.Lists; import com.google.gson.Gson; + import org.bstats.bukkit.Metrics; -import org.bstats.charts.SimplePie; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Material; @@ -107,16 +91,19 @@ import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPlugin; -import org.eclipse.jdt.annotation.Nullable; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.UnknownNullability; import org.junit.After; import org.junit.runner.JUnitCore; import org.junit.runner.Result; +import org.skriptlang.skript.bukkit.SkriptMetrics; import org.skriptlang.skript.commands.CommandModule; import org.skriptlang.skript.lang.comparator.Comparator; import org.skriptlang.skript.lang.comparator.Comparators; import org.skriptlang.skript.lang.converter.Converter; import org.skriptlang.skript.lang.converter.Converters; import org.skriptlang.skript.lang.entry.EntryValidator; +import org.skriptlang.skript.lang.experiment.ExperimentRegistry; import org.skriptlang.skript.lang.script.Script; import org.skriptlang.skript.lang.structure.Structure; import org.skriptlang.skript.lang.structure.StructureInfo; @@ -170,7 +157,7 @@ *

* Once you made sure that Skript is loaded you can use Skript.getInstance() whenever you need a reference to the plugin, but you likely won't need it since all API * methods are static. - * + * * @author Peter Güttinger * @see #registerAddon(JavaPlugin) * @see #registerCondition(Class, String...) @@ -183,34 +170,34 @@ * @see Converters#registerConverter(Class, Class, Converter) */ public final class Skript extends JavaPlugin implements Listener { - + // ================ PLUGIN ================ - + @Nullable private static Skript instance = null; - + private static boolean disabled = false; private static boolean partDisabled = false; - + public static Skript getInstance() { final Skript i = instance; if (i == null) throw new IllegalStateException(); return i; } - + /** * Current updater instance used by Skript. */ @Nullable private SkriptUpdater updater; - + public Skript() throws IllegalStateException { if (instance != null) throw new IllegalStateException("Cannot create multiple instances of Skript!"); instance = this; } - + private static Version minecraftVersion = new Version(666), UNKNOWN_VERSION = new Version(666); private static ServerPlatform serverPlatform = ServerPlatform.BUKKIT_UNKNOWN; // Start with unknown... onLoad changes this @@ -228,24 +215,26 @@ public static void updateMinecraftVersion() { minecraftVersion = new Version("" + m.group()); } } - + @Nullable private static Version version = null; - + @Deprecated(forRemoval = true) // TODO this field will be replaced by a proper registry later + private static @UnknownNullability ExperimentRegistry experimentRegistry; + public static Version getVersion() { final Version v = version; if (v == null) throw new IllegalStateException(); return v; } - + public static final Message m_invalid_reload = new Message("skript.invalid reload"), m_finished_loading = new Message("skript.finished loading"), m_no_errors = new Message("skript.no errors"), m_no_scripts = new Message("skript.no scripts"); private static final PluralizingArgsMessage m_scripts_loaded = new PluralizingArgsMessage("skript.scripts loaded"); - + public static ServerPlatform getServerPlatform() { if (classExists("net.glowstone.GlowServer")) { return ServerPlatform.BUKKIT_GLOWSTONE; // Glowstone has timings too, so must check for it first @@ -271,7 +260,7 @@ private static boolean using32BitJava() { // Property returned should either be "Java HotSpot(TM) 32-Bit Server VM" or "OpenJDK 32-Bit Server VM" if 32-bit and using OracleJDK/OpenJDK return System.getProperty("java.vm.name").contains("32"); } - + /** * Checks if server software and Minecraft version are supported. * Prints errors or warnings to console if something is wrong. @@ -288,7 +277,7 @@ private static boolean checkServerPlatform() { minecraftVersion = new Version("" + m.group()); } Skript.debug("Loading for Minecraft " + minecraftVersion); - + // Check that MC version is supported if (!isRunningMinecraft(1, 9)) { // Prevent loading when not running at least Minecraft 1.9 @@ -297,7 +286,7 @@ private static boolean checkServerPlatform() { Skript.error("Note that those versions are, of course, completely unsupported!"); return false; } - + // Check that current server platform is somewhat supported serverPlatform = getServerPlatform(); Skript.debug("Server platform: " + serverPlatform); @@ -317,7 +306,7 @@ private static boolean checkServerPlatform() { Skript.warning("It will still probably work, but if it does not, you are on your own."); Skript.warning("Skript officially supports Paper and Spigot."); } - + // If nothing got triggered, everything is probably ok return true; } @@ -329,7 +318,7 @@ private static boolean checkServerPlatform() { * Checks whether a hook has been enabled. * @param hook The hook to check. * @return Whether the hook is enabled. - * @see #disableHookRegistration(Class[]) + * @see #disableHookRegistration(Class[]) */ public static boolean isHookEnabled(Class> hook) { return !disabledHookRegistrations.contains(hook); @@ -347,7 +336,7 @@ public static boolean isFinishedLoadingHooks() { * Disables the registration for the given hook classes. If Skript has been enabled, this method * will throw an API exception. It should be used in something like {@link JavaPlugin#onLoad()}. * @param hooks The hooks to disable the registration of. - * @see #isHookEnabled(Class) + * @see #isHookEnabled(Class) */ @SafeVarargs public static void disableHookRegistration(Class>... hooks) { @@ -363,6 +352,13 @@ public static void disableHookRegistration(Class>... hooks) { */ private File scriptsFolder; + /** + * @return The manager for experimental, optional features. + */ + public static ExperimentRegistry experiments() { + return experimentRegistry; + } + /** * @return The folder containing all Scripts. */ @@ -372,7 +368,7 @@ public File getScriptsFolder() { scriptsFolder.mkdirs(); return scriptsFolder; } - + @Override public void onEnable() { Bukkit.getPluginManager().registerEvents(this, this); @@ -381,11 +377,11 @@ public void onEnable() { setEnabled(false); return; } - + handleJvmArguments(); // JVM arguments - + version = new Version("" + getDescription().getVersion()); // Skript version - + // Start the updater // Note: if config prohibits update checks, it will NOT do network connections try { @@ -393,7 +389,9 @@ public void onEnable() { } catch (Exception e) { Skript.exception(e, "Update checker could not be initialized."); } - + experimentRegistry = new ExperimentRegistry(this); + Feature.registerAll(getAddonInstance(), experimentRegistry); + if (!getDataFolder().isDirectory()) getDataFolder().mkdirs(); @@ -469,7 +467,7 @@ public void onEnable() { // initialize the Skript addon instance getAddonInstance(); - + // Load classes which are always safe to use new JavaClasses(); // These may be needed in configuration @@ -488,15 +486,15 @@ public void onEnable() { } catch (Throwable e) { classLoadError = e; } - + // Config must be loaded after Java and Skript classes are parseable // ... but also before platform check, because there is a config option to ignore some errors SkriptConfig.load(); - + // Now override the verbosity if test mode is enabled if (TestMode.VERBOSITY != null) SkriptLogger.setVerbosity(Verbosity.valueOf(TestMode.VERBOSITY)); - + // Use the updater, now that it has been configured to (not) do stuff if (updater != null) { CommandSender console = Bukkit.getConsoleSender(); @@ -518,29 +516,29 @@ public void onEnable() { throw e; // Uh oh, this shouldn't happen. Re-throw the error. } } - + // If loading can continue (platform ok), check for potentially thrown error if (classLoadError != null) { exception(classLoadError); setEnabled(false); return; } - + PluginCommand skriptCommand = getCommand("skript"); assert skriptCommand != null; // It is defined, unless build is corrupted or something like that skriptCommand.setExecutor(new SkriptCommand()); skriptCommand.setTabCompleter(new SkriptCommandTabCompleter()); - + // Load Bukkit stuff. It is done after platform check, because something might be missing! new BukkitEventValues(); - + new DefaultComparators(); new DefaultConverters(); new DefaultFunctions(); new DefaultOperations(); - + ChatMessages.registerListeners(); - + try { getAddonInstance().loadClasses("ch.njol.skript", "conditions", "effects", "events", "expressions", "entity", "sections", "structures"); @@ -552,17 +550,17 @@ public void onEnable() { } Commands.registerListeners(); - + if (logNormal()) info(" " + Language.get("skript.copyright")); - + final long tick = testing() ? Bukkit.getWorlds().get(0).getFullTime() : 0; Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() { @SuppressWarnings("synthetic-access") @Override public void run() { assert Bukkit.getWorlds().get(0).getFullTime() == tick; - + // Load hooks from Skript jar try { try (JarFile jar = new JarFile(getFile())) { @@ -590,7 +588,7 @@ public void run() { Skript.exception(e); } finishedLoadingHooks = true; - + if (TestMode.ENABLED) { info("Preparing Skript for testing..."); tainted = true; @@ -603,10 +601,10 @@ public void run() { Bukkit.getServer().shutdown(); } } - + stopAcceptingRegistrations(); - - + + Documentation.generate(); // TODO move to test classes? // Variable loading @@ -741,6 +739,8 @@ protected void afterErrors() { TestTracker.JUnitTestFailed(test, message); Skript.exception(failure.getException(), "JUnit test '" + failure.getTestHeader() + " failed."); }); + if (SkriptJUnitTest.class.isAssignableFrom(clazz)) + ((SkriptJUnitTest) clazz.getConstructor().newInstance()).cleanup(); SkriptJUnitTest.clearJUnitTest(); } } catch (IOException e) { @@ -748,10 +748,12 @@ protected void afterErrors() { } catch (ClassNotFoundException e) { // Should be the Skript test jar gradle task. assert false : "Class 'ch.njol.skript.variables.FlatFileStorageTest' was not found."; + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { + Skript.exception(e, "Failed to initalize test JUnit classes."); } if (ignored > 0) Skript.warning("There were " + ignored + " ignored test cases! This can mean they are not properly setup in order in that class!"); - + info("Completed " + tests + " JUnit tests in " + size + " classes with " + fails + " failures in " + milliseconds + " milliseconds."); } } @@ -775,58 +777,9 @@ protected void afterErrors() { }, 100); } - // Enable metrics and register custom charts - Metrics metrics = new Metrics(Skript.this, 722); // 722 is our bStats plugin ID - metrics.addCustomChart(new SimplePie("pluginLanguage", Language::getName)); - metrics.addCustomChart(new SimplePie("effectCommands", () -> - SkriptConfig.enableEffectCommands.value().toString() - )); - metrics.addCustomChart(new SimplePie("uuidsWithPlayers", () -> - SkriptConfig.usePlayerUUIDsInVariableNames.value().toString() - )); - metrics.addCustomChart(new SimplePie("playerVariableFix", () -> - SkriptConfig.enablePlayerVariableFix.value().toString() - )); - metrics.addCustomChart(new SimplePie("logVerbosity", () -> - SkriptConfig.verbosity.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' ') - )); - metrics.addCustomChart(new SimplePie("pluginPriority", () -> - SkriptConfig.defaultEventPriority.value().name().toLowerCase(Locale.ENGLISH).replace('_', ' ') - )); - metrics.addCustomChart(new SimplePie("logPlayerCommands", () -> - String.valueOf((SkriptConfig.logEffectCommands.value() || SkriptConfig.logPlayerCommands.value())) - )); - metrics.addCustomChart(new SimplePie("maxTargetDistance", () -> - SkriptConfig.maxTargetBlockDistance.value().toString() - )); - metrics.addCustomChart(new SimplePie("softApiExceptions", () -> - SkriptConfig.apiSoftExceptions.value().toString() - )); - metrics.addCustomChart(new SimplePie("timingsStatus", () -> { - if (!Skript.classExists("co.aikar.timings.Timings")) - return "unsupported"; - return SkriptConfig.enableTimings.value().toString(); - })); - metrics.addCustomChart(new SimplePie("parseLinks", () -> - ChatMessages.linkParseMode.name().toLowerCase(Locale.ENGLISH) - )); - metrics.addCustomChart(new SimplePie("colorResetCodes", () -> - SkriptConfig.colorResetCodes.value().toString() - )); - metrics.addCustomChart(new SimplePie("functionsWithNulls", () -> - SkriptConfig.executeFunctionsWithMissingParams.value().toString() - )); - metrics.addCustomChart(new SimplePie("buildFlavor", () -> { - if (updater != null) - return updater.getCurrentRelease().flavor; - return "unknown"; - })); - metrics.addCustomChart(new SimplePie("updateCheckerEnabled", () -> - SkriptConfig.checkForNewVersion.value().toString() - )); - metrics.addCustomChart(new SimplePie("releaseChannel", SkriptConfig.releaseChannel::value)); - Skript.metrics = metrics; - + Skript.metrics = new Metrics(Skript.getInstance(), 722); // 722 is our bStats plugin ID + SkriptMetrics.setupMetrics(Skript.metrics); + /* * Start loading scripts */ @@ -877,10 +830,10 @@ protected void afterErrors() { throw Skript.exception(e); } }); - + } }); - + Bukkit.getPluginManager().registerEvents(new Listener() { @EventHandler public void onJoin(final PlayerJoinEvent e) { @@ -892,7 +845,7 @@ public void run() { SkriptUpdater updater = getUpdater(); if (updater == null) return; - + // Don't actually check for updates to avoid breaking Github rate limit if (updater.getReleaseStatus() == ReleaseStatus.OUTDATED) { // Last check indicated that an update is available @@ -907,17 +860,17 @@ public void run() { } } }, this); - + // Tell Timings that we are here! SkriptTimings.setSkript(this); } - + /** * Handles -Dskript.stuff command line arguments. */ private void handleJvmArguments() { Path folder = getDataFolder().toPath(); - + /* * Burger is a Python application that extracts data from Minecraft. * Datasets for most common versions are available for download. @@ -956,13 +909,13 @@ private void handleJvmArguments() { return; } } - + // Use BurgerHelper to create some mappings, then dump them as JSON try { BurgerHelper burger = new BurgerHelper(burgerInput); Map materials = burger.mapMaterials(); Map ids = BurgerHelper.mapIds(); - + Gson gson = new Gson(); Files.write(folder.resolve("materials_mappings.json"), gson.toJson(materials) .getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE); @@ -973,18 +926,18 @@ private void handleJvmArguments() { } } } - + public static Version getMinecraftVersion() { return minecraftVersion; } - + /** * @return Whether this server is running CraftBukkit */ public static boolean isRunningCraftBukkit() { return serverPlatform == ServerPlatform.BUKKIT_CRAFTBUKKIT; } - + /** * @return Whether this server is running Minecraft major.minor or higher */ @@ -994,24 +947,24 @@ public static boolean isRunningMinecraft(final int major, final int minor) { } return minecraftVersion.compareTo(major, minor) >= 0; } - + public static boolean isRunningMinecraft(final int major, final int minor, final int revision) { if (minecraftVersion.compareTo(UNKNOWN_VERSION) == 0) { updateMinecraftVersion(); } return minecraftVersion.compareTo(major, minor, revision) >= 0; } - + public static boolean isRunningMinecraft(final Version v) { if (minecraftVersion.compareTo(UNKNOWN_VERSION) == 0) { updateMinecraftVersion(); } return minecraftVersion.compareTo(v) >= 0; } - + /** * Used to test whether certain Bukkit features are supported. - * + * * @param className * @return Whether the given class exists. * @deprecated use {@link #classExists(String)} @@ -1020,10 +973,10 @@ public static boolean isRunningMinecraft(final Version v) { public static boolean supports(final String className) { return classExists(className); } - + /** * Tests whether a given class exists in the classpath. - * + * * @param className The {@link Class#getCanonicalName() canonical name} of the class * @return Whether the given class exists. */ @@ -1035,10 +988,10 @@ public static boolean classExists(final String className) { return false; } } - + /** * Tests whether a method exists in the given class. - * + * * @param c The class * @param methodName The name of the method * @param parameterTypes The parameter types of the method @@ -1054,12 +1007,12 @@ public static boolean methodExists(final Class c, final String methodName, fi return false; } } - + /** * Tests whether a method exists in the given class, and whether the return type matches the expected one. *

* Note that this method doesn't work properly if multiple methods with the same name and parameters exist but have different return types. - * + * * @param c The class * @param methodName The name of the method * @param parameterTypes The parameter types of the method @@ -1076,10 +1029,10 @@ public static boolean methodExists(final Class c, final String methodName, fi return false; } } - + /** * Tests whether a field exists in the given class. - * + * * @param c The class * @param fieldName The name of the field * @return Whether the given field exists. @@ -1094,23 +1047,23 @@ public static boolean fieldExists(final Class c, final String fieldName) { return false; } } - + @Nullable static Metrics metrics; - + @Nullable public static Metrics getMetrics() { return metrics; } - + @SuppressWarnings("null") private final static Collection closeOnDisable = Collections.synchronizedCollection(new ArrayList()); - + /** * Registers a Closeable that should be closed when this plugin is disabled. *

* All registered Closeables will be closed after all scripts have been stopped. - * + * * @param closeable */ public static void closeOnDisable(final Closeable closeable) { @@ -1201,13 +1154,14 @@ public void onDisable() { if (disabled) return; disabled = true; + this.experimentRegistry = null; if (!partDisabled) { beforeDisable(); } - + Bukkit.getScheduler().cancelTasks(this); - + for (Closeable c : closeOnDisable) { try { c.close(); @@ -1216,46 +1170,46 @@ public void onDisable() { } } } - + // ================ CONSTANTS, OPTIONS & OTHER ================ - + public final static String SCRIPTSFOLDER = "scripts"; - + public static void outdatedError() { error("Skript v" + getInstance().getDescription().getVersion() + " is not fully compatible with Bukkit " + Bukkit.getVersion() + ". Some feature(s) will be broken until you update Skript."); } - + public static void outdatedError(final Exception e) { outdatedError(); if (testing()) e.printStackTrace(); } - + /** * A small value, useful for comparing doubles or floats. *

* E.g. to test whether two floating-point numbers are equal: - * + * *

 	 * Math.abs(a - b) < Skript.EPSILON
 	 * 
- * + * * or whether a location is within a specific radius of another location: - * + * *
 	 * location.distanceSquared(center) - radius * radius < Skript.EPSILON
 	 * 
- * + * * @see #EPSILON_MULT */ public final static double EPSILON = 1e-10; /** * A value a bit larger than 1 - * + * * @see #EPSILON */ public final static double EPSILON_MULT = 1.00001; - + /** * The maximum ID a block can have in Minecraft. */ @@ -1264,19 +1218,19 @@ public static void outdatedError(final Exception e) { * The maximum data value of Minecraft, i.e. Short.MAX_VALUE - Short.MIN_VALUE. */ public final static int MAXDATAVALUE = Short.MAX_VALUE - Short.MIN_VALUE; - + // TODO localise Infinity, -Infinity, NaN (and decimal point?) public static String toString(final double n) { return StringUtils.toString(n, SkriptConfig.numberAccuracy.value()); } - + public final static UncaughtExceptionHandler UEH = new UncaughtExceptionHandler() { @Override public void uncaughtException(final @Nullable Thread t, final @Nullable Throwable e) { Skript.exception(e, "Exception in thread " + (t == null ? null : t.getName())); } }; - + /** * Creates a new Thread and sets its UncaughtExceptionHandler. The Thread is not started automatically. */ @@ -1285,38 +1239,38 @@ public static Thread newThread(final Runnable r, final String name) { t.setUncaughtExceptionHandler(UEH); return t; } - + // ================ REGISTRATIONS ================ - + private static boolean acceptRegistrations = true; - + public static boolean isAcceptRegistrations() { if (instance == null) throw new IllegalStateException("Skript was never loaded"); return acceptRegistrations && instance.isEnabled(); } - + public static void checkAcceptRegistrations() { if (!isAcceptRegistrations() && !Skript.testing()) throw new SkriptAPIException("Registration can only be done during plugin initialization"); } - + private static void stopAcceptingRegistrations() { Converters.createChainedConverters(); acceptRegistrations = false; - + Classes.onRegistrationsStop(); } - + // ================ ADDONS ================ - + private final static HashMap addons = new HashMap<>(); - + /** * Registers an addon to Skript. This is currently not required for addons to work, but the returned {@link SkriptAddon} provides useful methods for registering syntax elements * and adding new strings to Skript's localization system (e.g. the required "types.[type]" strings for registered classes). - * + * * @param p The plugin */ public static SkriptAddon registerAddon(final JavaPlugin p) { @@ -1327,25 +1281,25 @@ public static SkriptAddon registerAddon(final JavaPlugin p) { addons.put(p.getName(), addon); return addon; } - + @Nullable public static SkriptAddon getAddon(final JavaPlugin p) { return addons.get(p.getName()); } - + @Nullable public static SkriptAddon getAddon(final String name) { return addons.get(name); } - + @SuppressWarnings("null") public static Collection getAddons() { return Collections.unmodifiableCollection(addons.values()); } - + @Nullable private static SkriptAddon addon; - + /** * @return A {@link SkriptAddon} representing Skript. */ @@ -1359,28 +1313,60 @@ public static SkriptAddon getAddonInstance() { // ================ CONDITIONS & EFFECTS & SECTIONS ================ - private static final Collection> conditions = new ArrayList<>(50); - private static final Collection> effects = new ArrayList<>(50); - private static final Collection> statements = new ArrayList<>(100); - private static final Collection> sections = new ArrayList<>(50); + private static final List> conditions = new ArrayList<>(50); + private static final List> effects = new ArrayList<>(50); + private static final List> statements = new ArrayList<>(100); + private static final List> sections = new ArrayList<>(50); + + public static Collection> getStatements() { + return statements; + } + + public static Collection> getEffects() { + return effects; + } + + public static Collection> getSections() { + return sections; + } + + // ================ CONDITIONS ================ + public static Collection> getConditions() { + return conditions; + } + + private final static int[] conditionTypesStartIndices = new int[ConditionType.values().length]; + + /** + * registers a {@link Condition}. + * + * @param condition The condition's class + * @param patterns Skript patterns to match this condition + */ + public static void registerCondition(Class condition, String... patterns) throws IllegalArgumentException { + registerCondition(condition, ConditionType.COMBINED, patterns); + } /** * registers a {@link Condition}. * * @param condition The condition's class + * @param type The conditions {@link ConditionType type}. This is used to determine in which order to try to parse conditions. * @param patterns Skript patterns to match this condition */ - public static void registerCondition(final Class condition, final String... patterns) throws IllegalArgumentException { + public static void registerCondition(Class condition, ConditionType type, String... patterns) throws IllegalArgumentException { checkAcceptRegistrations(); String originClassPath = Thread.currentThread().getStackTrace()[2].getClassName(); final SyntaxElementInfo info = new SyntaxElementInfo<>(patterns, condition, originClassPath); - conditions.add(info); - statements.add(info); + conditions.add(conditionTypesStartIndices[type.ordinal()], info); + statements.add(conditionTypesStartIndices[type.ordinal()], info); + for (int i = type.ordinal(); i < ConditionType.values().length; i++) + conditionTypesStartIndices[i]++; } - + /** * Registers an {@link Effect}. - * + * * @param effect The effect's class * @param patterns Skript patterns to match this effect */ @@ -1406,31 +1392,15 @@ public static void registerSection(Class section, String. sections.add(info); } - public static Collection> getStatements() { - return statements; - } - - public static Collection> getConditions() { - return conditions; - } - - public static Collection> getEffects() { - return effects; - } - - public static Collection> getSections() { - return sections; - } - // ================ EXPRESSIONS ================ - + private final static List> expressions = new ArrayList<>(100); - + private final static int[] expressionTypesStartIndices = new int[ExpressionType.values().length]; - + /** * Registers an expression. - * + * * @param c The expression's class * @param returnType The superclass of all values returned by the expression * @param type The expression's {@link ExpressionType type}. This is used to determine in which order to try to parse expressions. @@ -1448,12 +1418,12 @@ public static , T> void registerExpression(final Class> getExpressions() { return expressions.iterator(); } - + public static Iterator> getExpressions(final Class... returnTypes) { return new CheckedIterator<>(getExpressions(), new NullableChecker>() { @Override @@ -1469,7 +1439,7 @@ public boolean check(final @Nullable ExpressionInfo i) { } }); } - + // ================ EVENTS ================ private static final List> events = new ArrayList<>(50); @@ -1477,7 +1447,7 @@ public boolean check(final @Nullable ExpressionInfo i) { /** * Registers an event. - * + * * @param name Capitalised name of the event without leading "On" which is added automatically (Start the name with an asterisk to prevent this). Used for error messages and * the documentation. * @param c The event's class @@ -1489,10 +1459,10 @@ public boolean check(final @Nullable ExpressionInfo i) { public static SkriptEventInfo registerEvent(String name, Class c, Class event, String... patterns) { return registerEvent(name, c, new Class[] {event}, patterns); } - + /** * Registers an event. - * + * * @param name The name of the event, used for error messages * @param c The event's class * @param events The Bukkit events this event applies to @@ -1542,10 +1512,10 @@ public static List> getStructures() { } // ================ COMMANDS ================ - + /** * Dispatches a command with calling command events - * + * * @param sender * @param command * @return Whether the command was run @@ -1570,39 +1540,39 @@ public static boolean dispatchCommand(final CommandSender sender, final String c return false; } } - + // ================ LOGGING ================ - + public static boolean logNormal() { return SkriptLogger.log(Verbosity.NORMAL); } - + public static boolean logHigh() { return SkriptLogger.log(Verbosity.HIGH); } - + public static boolean logVeryHigh() { return SkriptLogger.log(Verbosity.VERY_HIGH); } - + public static boolean debug() { return SkriptLogger.debug(); } - + public static boolean testing() { return debug() || Skript.class.desiredAssertionStatus(); } - + public static boolean log(final Verbosity minVerb) { return SkriptLogger.log(minVerb); } - + public static void debug(final String info) { if (!debug()) return; SkriptLogger.log(SkriptLogger.DEBUG, info); } - + /** * @see SkriptLogger#log(Level, String) */ @@ -1610,7 +1580,7 @@ public static void debug(final String info) { public static void info(final String info) { SkriptLogger.log(Level.INFO, info); } - + /** * @see SkriptLogger#log(Level, String) */ @@ -1618,7 +1588,7 @@ public static void info(final String info) { public static void warning(final String warning) { SkriptLogger.log(Level.WARNING, warning); } - + /** * @see SkriptLogger#log(Level, String) */ @@ -1627,54 +1597,54 @@ public static void error(final @Nullable String error) { if (error != null) SkriptLogger.log(Level.SEVERE, error); } - + /** * Use this in {@link Expression#init(Expression[], int, Kleenean, ch.njol.skript.lang.SkriptParser.ParseResult)} (and other methods that are called during the parsing) to log * errors with a specific {@link ErrorQuality}. - * + * * @param error * @param quality */ public static void error(final String error, final ErrorQuality quality) { SkriptLogger.log(new LogEntry(SkriptLogger.SEVERE, quality, error)); } - + private final static String EXCEPTION_PREFIX = "#!#! "; - + /** * Used if something happens that shouldn't happen - * + * * @param info Description of the error and additional information * @return an EmptyStacktraceException to throw if code execution should terminate. */ public static EmptyStacktraceException exception(final String... info) { return exception(null, info); } - + public static EmptyStacktraceException exception(final @Nullable Throwable cause, final String... info) { return exception(cause, null, null, info); } - + public static EmptyStacktraceException exception(final @Nullable Throwable cause, final @Nullable Thread thread, final String... info) { return exception(cause, thread, null, info); } - + public static EmptyStacktraceException exception(final @Nullable Throwable cause, final @Nullable TriggerItem item, final String... info) { return exception(cause, null, item, info); } - + /** * Maps Java packages of plugins to descriptions of said plugins. * This is only done for plugins that depend or soft-depend on Skript. */ private static Map pluginPackages = new HashMap<>(); private static boolean checkedPlugins = false; - + /** * Set by Skript when doing something that users shouldn't do. */ private static boolean tainted = false; - + /** * Set to true when an exception is thrown. */ @@ -1690,7 +1660,7 @@ public static void markErrored() { /** * Used if something happens that shouldn't happen - * + * * @param cause exception that shouldn't occur * @param info Description of the error and additional information * @return an EmptyStacktraceException to throw if code execution should terminate. @@ -1704,11 +1674,11 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina } // First error: gather plugin package information - if (!checkedPlugins) { + if (!checkedPlugins) { for (Plugin plugin : Bukkit.getPluginManager().getPlugins()) { if (plugin.getName().equals("Skript")) // Don't track myself! continue; - + PluginDescriptionFile desc = plugin.getDescription(); if (desc.getDepend().contains("Skript") || desc.getSoftDepend().contains("Skript")) { // Take actual main class out from the qualified name @@ -1717,24 +1687,24 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina for (int i = 0; i < parts.length - 1; i++) { name.append(parts[i]).append('.'); } - + // Put this to map pluginPackages.put(name.toString(), desc); if (Skript.debug()) Skript.info("Identified potential addon: " + desc.getFullName() + " (" + name.toString() + ")"); } } - + checkedPlugins = true; // No need to do this next time } - + String issuesUrl = "https://github.com/SkriptLang/Skript/issues"; - + logEx(); logEx("[Skript] Severe Error:"); logEx(info); logEx(); - + // Parse something useful out of the stack trace StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); Set stackPlugins = new HashSet<>(); @@ -1744,9 +1714,9 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina stackPlugins.add(e.getValue()); // Yes? Add it to list } } - + SkriptUpdater updater = Skript.getInstance().getUpdater(); - + // Check if server platform is supported if (tainted) { logEx("Skript is running with developer command-line options."); @@ -1788,7 +1758,7 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina String website = desc.getWebsite(); if (website != null && !website.isEmpty()) // Add website if found pluginsMessage.append(" (").append(desc.getWebsite()).append(")"); - + pluginsMessage.append(" "); } logEx(pluginsMessage.toString()); @@ -1801,12 +1771,12 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina String website = desc.getWebsite(); if (website != null && !website.isEmpty()) // Add website if found pluginsMessage.append(" (").append(desc.getWebsite()).append(")"); - + pluginsMessage.append(" "); } logEx(pluginsMessage.toString()); } - + logEx("You should try disabling those plugins one by one, trying to find which one causes it."); logEx("If the error doesn't disappear even after disabling all listed plugins, it is probably Skript issue."); logEx("In that case, you will be given instruction on how should you report it."); @@ -1814,7 +1784,7 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina logEx("Only if the author tells you to do so, report it to Skript's issue tracker."); } } - + logEx(); logEx("Stack trace:"); if (cause == null || cause.getStackTrace().length == 0) { @@ -1829,7 +1799,7 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina cause = cause.getCause(); first = false; } - + logEx(); logEx("Version Information:"); if (updater != null) { @@ -1865,14 +1835,14 @@ public static EmptyStacktraceException exception(@Nullable Throwable cause, fina logEx(); logEx("End of Error."); logEx(); - + return new EmptyStacktraceException(); } - + static void logEx() { SkriptLogger.LOGGER.severe(EXCEPTION_PREFIX); } - + static void logEx(final String... lines) { for (final String line : lines) SkriptLogger.LOGGER.severe(EXCEPTION_PREFIX + line); @@ -1887,7 +1857,7 @@ public static String getSkriptPrefix() { public static void info(final CommandSender sender, final String info) { sender.sendMessage(Utils.replaceEnglishChatStyles(getSkriptPrefix() + info)); } - + /** * @param message * @param permission @@ -1896,25 +1866,25 @@ public static void info(final CommandSender sender, final String info) { public static void broadcast(final String message, final String permission) { Bukkit.broadcast(Utils.replaceEnglishChatStyles(getSkriptPrefix() + message), permission); } - + public static void adminBroadcast(final String message) { broadcast(message, "skript.admin"); } - + /** * Similar to {@link #info(CommandSender, String)} but no [Skript] prefix is added. - * + * * @param sender * @param info */ public static void message(final CommandSender sender, final String info) { sender.sendMessage(Utils.replaceEnglishChatStyles(info)); } - + public static void error(final CommandSender sender, final String error) { sender.sendMessage(Utils.replaceEnglishChatStyles(getSkriptPrefix() + ChatColor.DARK_RED + error)); } - + /** * Gets the updater instance currently used by Skript. * @return SkriptUpdater instance. @@ -1923,5 +1893,5 @@ public static void error(final CommandSender sender, final String error) { public SkriptUpdater getUpdater() { return updater; } - + } diff --git a/src/main/java/ch/njol/skript/SkriptCommand.java b/src/main/java/ch/njol/skript/SkriptCommand.java index 62261d0e426..9a6091b2df6 100644 --- a/src/main/java/ch/njol/skript/SkriptCommand.java +++ b/src/main/java/ch/njol/skript/SkriptCommand.java @@ -25,6 +25,7 @@ import ch.njol.skript.localization.ArgsMessage; import ch.njol.skript.localization.Language; import ch.njol.skript.localization.PluralizingArgsMessage; +import ch.njol.skript.log.LogEntry; import ch.njol.skript.log.RedirectingLogHandler; import ch.njol.skript.log.TimingLogHandler; import ch.njol.skript.test.runner.SkriptTestEvent; @@ -52,29 +53,31 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.logging.Level; import java.util.stream.Collectors; + public class SkriptCommand implements CommandExecutor { - + private static final String CONFIG_NODE = "skript command"; private static final ArgsMessage m_reloading = new ArgsMessage(CONFIG_NODE + ".reload.reloading"); // TODO /skript scripts show/list - lists all enabled and/or disabled scripts in the scripts folder and/or subfolders (maybe add a pattern [using * and **]) // TODO document this command on the website private static final CommandHelp SKRIPT_COMMAND_HELP = new CommandHelp("/skript", SkriptColor.LIGHT_CYAN, CONFIG_NODE + ".help") - .add(new CommandHelp("reload", SkriptColor.DARK_RED) + .add(new CommandHelp("reload", SkriptColor.DARK_CYAN) .add("all") .add("config") .add("aliases") .add("scripts") .add("