diff --git a/src/main/java/ch/njol/skript/doc/JSONGenerator.java b/src/main/java/ch/njol/skript/doc/JSONGenerator.java index d30d0f039ac..194f17a6be9 100644 --- a/src/main/java/ch/njol/skript/doc/JSONGenerator.java +++ b/src/main/java/ch/njol/skript/doc/JSONGenerator.java @@ -18,6 +18,7 @@ import org.bukkit.event.block.BlockCanBuildEvent; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.skriptlang.skript.lang.experiment.Experiment; import org.skriptlang.skript.lang.structure.Structure; import org.skriptlang.skript.lang.structure.StructureInfo; @@ -366,6 +367,44 @@ private static JsonArray generateFunctionArray(Iterator> functio return syntaxArray; } + /** + * Generates a JsonArray with all data for each {@link Experiment}. + * + * @return a JsonArray containing the documentation JsonObjects for each experiment + */ + private static JsonArray generateExperiments() { + JsonArray array = new JsonArray(); + + for (Experiment experiment : Skript.experiments().registered()) { + JsonObject object = new JsonObject(); + + object.addProperty("id", experiment.codeName()); + + if (experiment.displayName().isEmpty()) { + object.addProperty("name", (String) null); + } else { + object.addProperty("name", experiment.displayName()); + } + + if (experiment.description().isEmpty()) { + object.add("description", null); + } else { + JsonArray description = new JsonArray(); + for (String part : experiment.description()) { + description.add(part); + } + object.add("description", description); + } + + object.addProperty("pattern", experiment.pattern().toString()); + object.addProperty("phase", experiment.phase().name().toLowerCase(Locale.ENGLISH)); + + array.add(object); + } + + return array; + } + /** * Cleans the provided patterns * @@ -416,6 +455,8 @@ public void generate() { jsonDocs.add("functions", generateFunctionArray(Functions.getJavaFunctions().iterator())); + jsonDocs.add("experiments", generateExperiments()); + saveDocs(outputDir.toPath().resolve("docs.json"), jsonDocs); } diff --git a/src/main/java/ch/njol/skript/registrations/Feature.java b/src/main/java/ch/njol/skript/registrations/Feature.java index eb2c2f0f9d7..130866acc4b 100644 --- a/src/main/java/ch/njol/skript/registrations/Feature.java +++ b/src/main/java/ch/njol/skript/registrations/Feature.java @@ -3,28 +3,178 @@ import ch.njol.skript.SkriptAddon; import ch.njol.skript.patterns.PatternCompiler; import ch.njol.skript.patterns.SkriptPattern; +import com.google.common.base.Preconditions; +import org.jetbrains.annotations.NotNull; import org.skriptlang.skript.lang.experiment.Experiment; import org.skriptlang.skript.lang.experiment.ExperimentRegistry; import org.skriptlang.skript.lang.experiment.LifeCycle; +import java.util.Collection; +import java.util.List; + /** * Experimental feature toggles as provided by Skript itself. */ public enum Feature implements Experiment { - EXAMPLES("examples", LifeCycle.STABLE), - QUEUES("queues", LifeCycle.EXPERIMENTAL), - FOR_EACH_LOOPS("for loop", LifeCycle.EXPERIMENTAL, "for [each] loop[s]"), - SCRIPT_REFLECTION("reflection", LifeCycle.EXPERIMENTAL, "[script] reflection"), - CATCH_ERRORS("catch runtime errors", LifeCycle.EXPERIMENTAL, "error catching [section]"), - TYPE_HINTS("type hints", LifeCycle.EXPERIMENTAL, "[local variable] type hints"), - DAMAGE_SOURCE("damage source", LifeCycle.EXPERIMENTAL, "damage source[s]") - ; + EXAMPLES("examples", + "Examples", + """ + A section used to provide examples inside code. + + ``` + example: + kick the player due to "you are not allowed here!" + ``` + """, + LifeCycle.STABLE), + QUEUES("queues", + "Queues", + """ + A collection that removes elements whenever they are requested. + + This is useful for processing tasks or keeping track of things that need to happen only once. + + ``` + set {queue} to a new queue of "hello" and "world" + + broadcast the first element of {queue} + # "hello" is now removed + + broadcast the first element of {queue} + # "world" is now removed + + # queue is empty + ``` + + ``` + set {queue} to a new queue of all players + + set {player 1} to a random element out of {queue}\s + set {player 2} to a random element out of {queue} + # players 1 and 2 are guaranteed to be distinct + ``` + + Queues can be looped over like a regular list. + """, + LifeCycle.EXPERIMENTAL), + FOR_EACH_LOOPS("for loop", + "For Loops", + """ + A new kind of loop syntax that stores the loop index and value in variables for convenience. + + This can be used to avoid confusion when nesting multiple loops inside each other. + + ``` + for {_index}, {_value} in {my list::*}: + broadcast "%{_index}%: %{_value}%" + ``` + + ``` + for each {_player} in all players: + send "Hello %{_player}%!" to {_player} + ``` + + All existing loop features are also available in this section. + """, + LifeCycle.EXPERIMENTAL, + "for [each] loop[s]"), + SCRIPT_REFLECTION("reflection", + "Script Reflection", + """ + This feature includes: + + - The ability to reference a script in code. + - Finding and running functions by name. + - Reading configuration files and values. + """, + LifeCycle.EXPERIMENTAL, + "[script] reflection"), + CATCH_ERRORS("catch runtime errors", + "Runtime Error Catching", + """ + A new catch runtime errors section allows you to catch and \ + suppress runtime errors within it and access them later with \ + the last caught runtime errors. + + ``` + catch runtime errors: + ... + set worldborder center of {_border} to {_my unsafe location} + ... + if last caught runtime errors contains "Your location can't have a NaN value as one of its components": + set worldborder center of {_border} to location(0, 0, 0) + ``` + """, + LifeCycle.EXPERIMENTAL, + "error catching [section]"), + TYPE_HINTS("type hints", + "Type Hints", + """ + Local variable type hints enable Skript to understand \ + what kind of values your local variables will hold at parse time. \ + Consider the following example: + + ``` + set {_a} to 5 + set {_b} to "some string" + ... do stuff ... + set {_c} to {_a} in lowercase # oops i used the wrong variable + ``` + + Previously, the code above would parse without issue. \ + However, Skript now understands that when it is used, \ + {_a} could only be a number (and not a text). \ + Thus, the code above would now error with a message about mismatched types. + + Please note that this feature is currently only supported by simple local variables. \ + A simple local variable is one whose name does not contain any expressions: + + ``` + {_var} # can use type hints + {_var::%player's name%} # can't use type hints + ``` + """, + LifeCycle.EXPERIMENTAL, + "[local variable] type hints"), + DAMAGE_SOURCE("damage source", + "Damage Sources", + """ + Damage sources are a more advanced and detailed version of damage causes. \ + Damage sources include information such as the type of damage, \ + the location where the damage originated from, the entity that \ + directly caused the damage, and more. + + Below is an example of what damaging using custom damage sources looks like: + + ``` + damage all players by 5 using a custom damage source: + set the damage type to magic + set the causing entity to {_player} + set the direct entity to {_arrow} + set the damage location to location(0, 0, 10) + ``` + + For more details about the syntax, visit damage source on our documentation website. + """, + LifeCycle.EXPERIMENTAL, + "damage source[s]"); + + private final String displayName; private final String codeName; + private final String description; private final LifeCycle phase; private final SkriptPattern compiledPattern; - Feature(String codeName, LifeCycle phase, String... patterns) { + Feature(@NotNull String codeName, @NotNull String displayName, + @NotNull String description, @NotNull LifeCycle phase, + String... patterns) { + Preconditions.checkNotNull(codeName, "codeName cannot be null"); + Preconditions.checkNotNull(displayName, "displayName cannot be null"); + Preconditions.checkNotNull(description, "description cannot be null"); + + this.displayName = displayName; + this.description = description.strip(); this.codeName = codeName; this.phase = phase; this.compiledPattern = switch (patterns.length) { @@ -40,6 +190,16 @@ public static void registerAll(SkriptAddon addon, ExperimentRegistry manager) { } } + @Override + public @NotNull String displayName() { + return displayName; + } + + @Override + public @NotNull Collection description() { + return List.of(description); + } + @Override public String codeName() { return codeName; diff --git a/src/main/java/ch/njol/skript/test/runner/TestFeatures.java b/src/main/java/ch/njol/skript/test/runner/TestFeatures.java index e42fcdb5d81..6ff1a9d58e1 100644 --- a/src/main/java/ch/njol/skript/test/runner/TestFeatures.java +++ b/src/main/java/ch/njol/skript/test/runner/TestFeatures.java @@ -63,7 +63,9 @@ public SkriptPattern pattern() { } static { - registerAll(Skript.getAddonInstance(), Skript.experiments()); + if (!TestMode.GEN_DOCS) { + registerAll(Skript.getAddonInstance(), Skript.experiments()); + } } } diff --git a/src/main/java/org/skriptlang/skript/lang/experiment/Experiment.java b/src/main/java/org/skriptlang/skript/lang/experiment/Experiment.java index 55422718eb7..95cb20964dc 100644 --- a/src/main/java/org/skriptlang/skript/lang/experiment/Experiment.java +++ b/src/main/java/org/skriptlang/skript/lang/experiment/Experiment.java @@ -4,7 +4,10 @@ import ch.njol.skript.patterns.SkriptPattern; import ch.njol.skript.registrations.Feature; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import java.util.Collection; +import java.util.List; import java.util.Objects; /** @@ -56,6 +59,20 @@ default boolean isKnown() { */ SkriptPattern pattern(); + /** + * @return The display name for this experiment. + */ + default @NotNull String displayName() { + return codeName(); + } + + /** + * @return The description for this experiment. + */ + default @NotNull Collection description() { + return List.of(); + } + /** * @return Whether the usage pattern of this experiment matches the input text */