diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 974188b..299ed6c 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,6 @@ { - "recommendations": ["esbenp.prettier-vscode"] + "recommendations": [ + "esbenp.prettier-vscode", + "ms-vscode.vscode-typescript-tslint-plugin" + ] } diff --git a/__snapshots__/block.test.ts.js b/__snapshots__/block.test.ts.js new file mode 100644 index 0000000..76f2351 --- /dev/null +++ b/__snapshots__/block.test.ts.js @@ -0,0 +1,2508 @@ +exports[ + "sharedBlockParser Tags allowed should correctly parse various tag inputs runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "#test:empty", + expect: { + cursor: 11, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 19, + start: 1, + text: "test:empty" + }, + { + kind: 19, + start: 1, + text: "test:empty_values" + }, + { + kind: 19, + start: 1, + text: "test:plain" + }, + { + kind: 19, + start: 1, + text: "minecraft:empty" + }, + { + kind: 19, + start: 1, + text: "minecraft:plain" + }, + { + kind: 19, + start: 1, + text: "test:othertags" + }, + { + kind: 19, + start: 1, + text: "test:duplicated_block" + }, + { + kind: 19, + start: 1, + text: "test:invalid_block" + }, + { + kind: 19, + start: 1, + text: "localdata:token" + }, + { + start: 11, + text: "[" + }, + { + start: 11, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "#test:unknown", + expect: { + cursor: 13, + parsing: { + actions: [], + errors: [ + { + code: "arguments.block.tag.unknown", + severity: 1, + substitutions: ["test:unknown"], + text: "Unknown block tag 'test:unknown'", + range: { + start: 0, + end: 13 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 19, + start: 1, + text: "test:empty" + }, + { + kind: 19, + start: 1, + text: "test:empty_values" + }, + { + kind: 19, + start: 1, + text: "test:plain" + }, + { + kind: 19, + start: 1, + text: "minecraft:empty" + }, + { + kind: 19, + start: 1, + text: "minecraft:plain" + }, + { + kind: 19, + start: 1, + text: "test:othertags" + }, + { + kind: 19, + start: 1, + text: "test:duplicated_block" + }, + { + kind: 19, + start: 1, + text: "test:invalid_block" + }, + { + kind: 19, + start: 1, + text: "localdata:token" + }, + { + start: 13, + text: "[" + }, + { + start: 13, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "#test:plain[", + expect: { + cursor: 12, + parsing: { + actions: [ + { + data: + '```json\n{\n "values": [\n "langserver:multi",\n "langserver:props",\n "test"\n ]\n}\n```', + high: 11, + low: 1, + type: "hover" + } + ], + errors: [ + { + code: "argument.block.property.unclosed", + severity: 1, + substitutions: [], + text: + "Expected closing ] for block state properties", + range: { + start: 11, + end: 12 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 11, + text: "[" + }, + { + start: 12, + text: "]" + }, + { + kind: 10, + start: 12, + text: "otherprop" + }, + { + kind: 10, + start: 12, + text: "prop1" + }, + { + kind: 10, + start: 12, + text: "prop" + }, + { + kind: 10, + start: 12, + text: "state" + } + ], + misc: [], + kind: false + } + } + } + ] +}; + +exports[ + "sharedBlockParser Tags allowed should work correctly for various inputs involving plain blocks runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "langserver:noprops", + expect: { + cursor: 18, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + text: "langserver:multi", + start: 0 + }, + { + text: "langserver:noprops", + start: 0 + }, + { + text: "langserver:props", + start: 0 + }, + { + text: "minecraft:lang", + start: 0 + }, + { + text: "minecraft:test", + start: 0 + }, + { + start: 18, + text: "[" + }, + { + start: 18, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:noprops[]", + expect: { + cursor: 20, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 18, + text: "[" + }, + { + start: 19, + text: "]" + }, + { + start: 20, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:noprops[ ]", + expect: { + cursor: 22, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 18, + text: "[" + }, + { + start: 21, + text: "]" + }, + { + start: 22, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:props[prop=value]", + expect: { + cursor: 28, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 21, + text: "=" + }, + { + start: 27, + text: "," + }, + { + start: 27, + text: "]" + }, + { + start: 28, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: 'langserver:props["prop"="value"]', + expect: { + cursor: 32, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 23, + text: "=" + }, + { + start: 31, + text: "," + }, + { + start: 31, + text: "]" + }, + { + start: 32, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:props[unknown=value]", + expect: { + cursor: 31, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.unknown", + severity: 1, + substitutions: ["langserver:props", "unknown"], + text: + "Block langserver:props does not have property 'unknown'", + range: { + start: 17, + end: 24 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 24, + text: "=" + }, + { + start: 30, + text: "," + }, + { + start: 30, + text: "]" + }, + { + start: 31, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:props[prop=unknown]", + expect: { + cursor: 30, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.invalid", + severity: 1, + substitutions: [ + "langserver:props", + "unknown", + "prop" + ], + text: + "Block langserver:props does not accept 'unknown' for prop property", + range: { + start: 22, + end: 29 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 21, + text: "=" + }, + { + start: 29, + text: "," + }, + { + start: 29, + text: "]" + }, + { + start: 30, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:multi[otherprop=propvalue,prop1=other]", + expect: { + cursor: 49, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 26, + text: "=" + }, + { + start: 36, + text: "," + }, + { + start: 42, + text: "=" + }, + { + start: 48, + text: "," + }, + { + start: 48, + text: "]" + }, + { + start: 49, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:multi[otherprop=propvalue,otherprop=lang]", + expect: { + cursor: 52, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.duplicate", + severity: 1, + substitutions: ["otherprop", "langserver:multi"], + text: + "Property 'otherprop' can only be set once for block langserver:multi", + range: { + start: 37, + end: 46 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 26, + text: "=" + }, + { + start: 36, + text: "," + }, + { + start: 46, + text: "=" + }, + { + start: 51, + text: "," + }, + { + start: 51, + text: "]" + }, + { + start: 52, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:multi[otherprop=lang", + expect: { + cursor: 31, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.unclosed", + severity: 1, + substitutions: [], + text: + "Expected closing ] for block state properties", + range: { + start: 16, + end: 31 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 26, + text: "=" + }, + { + kind: 20, + start: 27, + text: "lang" + }, + { + kind: 20, + start: 27, + text: "propvalue" + }, + { + start: 31, + text: "," + }, + { + start: 31, + text: "]" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "langserver:", + expect: { + cursor: 11, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.id.invalid", + severity: 1, + substitutions: [], + text: "Unknown block type 'undefined'", + range: { + start: 0, + end: 11 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + text: "langserver:multi", + start: 0 + }, + { + text: "langserver:noprops", + start: 0 + }, + { + text: "langserver:props", + start: 0 + }, + { + text: "minecraft:lang", + start: 0 + }, + { + text: "minecraft:test", + start: 0 + }, + { + start: 11, + text: "[" + }, + { + start: 11, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "lang", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + text: "langserver:multi", + start: 0 + }, + { + text: "langserver:noprops", + start: 0 + }, + { + text: "langserver:props", + start: 0 + }, + { + text: "minecraft:lang", + start: 0 + }, + { + text: "minecraft:test", + start: 0 + }, + { + start: 4, + text: "[" + }, + { + start: 4, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:multi[", + expect: { + cursor: 17, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.unclosed", + severity: 1, + substitutions: [], + text: + "Expected closing ] for block state properties", + range: { + start: 16, + end: 17 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + kind: 10, + start: 17, + text: "otherprop" + }, + { + kind: 10, + start: 17, + text: "prop1" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "langserver:props[prop", + expect: { + cursor: 21, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.novalue", + severity: 1, + substitutions: ["prop", "langserver:props"], + text: + "Expected value for property 'prop' on block langserver:props", + range: { + start: 17, + end: 21 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + kind: 10, + start: 17, + text: "prop" + }, + { + start: 21, + text: "=" + } + ], + misc: [], + kind: false + } + } + }, + { + given: 'langserver:props["prop"extra', + expect: { + cursor: 23, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.novalue", + severity: 1, + substitutions: ["prop", "langserver:props"], + text: + "Expected value for property 'prop' on block langserver:props", + range: { + start: 17, + end: 23 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 23, + text: "=" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "langserver:props[prop=", + expect: { + cursor: 22, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.unclosed", + severity: 1, + substitutions: [], + text: + "Expected closing ] for block state properties", + range: { + start: 16, + end: 22 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 21, + text: "=" + }, + { + kind: 20, + start: 22, + text: "value" + }, + { + kind: 20, + start: 22, + text: "value2" + }, + { + kind: 20, + start: 22, + text: "other" + }, + { + start: 22, + text: "," + }, + { + start: 22, + text: "]" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "langserver:props[prop=val", + expect: { + cursor: 25, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.invalid", + severity: 1, + substitutions: ["langserver:props", "val", "prop"], + text: + "Block langserver:props does not accept 'val' for prop property", + range: { + start: 22, + end: 25 + } + }, + { + code: "argument.block.property.unclosed", + severity: 1, + substitutions: [], + text: + "Expected closing ] for block state properties", + range: { + start: 16, + end: 25 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 21, + text: "=" + }, + { + kind: 20, + start: 22, + text: "value" + }, + { + kind: 20, + start: 22, + text: "value2" + }, + { + kind: 20, + start: 22, + text: "other" + }, + { + start: 25, + text: "," + }, + { + start: 25, + text: "]" + } + ], + misc: [], + kind: false + } + } + } + ] +}; + +exports[ + "sharedBlockParser Tags not allowed should do the right thing when tags are provided runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "#minecraft:anything", + expect: { + cursor: 19, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.tag.disallowed", + severity: 1, + substitutions: [], + text: + "Tags aren't allowed here, only actual blocks", + range: { + start: 0, + end: 19 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + } + } + }, + { + given: "#minecraft:anything[anyprop=value]", + expect: { + cursor: 19, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.tag.disallowed", + severity: 1, + substitutions: [], + text: + "Tags aren't allowed here, only actual blocks", + range: { + start: 0, + end: 19 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + } + } + } + ] +}; + +exports[ + "sharedBlockParser Tags not allowed should work correctly for various inputs involving plain blocks runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "langserver:noprops", + expect: { + cursor: 18, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + text: "langserver:multi", + start: 0 + }, + { + text: "langserver:noprops", + start: 0 + }, + { + text: "langserver:props", + start: 0 + }, + { + text: "minecraft:lang", + start: 0 + }, + { + text: "minecraft:test", + start: 0 + }, + { + start: 18, + text: "[" + }, + { + start: 18, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:noprops[]", + expect: { + cursor: 20, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 18, + text: "[" + }, + { + start: 19, + text: "]" + }, + { + start: 20, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:noprops[ ]", + expect: { + cursor: 22, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 18, + text: "[" + }, + { + start: 21, + text: "]" + }, + { + start: 22, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:props[prop=value]", + expect: { + cursor: 28, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 21, + text: "=" + }, + { + start: 27, + text: "," + }, + { + start: 27, + text: "]" + }, + { + start: 28, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: 'langserver:props["prop"="value"]', + expect: { + cursor: 32, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 23, + text: "=" + }, + { + start: 31, + text: "," + }, + { + start: 31, + text: "]" + }, + { + start: 32, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:props[unknown=value]", + expect: { + cursor: 31, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.unknown", + severity: 1, + substitutions: ["langserver:props", "unknown"], + text: + "Block langserver:props does not have property 'unknown'", + range: { + start: 17, + end: 24 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 24, + text: "=" + }, + { + start: 30, + text: "," + }, + { + start: 30, + text: "]" + }, + { + start: 31, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:props[prop=unknown]", + expect: { + cursor: 30, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.invalid", + severity: 1, + substitutions: [ + "langserver:props", + "unknown", + "prop" + ], + text: + "Block langserver:props does not accept 'unknown' for prop property", + range: { + start: 22, + end: 29 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 21, + text: "=" + }, + { + start: 29, + text: "," + }, + { + start: 29, + text: "]" + }, + { + start: 30, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:multi[otherprop=propvalue,prop1=other]", + expect: { + cursor: 49, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 26, + text: "=" + }, + { + start: 36, + text: "," + }, + { + start: 42, + text: "=" + }, + { + start: 48, + text: "," + }, + { + start: 48, + text: "]" + }, + { + start: 49, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:multi[otherprop=propvalue,otherprop=lang]", + expect: { + cursor: 52, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.duplicate", + severity: 1, + substitutions: ["otherprop", "langserver:multi"], + text: + "Property 'otherprop' can only be set once for block langserver:multi", + range: { + start: 37, + end: 46 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 26, + text: "=" + }, + { + start: 36, + text: "," + }, + { + start: 46, + text: "=" + }, + { + start: 51, + text: "," + }, + { + start: 51, + text: "]" + }, + { + start: 52, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:multi[otherprop=lang", + expect: { + cursor: 31, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.unclosed", + severity: 1, + substitutions: [], + text: + "Expected closing ] for block state properties", + range: { + start: 16, + end: 31 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 26, + text: "=" + }, + { + kind: 20, + start: 27, + text: "lang" + }, + { + kind: 20, + start: 27, + text: "propvalue" + }, + { + start: 31, + text: "," + }, + { + start: 31, + text: "]" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "langserver:", + expect: { + cursor: 11, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.id.invalid", + severity: 1, + substitutions: [], + text: "Unknown block type 'undefined'", + range: { + start: 0, + end: 11 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + text: "langserver:multi", + start: 0 + }, + { + text: "langserver:noprops", + start: 0 + }, + { + text: "langserver:props", + start: 0 + }, + { + text: "minecraft:lang", + start: 0 + }, + { + text: "minecraft:test", + start: 0 + }, + { + start: 11, + text: "[" + }, + { + start: 11, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "lang", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + text: "langserver:multi", + start: 0 + }, + { + text: "langserver:noprops", + start: 0 + }, + { + text: "langserver:props", + start: 0 + }, + { + text: "minecraft:lang", + start: 0 + }, + { + text: "minecraft:test", + start: 0 + }, + { + start: 4, + text: "[" + }, + { + start: 4, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:multi[", + expect: { + cursor: 17, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.unclosed", + severity: 1, + substitutions: [], + text: + "Expected closing ] for block state properties", + range: { + start: 16, + end: 17 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + kind: 10, + start: 17, + text: "otherprop" + }, + { + kind: 10, + start: 17, + text: "prop1" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "langserver:props[prop", + expect: { + cursor: 21, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.novalue", + severity: 1, + substitutions: ["prop", "langserver:props"], + text: + "Expected value for property 'prop' on block langserver:props", + range: { + start: 17, + end: 21 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + kind: 10, + start: 17, + text: "prop" + }, + { + start: 21, + text: "=" + } + ], + misc: [], + kind: false + } + } + }, + { + given: 'langserver:props["prop"extra', + expect: { + cursor: 23, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.novalue", + severity: 1, + substitutions: ["prop", "langserver:props"], + text: + "Expected value for property 'prop' on block langserver:props", + range: { + start: 17, + end: 23 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 23, + text: "=" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "langserver:props[prop=", + expect: { + cursor: 22, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.unclosed", + severity: 1, + substitutions: [], + text: + "Expected closing ] for block state properties", + range: { + start: 16, + end: 22 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 21, + text: "=" + }, + { + kind: 20, + start: 22, + text: "value" + }, + { + kind: 20, + start: 22, + text: "value2" + }, + { + kind: 20, + start: 22, + text: "other" + }, + { + start: 22, + text: "," + }, + { + start: 22, + text: "]" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "langserver:props[prop=val", + expect: { + cursor: 25, + parsing: { + actions: [], + errors: [ + { + code: "argument.block.property.invalid", + severity: 1, + substitutions: ["langserver:props", "val", "prop"], + text: + "Block langserver:props does not accept 'val' for prop property", + range: { + start: 22, + end: 25 + } + }, + { + code: "argument.block.property.unclosed", + severity: 1, + substitutions: [], + text: + "Expected closing ] for block state properties", + range: { + start: 16, + end: 25 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 16, + text: "[" + }, + { + start: 17, + text: "]" + }, + { + start: 21, + text: "=" + }, + { + kind: 20, + start: 22, + text: "value" + }, + { + kind: 20, + start: 22, + text: "value2" + }, + { + kind: 20, + start: 22, + text: "other" + }, + { + start: 25, + text: "," + }, + { + start: 25, + text: "]" + } + ], + misc: [], + kind: false + } + } + } + ] +}; + +exports[ + "sharedBlockParser block & NBT tests should work correctly for various input with nbt runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: 'langserver:nbt{customTag:"Hello World"}', + expect: { + cursor: 39, + parsing: { + actions: [ + { + data: "(compound) ", + high: 15, + low: 14, + type: "hover" + }, + { + data: "(string) ", + high: 38, + low: 25, + type: "hover" + }, + { + data: "(string) ", + high: 24, + low: 15, + type: "hover" + }, + { + data: "(compound) ", + high: 39, + low: 38, + type: "hover" + } + ], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 14, + text: "[" + }, + { + start: 14, + text: "{" + }, + { + start: 15, + text: "}" + }, + { + start: 24, + text: ":" + }, + { + start: 38, + text: "," + }, + { + start: 38, + text: "}" + } + ], + misc: [], + kind: true + } + } + }, + { + given: 'langserver:nbt{customTag:"Hello World"}', + expect: { + cursor: 39, + parsing: { + actions: [ + { + data: "(compound) ", + high: 15, + low: 14, + type: "hover" + }, + { + data: "(string) ", + high: 38, + low: 25, + type: "hover" + }, + { + data: "(string) ", + high: 24, + low: 15, + type: "hover" + }, + { + data: "(compound) ", + high: 39, + low: 38, + type: "hover" + } + ], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 14, + text: "[" + }, + { + start: 14, + text: "{" + }, + { + start: 15, + text: "}" + }, + { + start: 24, + text: ":" + }, + { + start: 38, + text: "," + }, + { + start: 38, + text: "}" + } + ], + misc: [], + kind: true + } + } + }, + { + given: 'langserver:nbt_prop[prop=1]{customTag:"Hello World"}', + expect: { + cursor: 52, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 19, + text: "[" + }, + { + start: 20, + text: "]" + }, + { + start: 24, + text: "=" + }, + { + start: 26, + text: "," + }, + { + start: 26, + text: "]" + }, + { + start: 27, + text: "{" + }, + { + start: 28, + text: "}" + }, + { + start: 37, + text: ":" + }, + { + start: 51, + text: "," + }, + { + start: 51, + text: "}" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:nbt{", + expect: { + cursor: 15, + parsing: { + actions: [ + { + data: "(compound) ", + high: 15, + low: 14, + type: "hover" + } + ], + errors: [ + { + code: "argument.nbt.compound.nokey", + severity: 1, + substitutions: [], + text: "Expected key", + range: { + start: 15, + end: 15 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 14, + text: "[" + }, + { + start: 14, + text: "{" + }, + { + start: 15, + text: "}" + }, + { + description: "(string) ", + kind: 5, + label: "customTag", + start: 15, + text: "customTag" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:nbt_two", + expect: { + cursor: 18, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + text: "langserver:nbt", + start: 0 + }, + { + text: "langserver:nbt_prop", + start: 0 + }, + { + text: "langserver:nbt_two", + start: 0 + }, + { + start: 18, + text: "[" + }, + { + start: 18, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "langserver:nbt_two{", + expect: { + cursor: 19, + parsing: { + actions: [ + { + data: "(compound) ", + high: 19, + low: 18, + type: "hover" + } + ], + errors: [ + { + code: "argument.nbt.compound.nokey", + severity: 1, + substitutions: [], + text: "Expected key", + range: { + start: 19, + end: 19 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 18, + text: "[" + }, + { + start: 18, + text: "{" + }, + { + start: 19, + text: "}" + }, + { + description: "(int) ", + kind: 5, + label: "key1", + start: 19, + text: "key1" + }, + { + description: "(string) ", + kind: 5, + label: "key0", + start: 19, + text: "key0" + } + ], + misc: [], + kind: true + } + } + } + ] +}; diff --git a/__snapshots__/bool.test.ts.js b/__snapshots__/bool.test.ts.js new file mode 100644 index 0000000..c9705ed --- /dev/null +++ b/__snapshots__/bool.test.ts.js @@ -0,0 +1,229 @@ +exports[ + "Boolean Argument Parser should work correctly for various inputs runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "true", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "true" + }, + { + start: 0, + text: "false" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "false", + expect: { + cursor: 5, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "true" + }, + { + start: 0, + text: "false" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "notbool", + expect: { + cursor: 7, + parsing: { + actions: [], + errors: [ + { + code: "parsing.bool.invalid", + severity: 1, + substitutions: ["notbool"], + text: + "Invalid bool, expected true or false but found 'notbool'", + range: { + start: 0, + end: 7 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "true" + }, + { + start: 0, + text: "false" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "fal", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [ + { + code: "parsing.bool.invalid", + severity: 1, + substitutions: ["fal"], + text: + "Invalid bool, expected true or false but found 'fal'", + range: { + start: 0, + end: 3 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "true" + }, + { + start: 0, + text: "false" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "tru", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [ + { + code: "parsing.bool.invalid", + severity: 1, + substitutions: ["tru"], + text: + "Invalid bool, expected true or false but found 'tru'", + range: { + start: 0, + end: 3 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "true" + }, + { + start: 0, + text: "false" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "", + expect: { + cursor: 0, + parsing: { + actions: [], + errors: [ + { + code: "parsing.bool.invalid", + severity: 1, + substitutions: [""], + text: + "Invalid bool, expected true or false but found ''", + range: { + start: 0, + end: 0 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "true" + }, + { + start: 0, + text: "false" + } + ], + misc: [], + kind: false + } + } + } + ] +}; diff --git a/__snapshots__/coord.test.ts.js b/__snapshots__/coord.test.ts.js new file mode 100644 index 0000000..bb52560 --- /dev/null +++ b/__snapshots__/coord.test.ts.js @@ -0,0 +1,476 @@ +exports[ + "Coordinate tests should work for various inputs with settings: {count: 2, float: false, local: true } runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "~1 ~", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "2 3", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "5 ~", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "~ ~", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "1 ^4", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.pos.mixed", + severity: 1, + substitutions: [], + text: + "Cannot mix world & local coordinates (everything must either use ^ or not)", + range: { + start: 2, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "~1 ^4", + expect: { + cursor: 5, + parsing: { + actions: [], + errors: [ + { + code: "argument.pos.mixed", + severity: 1, + substitutions: [], + text: + "Cannot mix world & local coordinates (everything must either use ^ or not)", + range: { + start: 3, + end: 5 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "~2 ", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [ + { + code: "argument.pos.incomplete", + severity: 1, + substitutions: ["1", "2"], + text: + "Incomplete position argument. Only 1 coords are present, when 2 should be", + range: { + start: 0, + end: 3 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 3, + text: "~" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "1.3 1", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [ + { + code: "parsing.int.invalid", + severity: 1, + substitutions: ["1.3"], + text: "Invalid integer '1.3'", + range: { + start: 0, + end: 3 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + } + } + } + ] +}; + +exports[ + "Coordinate tests should work for various inputs with settings: {count: 2, float: true , local: false} runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "~1 ~", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "2 3", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "5 ~20", + expect: { + cursor: 5, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "~ ~", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "^ ^3", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.pos.nolocal", + severity: 1, + substitutions: [], + text: "Local coords are not allowed", + range: { + start: 0, + end: 1 + } + }, + { + code: "argument.pos.nolocal", + severity: 1, + substitutions: [], + text: "Local coords are not allowed", + range: { + start: 2, + end: 3 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Coordinate tests should work for various inputs with settings: {count: 3, float: true , local: true } runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1 2 3", + expect: { + cursor: 5, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "~1 ~2 3", + expect: { + cursor: 7, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "^1 ^ ^2", + expect: { + cursor: 7, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "~ ~ ~", + expect: { + cursor: 5, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "1.2 3 ~1", + expect: { + cursor: 8, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "^.1 ^ ^3", + expect: { + cursor: 8, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; diff --git a/__snapshots__/datapack-resource.test.ts.js b/__snapshots__/datapack-resource.test.ts.js new file mode 100644 index 0000000..8e514b7 --- /dev/null +++ b/__snapshots__/datapack-resource.test.ts.js @@ -0,0 +1,283 @@ +exports["Datapack Resource Testing should return the correct data 1"] = { + actions: [], + errors: [], + suggestions: [], + misc: [ + { + group: "InvalidTagNoValues", + filePath: + "E:\\mcfunction-langserver\\test_data\\test_world\\datapacks\\ExampleDatapack\\data\\minecraft\\tags\\functions\\tick.json", + kind: "ClearError" + }, + { + group: "InvalidTagValuesNotArray", + filePath: + "E:\\mcfunction-langserver\\test_data\\test_world\\datapacks\\ExampleDatapack\\data\\minecraft\\tags\\functions\\tick.json", + kind: "ClearError" + }, + { + group: "InvalidTagValuesNotString", + filePath: + "E:\\mcfunction-langserver\\test_data\\test_world\\datapacks\\ExampleDatapack\\data\\minecraft\\tags\\functions\\tick.json", + kind: "ClearError" + }, + { + group: "InvalidTagValuesDuplicates", + filePath: + "E:\\mcfunction-langserver\\test_data\\test_world\\datapacks\\ExampleDatapack\\data\\minecraft\\tags\\functions\\tick.json", + kind: "ClearError" + }, + { + group: "InvalidTagValuesUnknown", + filePath: + "E:\\mcfunction-langserver\\test_data\\test_world\\datapacks\\ExampleDatapack\\data\\minecraft\\tags\\functions\\tick.json", + kind: "ClearError" + } + ], + data: { + location: "E:\\mcfunction-langserver\\test_data\\test_world\\datapacks", + packnamesmap: { + ExampleDatapack: 0, + ExampleDatapack2: 1 + }, + packs: { + "0": { + id: 0, + data: { + functions: [ + { + namespace: "test_namespace", + pack: 0, + path: "function" + } + ], + function_tags: [ + { + namespace: "minecraft", + pack: 0, + path: "tick", + data: { + values: ["test_namespace:function"] + } + } + ] + }, + name: "ExampleDatapack", + mcmeta: { + pack: { + pack_format: 3, + description: "test datapack" + } + } + }, + "1": { + id: 1, + data: { + functions: [ + { + namespace: "test_namespace", + pack: 1, + path: "function" + } + ] + }, + name: "ExampleDatapack2", + mcmeta: { + pack: { + pack_format: 3, + description: "test datapack 2" + } + } + } + }, + nbt: { + level: { + Data: { + RandomSeed: { + low: -2021306761, + high: -1388691519, + unsigned: false + }, + generatorName: "flat", + BorderCenterZ: 0, + Difficulty: 2, + BorderSizeLerpTime: { + low: 0, + high: 0, + unsigned: false + }, + raining: 0, + DimensionData: { + "1": { + DragonFight: { + Gateways: [ + 6, + 11, + 18, + 10, + 16, + 3, + 7, + 8, + 5, + 1, + 14, + 12, + 0, + 2, + 19, + 15, + 4, + 9, + 17, + 13 + ], + DragonKilled: 1, + PreviouslyKilled: 1 + } + } + }, + Time: { + low: 404036, + high: 0, + unsigned: false + }, + GameType: 1, + MapFeatures: 1, + BorderCenterX: 0, + BorderDamagePerBlock: 0.2, + BorderWarningBlocks: 5, + BorderSizeLerpTarget: 60000000, + Version: { + Snapshot: 0, + Id: 1628, + Name: "1.13.1" + }, + DayTime: { + low: 18000, + high: 0, + unsigned: false + }, + initialized: 1, + allowCommands: 1, + SizeOnDisk: { + low: 0, + high: 0, + unsigned: false + }, + CustomBossEvents: { + bossbar: { + PlayBossMusic: 0, + CreateWorldFog: 0, + Max: 20, + Color: "red", + Visible: 1, + Value: 0, + Overlay: "notched_20", + DarkenScreen: 0, + Name: '{"color":"white","text":"Custom Bar"}', + Players: [ + { + L: { + low: -400175091, + high: -1742195773, + unsigned: false + }, + M: { + low: 1954434988, + high: -161708486, + unsigned: false + } + } + ] + } + }, + GameRules: { + doTileDrops: "true", + doFireTick: "false", + maxCommandChainLength: "65536", + reducedDebugInfo: "false", + naturalRegeneration: "true", + disableElytraMovementCheck: "false", + doMobLoot: "true", + announceAdvancements: "true", + keepInventory: "false", + doEntityDrops: "true", + doLimitedCrafting: "false", + mobGriefing: "true", + randomTickSpeed: "3", + commandBlockOutput: "false", + spawnRadius: "10", + doMobSpawning: "false", + maxEntityCramming: "24", + logAdminCommands: "true", + spectatorsGenerateChunks: "true", + doWeatherCycle: "false", + sendCommandFeedback: "true", + doDaylightCycle: "false", + showDeathMessages: "true" + }, + SpawnY: 4, + rainTime: 34330, + thunderTime: 98129, + SpawnZ: 8, + hardcore: 0, + DifficultyLocked: 0, + SpawnX: 8, + clearWeatherTime: 0, + thundering: 0, + generatorVersion: 0, + version: 19133, + BorderSafeZone: 5, + generatorOptions: { + biome: "minecraft:the_void", + layers: [ + { + block: "minecraft:air", + height: 1 + } + ], + structures: { + decoration: {} + } + }, + LastPlayed: { + low: 2081333130, + high: 357, + unsigned: false + }, + BorderWarningTime: 15, + LevelName: "The Second Hiest", + BorderSize: 60000000, + DataVersion: 1628, + DataPacks: { + Enabled: ["file/ExampleDatapack"], + Disabled: ["vanilla", "file/ExampleDatapack2"] + } + } + } + } + }, + kind: true +}; + +exports["Datapack Resource Testing should return the correct data 2"] = [ + { + namespace: "minecraft", + path: "tick" + } +]; + +exports["Datapack Resource Testing should return the correct data 3"] = [ + { + namespace: "test_namespace", + path: "function" + } +]; + +exports["Datapack Resource Testing should return the correct data 4"] = [ + { + namespace: "test_namespace", + path: "function" + } +]; diff --git a/__snapshots__/double.test.ts.js b/__snapshots__/double.test.ts.js new file mode 100644 index 0000000..d7433b6 --- /dev/null +++ b/__snapshots__/double.test.ts.js @@ -0,0 +1,431 @@ +exports[ + "Double Argument Parser valid float with `.` and space should reject a number less than the minimum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234.5678 ", + expect: { + cursor: 9, + parsing: { + actions: [], + errors: [ + { + code: "argument.double.low", + severity: 1, + substitutions: ["1235.5678", "1234.5678"], + text: + "Float must not be less than 1235.5678, found 1234.5678", + range: { + start: 0, + end: 9 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Double Argument Parser valid float with `.` and space should reject a number more than the maximum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234.5678 ", + expect: { + cursor: 9, + parsing: { + actions: [], + errors: [ + { + code: "argument.double.big", + severity: 1, + substitutions: ["1233.5678", "1234.5678"], + text: + "Float must not be more than 1233.5678, found 1234.5678", + range: { + start: 0, + end: 9 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Double Argument Parser valid float with `.` and space should succeed with no constraints runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234.5678 ", + expect: { + cursor: 9, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Double Argument Parser valid float with `.` should reject a number less than the minimum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234.5678", + expect: { + cursor: 9, + parsing: { + actions: [], + errors: [ + { + code: "argument.double.low", + severity: 1, + substitutions: ["1235.5678", "1234.5678"], + text: + "Float must not be less than 1235.5678, found 1234.5678", + range: { + start: 0, + end: 9 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Double Argument Parser valid float with `.` should reject a number more than the maximum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234.5678", + expect: { + cursor: 9, + parsing: { + actions: [], + errors: [ + { + code: "argument.double.big", + severity: 1, + substitutions: ["1233.5678", "1234.5678"], + text: + "Float must not be more than 1233.5678, found 1234.5678", + range: { + start: 0, + end: 9 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Double Argument Parser valid float with `.` should succeed with no constraints runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234.5678", + expect: { + cursor: 9, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Double Argument Parser valid integer should reject a number less than the minimum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.double.low", + severity: 1, + substitutions: ["1235", "1234"], + text: + "Float must not be less than 1235, found 1234", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Double Argument Parser valid integer should reject a number more than the maximum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.double.big", + severity: 1, + substitutions: ["1233", "1234"], + text: + "Float must not be more than 1233, found 1234", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Double Argument Parser valid integer should succeed with no constraints runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Double Argument Parser valid integer with space should reject a number less than the minimum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234 ", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.double.low", + severity: 1, + substitutions: ["1235", "1234"], + text: + "Float must not be less than 1235, found 1234", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Double Argument Parser valid integer with space should reject a number more than the maximum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234 ", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.double.big", + severity: 1, + substitutions: ["1233", "1234"], + text: + "Float must not be more than 1233, found 1234", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Double Argument Parser valid integer with space should succeed with no constraints runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234 ", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; diff --git a/__snapshots__/dummy1.test.ts.js b/__snapshots__/dummy1.test.ts.js new file mode 100644 index 0000000..1ef70bb --- /dev/null +++ b/__snapshots__/dummy1.test.ts.js @@ -0,0 +1,71 @@ +exports["dummyParser1 should default to 3 when not given any properties 1"] = { + cursor: 3, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + "hello", + { + start: 1, + text: "welcome" + } + ], + misc: [], + kind: true + } +}; + +exports["dummyParser1 should not succeed if there is not enough room 1"] = { + cursor: 0, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + "hello", + { + start: 1, + text: "welcome" + } + ], + misc: [], + kind: false + } +}; + +exports["dummyParser1 should read the specified number of characters 1"] = { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + "hello", + { + start: 2, + text: "welcome" + } + ], + misc: [], + kind: true + } +}; diff --git a/__snapshots__/entity.test.ts.js b/__snapshots__/entity.test.ts.js new file mode 100644 index 0000000..c14e7a4 --- /dev/null +++ b/__snapshots__/entity.test.ts.js @@ -0,0 +1,2040 @@ +exports[ + "entity parser entity selectors should parse basic selectors runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "@p", + expect: { + cursor: 2, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "@p", + expect: { + cursor: 2, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "@", + expect: { + cursor: 1, + parsing: { + actions: [], + errors: [ + { + code: "parsing.expected.option", + severity: 1, + substitutions: ["a,e,p,r,s", ""], + text: "Expected string from [a,e,p,r,s], got ''", + range: { + start: 1, + end: 1 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "", + expect: { + cursor: 0, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + } + ], + misc: [], + kind: false + } + } + } + ] +}; + +exports[ + "entity parser entity selectors should parse inputs with arguments runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "@a[]", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "@e[", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [ + { + code: "argument.entity.argument.unknown", + severity: 1, + substitutions: [], + text: "Unknown argument type 'undefined'", + range: { + start: 3, + end: 3 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 3, + text: "advancements" + }, + { + start: 3, + text: "distance" + }, + { + start: 3, + text: "dx" + }, + { + start: 3, + text: "dy" + }, + { + start: 3, + text: "dz" + }, + { + start: 3, + text: "gamemode" + }, + { + start: 3, + text: "level" + }, + { + start: 3, + text: "limit" + }, + { + start: 3, + text: "name" + }, + { + start: 3, + text: "nbt" + }, + { + start: 3, + text: "scores" + }, + { + start: 3, + text: "sort" + }, + { + start: 3, + text: "tag" + }, + { + start: 3, + text: "team" + }, + { + start: 3, + text: "type" + }, + { + start: 3, + text: "x" + }, + { + start: 3, + text: "x_rotation" + }, + { + start: 3, + text: "y" + }, + { + start: 3, + text: "y_rotation" + }, + { + start: 3, + text: "z" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "@e[x=12]", + expect: { + cursor: 8, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 4, + text: "=" + }, + { + start: 7, + text: "]" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "@e[x=30000000]", + expect: { + cursor: 14, + parsing: { + actions: [], + errors: [ + { + code: "argument.entity.option.number.abovemax", + severity: 1, + substitutions: ["x", "29999999"], + text: "Argument 'x' is greater than 29999999", + range: { + start: 5, + end: 13 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 4, + text: "=" + }, + { + start: 13, + text: "]" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "@p[x=10,x=13]", + expect: { + cursor: 13, + parsing: { + actions: [], + errors: [ + { + code: "argument.entity.option.duplicate", + severity: 1, + substitutions: ["x"], + text: "Duplicate argument 'x'", + range: { + start: 8, + end: 12 + } + } + ], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 4, + text: "=" + }, + { + start: 7, + text: "]" + }, + { + start: 7, + text: "," + }, + { + start: 9, + text: "=" + }, + { + start: 12, + text: "]" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "@a[dx=123]", + expect: { + cursor: 10, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 5, + text: "=" + }, + { + start: 9, + text: "]" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "@p[gamemode=survival]", + expect: { + cursor: 21, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 11, + text: "=" + }, + { + start: 12, + text: "!" + }, + { + start: 20, + text: "]" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "@a[gamemode=error]", + expect: { + cursor: 18, + parsing: { + actions: [], + errors: [ + { + code: "argument.entity.option.gamemode.invalid", + severity: 1, + substitutions: ["error"], + text: "Invalid gamemode 'error'", + range: { + start: 12, + end: 17 + } + } + ], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 11, + text: "=" + }, + { + start: 12, + text: "!" + }, + { + start: 17, + text: "]" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "@p[gamemode=", + expect: { + cursor: 12, + parsing: { + actions: [], + errors: [ + { + code: "argument.entity.option.gamemode.expected", + severity: 1, + substitutions: [], + text: "Expected gamemode", + range: { + start: 12, + end: 12 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 11, + text: "=" + }, + { + start: 12, + text: "!" + }, + { + start: 12, + text: "survival" + }, + { + start: 12, + text: "creative" + }, + { + start: 12, + text: "adventure" + }, + { + start: 12, + text: "spectator" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "@a[gamemode=creative,gamemode=creative]", + expect: { + cursor: 39, + parsing: { + actions: [], + errors: [ + { + code: "argument.entity.option.noinfo", + severity: 1, + substitutions: [], + text: "Argument 'undefined' is redundant", + range: { + start: 30, + end: 38 + } + } + ], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 11, + text: "=" + }, + { + start: 12, + text: "!" + }, + { + start: 20, + text: "]" + }, + { + start: 20, + text: "," + }, + { + start: 29, + text: "=" + }, + { + start: 30, + text: "!" + }, + { + start: 38, + text: "]" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "@a[gamemode=survival,gamemode=!survival]", + expect: { + cursor: 40, + parsing: { + actions: [], + errors: [ + { + code: "argument.entity.option.nointersect", + severity: 1, + substitutions: ["gamemode"], + text: "Argument 'gamemode' cannot match any entity", + range: { + start: 30, + end: 39 + } + } + ], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 11, + text: "=" + }, + { + start: 12, + text: "!" + }, + { + start: 20, + text: "]" + }, + { + start: 20, + text: "," + }, + { + start: 29, + text: "=" + }, + { + start: 30, + text: "!" + }, + { + start: 39, + text: "]" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "@e[tag=foo]", + expect: { + cursor: 11, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 6, + text: "=" + }, + { + start: 7, + text: "!" + }, + { + start: 10, + text: "]" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "@a[tag=one,tag=two,tag=three]", + expect: { + cursor: 29, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 6, + text: "=" + }, + { + start: 7, + text: "!" + }, + { + start: 10, + text: "]" + }, + { + start: 10, + text: "," + }, + { + start: 14, + text: "=" + }, + { + start: 15, + text: "!" + }, + { + start: 18, + text: "]" + }, + { + start: 18, + text: "," + }, + { + start: 22, + text: "=" + }, + { + start: 23, + text: "!" + }, + { + start: 28, + text: "]" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "@p[tag=foo,tag=!foo]", + expect: { + cursor: 20, + parsing: { + actions: [], + errors: [ + { + code: "argument.entity.option.nointersect", + severity: 1, + substitutions: ["tag"], + text: "Argument 'tag' cannot match any entity", + range: { + start: 15, + end: 19 + } + } + ], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 6, + text: "=" + }, + { + start: 7, + text: "!" + }, + { + start: 10, + text: "]" + }, + { + start: 10, + text: "," + }, + { + start: 14, + text: "=" + }, + { + start: 15, + text: "!" + }, + { + start: 19, + text: "]" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "@r[tag=foo,tag=]", + expect: { + cursor: 16, + parsing: { + actions: [], + errors: [ + { + code: "argument.entity.option.nointersect", + severity: 1, + substitutions: ["type"], + text: "Argument 'type' cannot match any entity", + range: { + start: 15, + end: 15 + } + } + ], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 6, + text: "=" + }, + { + start: 7, + text: "!" + }, + { + start: 10, + text: "]" + }, + { + start: 10, + text: "," + }, + { + start: 14, + text: "=" + }, + { + start: 15, + text: "!" + }, + { + start: 15, + text: "]" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "@e[tag=]", + expect: { + cursor: 8, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 6, + text: "=" + }, + { + start: 7, + text: "!" + }, + { + start: 7, + text: "]" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "@e[type=cow]", + expect: { + cursor: 12, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "cow" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "@" + }, + { + start: 1, + text: "a" + }, + { + start: 1, + text: "e" + }, + { + start: 1, + text: "p" + }, + { + start: 1, + text: "r" + }, + { + start: 1, + text: "s" + }, + { + start: 2, + text: "[" + }, + { + start: 3, + text: "]" + }, + { + start: 7, + text: "=" + }, + { + start: 8, + text: "!" + }, + { + start: 11, + text: "]" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "cow" + } + ] + } + }, + kind: true + } + } + } + ] +}; + +exports["entity parser should parse UUIDS runParsertest 1"] = { + name: "runParsertest", + behavior: [ + { + given: "f65c863a-747e-4fac-9828-33c3e825d00d", + expect: { + cursor: 36, + parsing: { + actions: [], + errors: [ + { + code: "argument.entity.uuid", + severity: 2, + substitutions: [], + text: + "Selecting an entity based on its UUID may cause instability [This warning can be disabled in the settings (Although not at the moment)]", + range: { + start: 0, + end: 36 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "ec-0-0-0-1", + expect: { + cursor: 10, + parsing: { + actions: [], + errors: [ + { + code: "argument.entity.uuid", + severity: 2, + substitutions: [], + text: + "Selecting an entity based on its UUID may cause instability [This warning can be disabled in the settings (Although not at the moment)]", + range: { + start: 0, + end: 10 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports["entity parser should parse player names runParsertest 1"] = { + name: "runParsertest", + behavior: [ + { + given: "FooBar", + expect: { + cursor: 6, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "", + expect: { + cursor: 0, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + } + } + } + ] +}; + +exports[ + "entity parser should suggest players in the scoreboard runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "Player1", + expect: { + cursor: 7, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "Player1" + }, + { + start: 0, + text: "Player2" + } + ], + misc: [], + data: { + otherEntity: { + ids: [ + { + namespace: "minecraft", + path: "player" + } + ] + } + }, + kind: true + } + } + }, + { + given: "", + expect: { + cursor: 0, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "Player1" + }, + { + start: 0, + text: "Player2" + } + ], + misc: [], + kind: false + } + } + } + ] +}; diff --git a/__snapshots__/float.test.ts.js b/__snapshots__/float.test.ts.js new file mode 100644 index 0000000..09c614a --- /dev/null +++ b/__snapshots__/float.test.ts.js @@ -0,0 +1,513 @@ +exports[ + "Float Argument Parser should fail when the number is bigger than the java maximum float runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: + "1000000000000000000000000000000000000000000000000000000000000", + expect: { + cursor: 61, + parsing: { + actions: [], + errors: [ + { + code: "argument.float.big", + severity: 1, + substitutions: ["3.4e+38", "1e+60"], + text: + "Float must not be more than 3.4e+38, found 1e+60", + range: { + start: 0, + end: 61 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser should fail when the number is less than the java minimum float runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: + "-1000000000000000000000000000000000000000000000000000000000000", + expect: { + cursor: 62, + parsing: { + actions: [], + errors: [ + { + code: "argument.float.low", + severity: 1, + substitutions: ["-3.4e+38", "-1e+60"], + text: + "Float must not be less than -3.4e+38, found -1e+60", + range: { + start: 0, + end: 62 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser valid float with `.` and space should reject a number less than the minimum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234.5678 ", + expect: { + cursor: 9, + parsing: { + actions: [], + errors: [ + { + code: "argument.float.low", + severity: 1, + substitutions: ["1235.5678", "1234.5678"], + text: + "Float must not be less than 1235.5678, found 1234.5678", + range: { + start: 0, + end: 9 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser valid float with `.` and space should reject a number more than the maximum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234.5678 ", + expect: { + cursor: 9, + parsing: { + actions: [], + errors: [ + { + code: "argument.float.big", + severity: 1, + substitutions: ["1233.5678", "1234.5678"], + text: + "Float must not be more than 1233.5678, found 1234.5678", + range: { + start: 0, + end: 9 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser valid float with `.` and space should succeed with no constraints runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234.5678 ", + expect: { + cursor: 9, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser valid float with `.` should reject a number less than the minimum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234.5678", + expect: { + cursor: 9, + parsing: { + actions: [], + errors: [ + { + code: "argument.float.low", + severity: 1, + substitutions: ["1235.5678", "1234.5678"], + text: + "Float must not be less than 1235.5678, found 1234.5678", + range: { + start: 0, + end: 9 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser valid float with `.` should reject a number more than the maximum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234.5678", + expect: { + cursor: 9, + parsing: { + actions: [], + errors: [ + { + code: "argument.float.big", + severity: 1, + substitutions: ["1233.5678", "1234.5678"], + text: + "Float must not be more than 1233.5678, found 1234.5678", + range: { + start: 0, + end: 9 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser valid float with `.` should succeed with no constraints runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234.5678", + expect: { + cursor: 9, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser valid integer should reject a number less than the minimum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.float.low", + severity: 1, + substitutions: ["1235", "1234"], + text: + "Float must not be less than 1235, found 1234", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser valid integer should reject a number more than the maximum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.float.big", + severity: 1, + substitutions: ["1233", "1234"], + text: + "Float must not be more than 1233, found 1234", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser valid integer should succeed with no constraints runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser valid integer with space should reject a number less than the minimum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234 ", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.float.low", + severity: 1, + substitutions: ["1235", "1234"], + text: + "Float must not be less than 1235, found 1234", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser valid integer with space should reject a number more than the maximum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234 ", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.float.big", + severity: 1, + substitutions: ["1233", "1234"], + text: + "Float must not be more than 1233, found 1234", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Float Argument Parser valid integer with space should succeed with no constraints runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234 ", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; diff --git a/__snapshots__/integer.test.ts.js b/__snapshots__/integer.test.ts.js new file mode 100644 index 0000000..40b285b --- /dev/null +++ b/__snapshots__/integer.test.ts.js @@ -0,0 +1,334 @@ +exports[ + "Integer Argument Parser should fail when the integer is bigger than the java max runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1000000000000000", + expect: { + cursor: 16, + parsing: { + actions: [], + errors: [ + { + code: "argument.integer.big", + severity: 1, + substitutions: ["2147483647", "1000000000000000"], + text: + "Integer must not be more than 2147483647, found 1000000000000000", + range: { + start: 0, + end: 16 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Integer Argument Parser should fail when the integer is less than the java min runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "-1000000000000000", + expect: { + cursor: 17, + parsing: { + actions: [], + errors: [ + { + code: "argument.integer.low", + severity: 1, + substitutions: ["-2147483648", "-1000000000000000"], + text: + "Integer must not be less than -2147483648, found -1000000000000000", + range: { + start: 0, + end: 17 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Integer Argument Parser should fail when there is an invalid integer runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "notint", + expect: { + cursor: 0, + parsing: { + actions: [], + errors: [ + { + code: "parsing.int.expected", + severity: 1, + substitutions: [], + text: "Expected integer", + range: { + start: 0, + end: 6 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + } + } + } + ] +}; + +exports[ + "Integer Argument Parser valid integer should fail with a value less than the minimum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.integer.low", + severity: 1, + substitutions: ["1235", "1234"], + text: + "Integer must not be less than 1235, found 1234", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Integer Argument Parser valid integer should fail with a value more than the maximum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.integer.big", + severity: 1, + substitutions: ["1233", "1234"], + text: + "Integer must not be more than 1233, found 1234", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Integer Argument Parser valid integer should succeed with an unconstrained value runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Integer Argument Parser valid integer with space should fail with a value less than the minimum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234 ", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.integer.low", + severity: 1, + substitutions: ["1235", "1234"], + text: + "Integer must not be less than 1235, found 1234", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Integer Argument Parser valid integer with space should fail with a value more than the maximum runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234 ", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [ + { + code: "argument.integer.big", + severity: 1, + substitutions: ["1233", "1234"], + text: + "Integer must not be more than 1233, found 1234", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "Integer Argument Parser valid integer with space should succeed with an unconstrained value runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "1234 ", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; diff --git a/__snapshots__/item.test.ts.js b/__snapshots__/item.test.ts.js new file mode 100644 index 0000000..0077500 --- /dev/null +++ b/__snapshots__/item.test.ts.js @@ -0,0 +1,484 @@ +exports[ + "item predicate parser should give the correct output for various inputs runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "test:item_one", + expect: { + cursor: 13, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + text: "test:item_one", + start: 0 + }, + { + text: "test:item_two", + start: 0 + }, + { + text: "test:item_three", + start: 0 + }, + { + text: "test:item_four", + start: 0 + }, + { + text: "test:item_four_one", + start: 0 + }, + { + text: "minecraft:apple", + start: 0 + }, + { + text: "minecraft:coal", + start: 0 + }, + { + start: 13, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "#test:item_tag_one", + expect: { + cursor: 18, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 19, + start: 1, + text: "test:item_tag_one" + }, + { + kind: 19, + start: 1, + text: "test:item_tag_two" + }, + { + kind: 19, + start: 1, + text: "test:item_tag_two_one" + }, + { + start: 18, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "#test:item_tag_two", + expect: { + cursor: 18, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 19, + start: 1, + text: "test:item_tag_one" + }, + { + kind: 19, + start: 1, + text: "test:item_tag_two" + }, + { + kind: 19, + start: 1, + text: "test:item_tag_two_one" + }, + { + start: 18, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "#test:bad_tag", + expect: { + cursor: 13, + parsing: { + actions: [], + errors: [ + { + code: "arguments.item.tag.unknown", + severity: 1, + substitutions: ["test:bad_tag"], + text: "Unknown item tag 'test:bad_tag'", + range: { + start: 0, + end: 13 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 19, + start: 1, + text: "test:item_tag_one" + }, + { + kind: 19, + start: 1, + text: "test:item_tag_two" + }, + { + kind: 19, + start: 1, + text: "test:item_tag_two_one" + }, + { + start: 13, + text: "{" + } + ], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "item stack parser (no tags) should have the correct output for various inputs runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "test:item_one", + expect: { + cursor: 13, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + text: "test:item_one", + start: 0 + }, + { + text: "test:item_two", + start: 0 + }, + { + text: "test:item_three", + start: 0 + }, + { + text: "test:item_four", + start: 0 + }, + { + text: "test:item_four_one", + start: 0 + }, + { + text: "minecraft:apple", + start: 0 + }, + { + text: "minecraft:coal", + start: 0 + }, + { + start: 13, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "#test:item_tag_one", + expect: { + cursor: 18, + parsing: { + actions: [], + errors: [ + { + code: "argument.item.tag.disallowed", + severity: 1, + substitutions: [], + text: "Tags aren't allowed here, only actual items", + range: { + start: 0, + end: 18 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + } + } + }, + { + given: "#test:bad_tag", + expect: { + cursor: 13, + parsing: { + actions: [], + errors: [ + { + code: "argument.item.tag.disallowed", + severity: 1, + substitutions: [], + text: "Tags aren't allowed here, only actual items", + range: { + start: 0, + end: 13 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + } + } + }, + { + given: "test:item_four", + expect: { + cursor: 14, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + text: "test:item_one", + start: 0 + }, + { + text: "test:item_two", + start: 0 + }, + { + text: "test:item_three", + start: 0 + }, + { + text: "test:item_four", + start: 0 + }, + { + text: "test:item_four_one", + start: 0 + }, + { + text: "minecraft:apple", + start: 0 + }, + { + text: "minecraft:coal", + start: 0 + }, + { + start: 14, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "test:bad_item", + expect: { + cursor: 13, + parsing: { + actions: [], + errors: [ + { + code: "argument.item.id.invalid", + severity: 1, + substitutions: ["test:bad_item"], + text: "Unknown item 'test:bad_item'", + range: { + start: 0, + end: 13 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + text: "test:item_one", + start: 0 + }, + { + text: "test:item_two", + start: 0 + }, + { + text: "test:item_three", + start: 0 + }, + { + text: "test:item_four", + start: 0 + }, + { + text: "test:item_four_one", + start: 0 + }, + { + text: "minecraft:apple", + start: 0 + }, + { + text: "minecraft:coal", + start: 0 + }, + { + start: 13, + text: "{" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "minecraft:coal", + expect: { + cursor: 14, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + text: "test:item_one", + start: 0 + }, + { + text: "test:item_two", + start: 0 + }, + { + text: "test:item_three", + start: 0 + }, + { + text: "test:item_four", + start: 0 + }, + { + text: "test:item_four_one", + start: 0 + }, + { + text: "minecraft:apple", + start: 0 + }, + { + text: "minecraft:coal", + start: 0 + }, + { + start: 14, + text: "{" + } + ], + misc: [], + kind: true + } + } + } + ] +}; diff --git a/__snapshots__/lists.test.ts.js b/__snapshots__/lists.test.ts.js new file mode 100644 index 0000000..ac890c6 --- /dev/null +++ b/__snapshots__/lists.test.ts.js @@ -0,0 +1,437 @@ +exports[ + "list tests parse() should parse all of the values correctly runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "foo", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "foo" + }, + { + kind: 20, + start: 0, + text: "bar" + }, + { + kind: 20, + start: 0, + text: "baz" + }, + { + kind: 20, + start: 0, + text: "hello" + }, + { + kind: 20, + start: 0, + text: "world" + }, + { + kind: 20, + start: 0, + text: "++" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "bar", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "foo" + }, + { + kind: 20, + start: 0, + text: "bar" + }, + { + kind: 20, + start: 0, + text: "baz" + }, + { + kind: 20, + start: 0, + text: "hello" + }, + { + kind: 20, + start: 0, + text: "world" + }, + { + kind: 20, + start: 0, + text: "++" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "baz", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "foo" + }, + { + kind: 20, + start: 0, + text: "bar" + }, + { + kind: 20, + start: 0, + text: "baz" + }, + { + kind: 20, + start: 0, + text: "hello" + }, + { + kind: 20, + start: 0, + text: "world" + }, + { + kind: 20, + start: 0, + text: "++" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "hello", + expect: { + cursor: 5, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "foo" + }, + { + kind: 20, + start: 0, + text: "bar" + }, + { + kind: 20, + start: 0, + text: "baz" + }, + { + kind: 20, + start: 0, + text: "hello" + }, + { + kind: 20, + start: 0, + text: "world" + }, + { + kind: 20, + start: 0, + text: "++" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "world", + expect: { + cursor: 5, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "foo" + }, + { + kind: 20, + start: 0, + text: "bar" + }, + { + kind: 20, + start: 0, + text: "baz" + }, + { + kind: 20, + start: 0, + text: "hello" + }, + { + kind: 20, + start: 0, + text: "world" + }, + { + kind: 20, + start: 0, + text: "++" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "++", + expect: { + cursor: 2, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "foo" + }, + { + kind: 20, + start: 0, + text: "bar" + }, + { + kind: 20, + start: 0, + text: "baz" + }, + { + kind: 20, + start: 0, + text: "hello" + }, + { + kind: 20, + start: 0, + text: "world" + }, + { + kind: 20, + start: 0, + text: "++" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "badinput", + expect: { + cursor: 8, + parsing: { + actions: [], + errors: [ + { + code: "arg.ex", + severity: 1, + substitutions: ["badinput"], + text: "example error", + range: { + start: 0, + end: 8 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "foo" + }, + { + kind: 20, + start: 0, + text: "bar" + }, + { + kind: 20, + start: 0, + text: "baz" + }, + { + kind: 20, + start: 0, + text: "hello" + }, + { + kind: 20, + start: 0, + text: "world" + }, + { + kind: 20, + start: 0, + text: "++" + } + ], + misc: [], + kind: false + } + } + }, + { + given: "", + expect: { + cursor: 0, + parsing: { + actions: [], + errors: [ + { + code: "arg.ex", + severity: 1, + substitutions: [""], + text: "example error", + range: { + start: 0, + end: 0 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "foo" + }, + { + kind: 20, + start: 0, + text: "bar" + }, + { + kind: 20, + start: 0, + text: "baz" + }, + { + kind: 20, + start: 0, + text: "hello" + }, + { + kind: 20, + start: 0, + text: "world" + }, + { + kind: 20, + start: 0, + text: "++" + } + ], + misc: [], + kind: false + } + } + } + ] +}; diff --git a/__snapshots__/literal.test.ts.js b/__snapshots__/literal.test.ts.js new file mode 100644 index 0000000..ceb8584 --- /dev/null +++ b/__snapshots__/literal.test.ts.js @@ -0,0 +1,117 @@ +exports[ + "Literal Argument Parser should correctly handle various input runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "test", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "test" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "test ", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: "fail ", + expect: { + cursor: 0, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + } + } + }, + { + given: "tesnot", + expect: { + cursor: 0, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + } + } + }, + { + given: "tes", + expect: { + cursor: 0, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "test" + } + ], + misc: [], + kind: false + } + } + } + ] +}; diff --git a/__snapshots__/namespace-list.test.ts.js b/__snapshots__/namespace-list.test.ts.js new file mode 100644 index 0000000..d64ec83 --- /dev/null +++ b/__snapshots__/namespace-list.test.ts.js @@ -0,0 +1,209 @@ +exports[ + "NamespaceListParser should work correctly for various inputs runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "minecraft:test", + expect: { + cursor: 14, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "minecraft:test" + }, + { + start: 0, + text: "minecraft:test2" + }, + { + start: 0, + text: "minecraft:other" + }, + { + start: 0, + text: "something:hello" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "something:hello", + expect: { + cursor: 15, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "minecraft:test" + }, + { + start: 0, + text: "minecraft:test2" + }, + { + start: 0, + text: "minecraft:other" + }, + { + start: 0, + text: "something:hello" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "unknown:unknown", + expect: { + cursor: 15, + parsing: { + actions: [], + errors: [ + { + code: "namespace.test.unknown", + severity: 1, + substitutions: ["unknown:unknown"], + text: "Unkown", + range: { + start: 0, + end: 15 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "minecraft:test" + }, + { + start: 0, + text: "minecraft:test2" + }, + { + start: 0, + text: "minecraft:other" + }, + { + start: 0, + text: "something:hello" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "fail:fail:fail", + expect: { + cursor: 14, + parsing: { + actions: [], + errors: [ + { + code: "argument.id.invalid", + severity: 1, + substitutions: [":", "fail:fail:fail"], + text: + "The seperator ':' should not be repeated in the ID 'fail:fail:fail'", + range: { + start: 9, + end: 10 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: false + } + } + }, + { + given: "", + expect: { + cursor: 0, + parsing: { + actions: [], + errors: [ + { + code: "namespace.test.unknown", + severity: 1, + substitutions: ["minecraft:"], + text: "Unkown", + range: { + start: 0, + end: 0 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "minecraft:test" + }, + { + start: 0, + text: "minecraft:test2" + }, + { + start: 0, + text: "minecraft:other" + }, + { + start: 0, + text: "something:hello" + } + ], + misc: [], + kind: true + } + } + } + ] +}; diff --git a/__snapshots__/nbt.test.ts.js b/__snapshots__/nbt.test.ts.js new file mode 100644 index 0000000..0702720 --- /dev/null +++ b/__snapshots__/nbt.test.ts.js @@ -0,0 +1,318 @@ +exports["SNBT Parser should correctly parse a compound runParsertest 1"] = { + name: "runParsertest", + behavior: [ + { + given: "{foo:{bar:baz}}", + expect: { + cursor: 15, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "{" + }, + { + start: 1, + text: "}" + }, + { + start: 4, + text: ":" + }, + { + start: 5, + text: "{" + }, + { + start: 6, + text: "}" + }, + { + start: 9, + text: ":" + }, + { + start: 13, + text: "," + }, + { + start: 13, + text: "}" + }, + { + start: 14, + text: "," + }, + { + start: 14, + text: "}" + } + ], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "SNBT Parser should return give the right results with vanilla data for a zombie runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "{", + expect: { + cursor: 1, + parsing: { + actions: [], + errors: [ + { + code: "argument.nbt.compound.nokey", + severity: 1, + substitutions: [], + text: "Expected key", + range: { + start: 1, + end: 1 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "{" + }, + { + start: 1, + text: "}" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "{HandItems:[{", + expect: { + cursor: 13, + parsing: { + actions: [], + errors: [ + { + code: "argument.nbt.compound.nokey", + severity: 1, + substitutions: [], + text: "Expected key", + range: { + start: 13, + end: 13 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "{" + }, + { + start: 1, + text: "}" + }, + { + start: 10, + text: ":" + }, + { + start: 11, + text: "[" + }, + { + start: 12, + text: "]" + }, + { + start: 12, + text: "{" + }, + { + start: 13, + text: "}" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "{HandItems:[{display:{", + expect: { + cursor: 22, + parsing: { + actions: [], + errors: [ + { + code: "argument.nbt.compound.nokey", + severity: 1, + substitutions: [], + text: "Expected key", + range: { + start: 22, + end: 22 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "{" + }, + { + start: 1, + text: "}" + }, + { + start: 10, + text: ":" + }, + { + start: 11, + text: "[" + }, + { + start: 12, + text: "]" + }, + { + start: 12, + text: "{" + }, + { + start: 13, + text: "}" + }, + { + start: 20, + text: ":" + }, + { + start: 21, + text: "{" + }, + { + start: 22, + text: "}" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "{HandItems:[{display:{", + expect: { + cursor: 22, + parsing: { + actions: [], + errors: [ + { + code: "argument.nbt.compound.nokey", + severity: 1, + substitutions: [], + text: "Expected key", + range: { + start: 22, + end: 22 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "{" + }, + { + start: 1, + text: "}" + }, + { + start: 10, + text: ":" + }, + { + start: 11, + text: "[" + }, + { + start: 12, + text: "]" + }, + { + start: 12, + text: "{" + }, + { + start: 13, + text: "}" + }, + { + start: 20, + text: ":" + }, + { + start: 21, + text: "{" + }, + { + start: 22, + text: "}" + } + ], + misc: [], + kind: true + } + } + } + ] +}; diff --git a/__snapshots__/parse.test.ts.js b/__snapshots__/parse.test.ts.js new file mode 100644 index 0000000..6e189dd --- /dev/null +++ b/__snapshots__/parse.test.ts.js @@ -0,0 +1,137 @@ +exports[ + "parseCommand() Multi Argument Tests should not add a node when a node which follows fails 1" +] = { + actions: [], + nodes: [ + { + context: {}, + final: {}, + high: 3, + low: 0, + path: ["test1"] + } + ], + errors: [ + { + code: "command.parsing.matchless", + severity: 1, + substitutions: ["hel1"], + text: "No nodes which matched 'hel1' found", + range: { + start: 4, + end: 8 + } + } + ] +}; + +exports[ + "parseCommand() Multi Argument Tests should only add a space between nodes 1" +] = { + actions: [], + nodes: [ + { + context: {}, + final: {}, + high: 7, + low: 4, + path: ["test1", "testchild1"] + }, + { + context: {}, + high: 3, + low: 0, + path: ["test1"] + } + ], + errors: [ + { + code: "parsing.command.executable", + severity: 2, + substitutions: ["hel hel"], + text: "The command 'hel hel' cannot be run.", + range: { + start: 0, + end: 7 + } + } + ] +}; + +exports[ + "parseCommand() single argument tests should add an error if there text at the start not matched by a node 1" +] = { + actions: [], + nodes: [], + errors: [ + { + code: "command.parsing.matchless", + severity: 1, + substitutions: ["hi"], + text: "No nodes which matched 'hi' found", + range: { + start: 0, + end: 2 + } + } + ] +}; + +exports[ + "parseCommand() single argument tests should parse an executable, valid, command as such 1" +] = { + actions: [], + nodes: [ + { + context: {}, + final: {}, + high: 3, + low: 0, + path: ["test1"] + } + ], + errors: [] +}; + +exports[ + "parseCommand() single argument tests should parse an executable, valid, command as such prefixed by whitespace 1" +] = { + actions: [], + nodes: [ + { + context: {}, + final: {}, + high: 6, + low: 3, + path: ["test1"] + } + ], + errors: [] +}; + +exports[ + "parseCommand() single argument tests should return a warning from a command which cannot be executed 1" +] = { + actions: [], + nodes: [ + { + context: {}, + final: {}, + high: 5, + low: 0, + path: ["test2"] + } + ], + errors: [ + { + code: "parsing.command.executable", + severity: 2, + substitutions: ["hello"], + text: "The command 'hello' cannot be run.", + range: { + start: 0, + end: 5 + } + } + ] +}; diff --git a/__snapshots__/range.test.ts.js b/__snapshots__/range.test.ts.js new file mode 100644 index 0000000..bc688ba --- /dev/null +++ b/__snapshots__/range.test.ts.js @@ -0,0 +1,536 @@ +exports[ + "range parser float range should do the right thing for different inputs runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "10", + expect: { + cursor: 2, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + }, + { + start: 2, + text: ".." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "..34", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "12..34", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + }, + { + start: 3, + text: ".." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "..", + expect: { + cursor: 2, + parsing: { + actions: [], + errors: [ + { + code: "parsing.float.expected", + severity: 1, + substitutions: [], + text: "Expected float", + range: { + start: 2, + end: 2 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + } + ], + misc: [], + kind: false + } + } + }, + { + given: "7..-12", + expect: { + cursor: 2, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + }, + { + start: 2, + text: ".." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "5..5", + expect: { + cursor: 2, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + }, + { + start: 2, + text: ".." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "9.32", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + }, + { + start: 4, + text: ".." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "3.12..", + expect: { + cursor: 6, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + }, + { + start: 4, + text: ".." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "...4", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "3.4..0.2", + expect: { + cursor: 8, + parsing: { + actions: [ + { + data: "0.2..3.4", + high: 8, + low: 0, + type: "format" + } + ], + errors: [ + { + code: "argument.range.swapped", + severity: 1, + substitutions: [], + text: "Min cannot be bigger than max", + range: { + start: 0, + end: 8 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + }, + { + start: 3, + text: ".." + } + ], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "range parser int range should do the right thing for different inputs runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "10", + expect: { + cursor: 2, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + }, + { + start: 2, + text: ".." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "..34", + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "12..34", + expect: { + cursor: 6, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + }, + { + start: 2, + text: ".." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "..", + expect: { + cursor: 2, + parsing: { + actions: [], + errors: [ + { + code: "parsing.int.expected", + severity: 1, + substitutions: [], + text: "Expected integer", + range: { + start: 2, + end: 2 + } + } + ], + suggestions: [], + misc: [], + kind: false + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + } + ], + misc: [], + kind: false + } + } + }, + { + given: "7..-12", + expect: { + cursor: 6, + parsing: { + actions: [ + { + data: "-12..7", + high: 6, + low: 0, + type: "format" + } + ], + errors: [ + { + code: "argument.range.swapped", + severity: 1, + substitutions: [], + text: "Min cannot be bigger than max", + range: { + start: 0, + end: 6 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + }, + { + start: 1, + text: ".." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "5..5", + expect: { + cursor: 4, + parsing: { + actions: [ + { + data: "5", + high: 4, + low: 0, + type: "format" + } + ], + errors: [ + { + code: "argument.range.equal", + severity: 4, + substitutions: ["5"], + text: + "Min and max are eqaul and can be replaced by '5'", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: ".." + }, + { + start: 1, + text: ".." + } + ], + misc: [], + kind: true + } + } + } + ] +}; diff --git a/__snapshots__/resources.test.ts.js b/__snapshots__/resources.test.ts.js new file mode 100644 index 0000000..91f65b4 --- /dev/null +++ b/__snapshots__/resources.test.ts.js @@ -0,0 +1,144 @@ +exports[ + "function parser should have the correct behaviour for various inputs runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: "minecraft:test", + expect: { + cursor: 14, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 2, + start: 0, + text: "minecraft:test" + }, + { + kind: 2, + start: 0, + text: "minecraft:test2" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "minecraft:unknown", + expect: { + cursor: 17, + parsing: { + actions: [], + errors: [ + { + code: "arguments.function.unknown", + severity: 1, + substitutions: ["minecraft:unknown"], + text: "Unknown function 'minecraft:unknown'", + range: { + start: 0, + end: 17 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 2, + start: 0, + text: "minecraft:test" + }, + { + kind: 2, + start: 0, + text: "minecraft:test2" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "#minecraft:tag", + expect: { + cursor: 14, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 19, + start: 1, + text: "minecraft:tag" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "#minecraft:unknowntag", + expect: { + cursor: 21, + parsing: { + actions: [], + errors: [ + { + code: "arguments.function.tag.unknown", + severity: 1, + substitutions: ["minecraft:unknowntag"], + text: + "Unknown function tag '#minecraft:unknowntag'", + range: { + start: 0, + end: 21 + } + } + ], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 19, + start: 1, + text: "minecraft:tag" + } + ], + misc: [], + kind: true + } + } + } + ] +}; diff --git a/__snapshots__/scoreboard.test.ts.js b/__snapshots__/scoreboard.test.ts.js new file mode 100644 index 0000000..fdffc69 --- /dev/null +++ b/__snapshots__/scoreboard.test.ts.js @@ -0,0 +1,535 @@ +exports["criterion parser should parse correctly runParsertest 1"] = { + name: "runParsertest", + behavior: [ + { + given: "air", + expect: { + cursor: 3, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "air" + }, + { + kind: 20, + start: 0, + text: "armor" + }, + { + kind: 20, + start: 0, + text: "deathCount" + }, + { + kind: 20, + start: 0, + text: "dummy" + }, + { + kind: 20, + start: 0, + text: "food" + }, + { + kind: 20, + start: 0, + text: "health" + }, + { + kind: 20, + start: 0, + text: "level" + }, + { + kind: 20, + start: 0, + text: "playerKillCount" + }, + { + kind: 20, + start: 0, + text: "totalKillCount" + }, + { + kind: 20, + start: 0, + text: "trigger" + }, + { + kind: 20, + start: 0, + text: "xp" + }, + { + kind: 20, + start: 0, + text: "teamkill." + }, + { + kind: 20, + start: 0, + text: "killedByTeam." + } + ], + misc: [], + kind: true + } + } + }, + { + given: "teamkill.aqua", + expect: { + cursor: 13, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "air" + }, + { + kind: 20, + start: 0, + text: "armor" + }, + { + kind: 20, + start: 0, + text: "deathCount" + }, + { + kind: 20, + start: 0, + text: "dummy" + }, + { + kind: 20, + start: 0, + text: "food" + }, + { + kind: 20, + start: 0, + text: "health" + }, + { + kind: 20, + start: 0, + text: "level" + }, + { + kind: 20, + start: 0, + text: "playerKillCount" + }, + { + kind: 20, + start: 0, + text: "totalKillCount" + }, + { + kind: 20, + start: 0, + text: "trigger" + }, + { + kind: 20, + start: 0, + text: "xp" + }, + { + kind: 20, + start: 0, + text: "teamkill." + }, + { + kind: 20, + start: 0, + text: "killedByTeam." + }, + { + kind: 16, + start: 9, + text: "black" + }, + { + kind: 16, + start: 9, + text: "dark_blue" + }, + { + kind: 16, + start: 9, + text: "dark_green" + }, + { + kind: 16, + start: 9, + text: "dark_aqua" + }, + { + kind: 16, + start: 9, + text: "dark_red" + }, + { + kind: 16, + start: 9, + text: "dark_purple" + }, + { + kind: 16, + start: 9, + text: "gold" + }, + { + kind: 16, + start: 9, + text: "gray" + }, + { + kind: 16, + start: 9, + text: "dark_gray" + }, + { + kind: 16, + start: 9, + text: "blue" + }, + { + kind: 16, + start: 9, + text: "green" + }, + { + kind: 16, + start: 9, + text: "aqua" + }, + { + kind: 16, + start: 9, + text: "red" + }, + { + kind: 16, + start: 9, + text: "light_purple" + }, + { + kind: 16, + start: 9, + text: "yellow" + }, + { + kind: 16, + start: 9, + text: "white" + }, + { + kind: 16, + start: 9, + text: "reset" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "minecraft.custom:minecraft.some_custom", + expect: { + cursor: 38, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "air" + }, + { + kind: 20, + start: 0, + text: "armor" + }, + { + kind: 20, + start: 0, + text: "deathCount" + }, + { + kind: 20, + start: 0, + text: "dummy" + }, + { + kind: 20, + start: 0, + text: "food" + }, + { + kind: 20, + start: 0, + text: "health" + }, + { + kind: 20, + start: 0, + text: "level" + }, + { + kind: 20, + start: 0, + text: "playerKillCount" + }, + { + kind: 20, + start: 0, + text: "totalKillCount" + }, + { + kind: 20, + start: 0, + text: "trigger" + }, + { + kind: 20, + start: 0, + text: "xp" + }, + { + kind: 20, + start: 0, + text: "teamkill." + }, + { + kind: 20, + start: 0, + text: "killedByTeam." + }, + { + kind: 18, + start: 17, + text: "minecraft.some_custom" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "minecraft.custom:some_custom", + expect: { + cursor: 28, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "air" + }, + { + kind: 20, + start: 0, + text: "armor" + }, + { + kind: 20, + start: 0, + text: "deathCount" + }, + { + kind: 20, + start: 0, + text: "dummy" + }, + { + kind: 20, + start: 0, + text: "food" + }, + { + kind: 20, + start: 0, + text: "health" + }, + { + kind: 20, + start: 0, + text: "level" + }, + { + kind: 20, + start: 0, + text: "playerKillCount" + }, + { + kind: 20, + start: 0, + text: "totalKillCount" + }, + { + kind: 20, + start: 0, + text: "trigger" + }, + { + kind: 20, + start: 0, + text: "xp" + }, + { + kind: 20, + start: 0, + text: "teamkill." + }, + { + kind: 20, + start: 0, + text: "killedByTeam." + }, + { + kind: 18, + start: 17, + text: "minecraft.some_custom" + } + ], + misc: [], + kind: true + } + } + }, + { + given: "custom:some_custom", + expect: { + cursor: 18, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [ + { + kind: 20, + start: 0, + text: "air" + }, + { + kind: 20, + start: 0, + text: "armor" + }, + { + kind: 20, + start: 0, + text: "deathCount" + }, + { + kind: 20, + start: 0, + text: "dummy" + }, + { + kind: 20, + start: 0, + text: "food" + }, + { + kind: 20, + start: 0, + text: "health" + }, + { + kind: 20, + start: 0, + text: "level" + }, + { + kind: 20, + start: 0, + text: "playerKillCount" + }, + { + kind: 20, + start: 0, + text: "totalKillCount" + }, + { + kind: 20, + start: 0, + text: "trigger" + }, + { + kind: 20, + start: 0, + text: "xp" + }, + { + kind: 20, + start: 0, + text: "teamkill." + }, + { + kind: 20, + start: 0, + text: "killedByTeam." + }, + { + kind: 18, + start: 7, + text: "minecraft.some_custom" + } + ], + misc: [], + kind: true + } + } + } + ] +}; diff --git a/__snapshots__/string-reader.test.ts.js b/__snapshots__/string-reader.test.ts.js new file mode 100644 index 0000000..17c437e --- /dev/null +++ b/__snapshots__/string-reader.test.ts.js @@ -0,0 +1,959 @@ +exports[ + "string-reader expect() should work correctly for various inputs expectRead 1" +] = { + name: "expectRead", + behavior: [ + { + given: ["test", "t"], + expect: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "t" + } + ], + misc: [], + kind: true + } + }, + { + given: ["test", "n"], + expect: { + actions: [], + errors: [ + { + code: "parsing.expected", + severity: 1, + substitutions: ["n"], + text: "Expected 'n'", + range: { + start: 0, + end: 1 + } + } + ], + suggestions: [ + { + start: 0, + text: "n" + } + ], + misc: [], + kind: false + } + }, + { + given: ["test", "tes"], + expect: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "tes" + } + ], + misc: [], + kind: true + } + }, + { + given: ["test", "not"], + expect: { + actions: [], + errors: [ + { + code: "parsing.expected", + severity: 1, + substitutions: ["not"], + text: "Expected 'not'", + range: { + start: 0, + end: 3 + } + } + ], + suggestions: [ + { + start: 0, + text: "not" + } + ], + misc: [], + kind: false + } + }, + { + given: ["te", "test"], + expect: { + actions: [], + errors: [ + { + code: "parsing.expected", + severity: 1, + substitutions: ["test"], + text: "Expected 'test'", + range: { + start: 0, + end: 2 + } + } + ], + suggestions: [ + { + start: 0, + text: "test" + } + ], + misc: [], + kind: false + } + } + ] +}; + +exports[ + "string-reader readBoolean() should work correctly for various inputs boolRead 1" +] = { + name: "boolRead", + behavior: [ + { + given: "true", + expect: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "true" + }, + { + start: 0, + text: "false" + } + ], + misc: [], + data: true, + kind: true + } + }, + { + given: "false", + expect: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "true" + }, + { + start: 0, + text: "false" + } + ], + misc: [], + data: false, + kind: true + } + }, + { + given: "nonBoolean", + expect: { + actions: [], + errors: [ + { + code: "parsing.bool.invalid", + severity: 1, + substitutions: ["nonBoolean"], + text: + "Invalid bool, expected true or false but found 'nonBoolean'", + range: { + start: 0, + end: 10 + } + } + ], + suggestions: [ + { + start: 0, + text: "true" + }, + { + start: 0, + text: "false" + } + ], + misc: [], + kind: false + } + } + ] +}; + +exports[ + "string-reader readFloat() should have the correct behaviour for various inputs floatReader 1" +] = { + name: "floatReader", + behavior: [ + { + given: "-1000", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: -1000, + kind: true + } + }, + { + given: "1000.", + expect: { + actions: [], + errors: [ + { + code: "parsing.int.invalid", + severity: 1, + substitutions: ["1000."], + text: "Invalid integer '1000.'", + range: { + start: 0, + end: 5 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: "1000test", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 1000, + kind: true + } + }, + { + given: "noint", + expect: { + actions: [], + errors: [ + { + code: "parsing.int.expected", + severity: 1, + substitutions: [], + text: "Expected integer", + range: { + start: 0, + end: 5 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: "1.", + expect: { + actions: [], + errors: [ + { + code: "parsing.int.invalid", + severity: 1, + substitutions: ["1."], + text: "Invalid integer '1.'", + range: { + start: 0, + end: 2 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: "1000.123", + expect: { + actions: [], + errors: [ + { + code: "parsing.int.invalid", + severity: 1, + substitutions: ["1000.123"], + text: "Invalid integer '1000.123'", + range: { + start: 0, + end: 8 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: "-1000.123", + expect: { + actions: [], + errors: [ + { + code: "parsing.int.invalid", + severity: 1, + substitutions: ["-1000.123"], + text: "Invalid integer '-1000.123'", + range: { + start: 0, + end: 9 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: "1000.123test", + expect: { + actions: [], + errors: [ + { + code: "parsing.int.invalid", + severity: 1, + substitutions: ["1000.123"], + text: "Invalid integer '1000.123'", + range: { + start: 0, + end: 8 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: "nofloat", + expect: { + actions: [], + errors: [ + { + code: "parsing.int.expected", + severity: 1, + substitutions: [], + text: "Expected integer", + range: { + start: 0, + end: 7 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: "1.1.1.1.1", + expect: { + actions: [], + errors: [ + { + code: "parsing.int.invalid", + severity: 1, + substitutions: ["1.1.1.1.1"], + text: "Invalid integer '1.1.1.1.1'", + range: { + start: 0, + end: 9 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: "0", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 0, + kind: true + } + }, + { + given: "01", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 1, + kind: true + } + }, + { + given: "012", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 12, + kind: true + } + }, + { + given: "0123", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 123, + kind: true + } + }, + { + given: "01234", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 1234, + kind: true + } + } + ] +}; + +exports[ + "string-reader readInt() should have the correct behaviour for various inputs intReader 1" +] = { + name: "intReader", + behavior: [ + { + given: "1000.", + expect: { + actions: [], + errors: [ + { + code: "parsing.int.invalid", + severity: 1, + substitutions: ["1000."], + text: "Invalid integer '1000.'", + range: { + start: 0, + end: 5 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: "-1000", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: -1000, + kind: true + } + }, + { + given: "1000test", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 1000, + kind: true + } + }, + { + given: "noint", + expect: { + actions: [], + errors: [ + { + code: "parsing.int.expected", + severity: 1, + substitutions: [], + text: "Expected integer", + range: { + start: 0, + end: 5 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: "1.", + expect: { + actions: [], + errors: [ + { + code: "parsing.int.invalid", + severity: 1, + substitutions: ["1."], + text: "Invalid integer '1.'", + range: { + start: 0, + end: 2 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: "0", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 0, + kind: true + } + }, + { + given: "01", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 1, + kind: true + } + }, + { + given: "012", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 12, + kind: true + } + }, + { + given: "0123", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 123, + kind: true + } + }, + { + given: "01234", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 1234, + kind: true + } + } + ] +}; + +exports[ + "string-reader readOption should work correctly for various inputs optionRead 1" +] = { + name: "optionRead", + behavior: [ + { + given: ["test", ["test", "other"]], + expect: { + actions: [], + errors: [], + suggestions: [ + { + start: 0, + text: "test" + }, + { + start: 0, + text: "other" + } + ], + misc: [], + data: "test", + kind: true + } + }, + { + given: ["test", ["nottest", "other"]], + expect: { + actions: [], + errors: [ + { + code: "parsing.expected.option", + severity: 1, + substitutions: ["nottest,other", "test"], + text: + "Expected string from [nottest,other], got 'test'", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [ + { + start: 0, + text: "nottest" + }, + { + start: 0, + text: "other" + } + ], + misc: [], + kind: false + } + } + ] +}; + +exports[ + "string-reader readQuotedString() should have the correct behaviour on various inputs quotedStringReader 1" +] = { + name: "quotedStringReader", + behavior: [ + { + given: "test", + expect: { + actions: [], + errors: [ + { + code: "parsing.quote.expected.start", + severity: 1, + substitutions: [], + text: "Expected quote to start a string", + range: { + start: 0, + end: 4 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: '"hello"', + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: "hello", + kind: true + } + }, + { + given: "'hello\"", + expect: { + actions: [], + errors: [ + { + code: "parsing.quote.expected.end", + severity: 1, + substitutions: [], + text: "Unclosed quoted string", + range: { + start: 0, + end: 7 + } + } + ], + suggestions: [ + { + start: 7, + text: "'" + } + ], + misc: [], + data: 'hello"', + kind: false + } + }, + { + given: '""', + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: "", + kind: true + } + }, + { + given: "'\"", + expect: { + actions: [], + errors: [ + { + code: "parsing.quote.expected.end", + severity: 1, + substitutions: [], + text: "Unclosed quoted string", + range: { + start: 0, + end: 2 + } + } + ], + suggestions: [ + { + start: 2, + text: "'" + } + ], + misc: [], + data: '"', + kind: false + } + }, + { + given: '"quote\\"here"', + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 'quote"here', + kind: true + } + }, + { + given: '\'quote\\"here"', + expect: { + actions: [], + errors: [ + { + code: "parsing.quote.escape", + severity: 1, + substitutions: ['"'], + text: + "Invalid escape sequence '\\\"' in quoted string)", + range: { + start: 6, + end: 8 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: '"backslash\\\\here"', + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: "backslash\\here", + kind: true + } + }, + { + given: "'backslash\\\\here\"", + expect: { + actions: [], + errors: [ + { + code: "parsing.quote.expected.end", + severity: 1, + substitutions: [], + text: "Unclosed quoted string", + range: { + start: 0, + end: 17 + } + } + ], + suggestions: [ + { + start: 17, + text: "'" + } + ], + misc: [], + data: 'backslash\\here"', + kind: false + } + }, + { + given: '"oop\\s"', + expect: { + actions: [], + errors: [ + { + code: "parsing.quote.escape", + severity: 1, + substitutions: ["s"], + text: "Invalid escape sequence '\\s' in quoted string)", + range: { + start: 4, + end: 6 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: "'oop\\s\"", + expect: { + actions: [], + errors: [ + { + code: "parsing.quote.escape", + severity: 1, + substitutions: ["s"], + text: "Invalid escape sequence '\\s' in quoted string)", + range: { + start: 4, + end: 6 + } + } + ], + suggestions: [], + misc: [], + kind: false + } + }, + { + given: '"trailing', + expect: { + actions: [], + errors: [ + { + code: "parsing.quote.expected.end", + severity: 1, + substitutions: [], + text: "Unclosed quoted string", + range: { + start: 0, + end: 9 + } + } + ], + suggestions: [ + { + start: 9, + text: '"' + } + ], + misc: [], + data: "trailing", + kind: false + } + }, + { + given: "'trailing", + expect: { + actions: [], + errors: [ + { + code: "parsing.quote.expected.end", + severity: 1, + substitutions: [], + text: "Unclosed quoted string", + range: { + start: 0, + end: 9 + } + } + ], + suggestions: [ + { + start: 9, + text: "'" + } + ], + misc: [], + data: "trailing", + kind: false + } + }, + { + given: "'quote\" in the middle'", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 'quote" in the middle', + kind: true + } + }, + { + given: '"quote\' in the middle"', + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: "quote' in the middle", + kind: true + } + } + ] +}; + +exports[ + "string-reader readQuotedString() should return an empty string if it reading from the end 1" +] = { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: "", + kind: true +}; diff --git a/__snapshots__/string.test.ts.js b/__snapshots__/string.test.ts.js new file mode 100644 index 0000000..f334db2 --- /dev/null +++ b/__snapshots__/string.test.ts.js @@ -0,0 +1,123 @@ +exports[ + "String Argument Parser should work for a phrase string runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: 'test space :"-)(*', + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: '"quote test" :"-)(*', + expect: { + cursor: 12, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "String Argument Parser should work for a word string runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: 'test space :"-)(*', + expect: { + cursor: 4, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + }, + { + given: '"quote test" :"-)(*', + expect: { + cursor: 0, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; + +exports[ + "String Argument Parser should work with a greedy string runParsertest 1" +] = { + name: "runParsertest", + behavior: [ + { + given: 'test space :"-)(*', + expect: { + cursor: 17, + parsing: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + }, + suggesting: { + actions: [], + errors: [], + suggestions: [], + misc: [], + kind: true + } + } + } + ] +}; diff --git a/__snapshots__/tag-parser.test.ts.js b/__snapshots__/tag-parser.test.ts.js new file mode 100644 index 0000000..1bad9d1 --- /dev/null +++ b/__snapshots__/tag-parser.test.ts.js @@ -0,0 +1,104 @@ +exports["SNBT Tag Parsers Byte Tag should parse correctly testTagInner 1"] = { + name: "testTagInner", + behavior: [ + { + given: "123b", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 2, + kind: true + } + }, + { + given: "321s", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 2, + kind: true + } + }, + { + given: "hello", + expect: { + actions: [], + errors: [ + { + code: "parsing.float.expected", + severity: 1, + substitutions: [], + text: "Expected float", + range: { + start: 0, + end: 5 + } + } + ], + suggestions: [], + misc: [], + data: 0, + kind: false + } + }, + { + given: "true", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 2, + kind: true + } + }, + { + given: "false", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 2, + kind: true + } + }, + { + given: "130b", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 2, + kind: true + } + }, + { + given: "-10b", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 2, + kind: true + } + }, + { + given: "-130b", + expect: { + actions: [], + errors: [], + suggestions: [], + misc: [], + data: 2, + kind: true + } + } + ] +}; diff --git a/dist/index.js b/dist/index.js index ed4e249..b1d4c90 100644 --- a/dist/index.js +++ b/dist/index.js @@ -212,7 +212,7 @@ exports.TAG_START = "#"; // Misc exports.JAVAMAXINT = 2147483647; exports.JAVAMININT = -2147483648; exports.NONWHITESPACE = /\S/; -},{}],"CWtC":[function(require,module,exports) { +},{}],"SoFr":[function(require,module,exports) { "use strict"; Object.defineProperty(exports, "__esModule", { @@ -221,17 +221,17 @@ Object.defineProperty(exports, "__esModule", { const consts_1 = require("../consts"); -function namespacesEqual(first, second) { - return namesEqual(first, second) && first.path === second.path; +function idsEqual(first, second) { + return namespacesEqual(first, second) && first.path === second.path; } -exports.namespacesEqual = namespacesEqual; +exports.idsEqual = idsEqual; -function namesEqual(first, second) { +function namespacesEqual(first, second) { return first.namespace === second.namespace || isNamespaceDefault(first) && isNamespaceDefault(second); } -exports.namesEqual = namesEqual; +exports.namespacesEqual = namespacesEqual; function isNamespaceDefault(name) { return name.namespace === undefined || name.namespace === consts_1.DEFAULT_NAMESPACE; @@ -239,23 +239,22 @@ function isNamespaceDefault(name) { exports.isNamespaceDefault = isNamespaceDefault; -function stringifyNamespace(namespace, seperator = consts_1.NAMESPACE) { +function stringifyID(namespace, seperator = consts_1.NAMESPACE) { return (namespace.namespace ? namespace.namespace : consts_1.DEFAULT_NAMESPACE) + seperator + namespace.path; } -exports.stringifyNamespace = stringifyNamespace; +exports.stringifyID = stringifyID; /** - * Convert a string into a `NamespacedName`. This should only be called directly on strings which are known to be valid + * Convert a string into an `ID`. This should only be called directly on strings which are known to be valid * The behaviour on invalid strings is to leave the second seperator in the path */ -function convertToNamespace(input, splitChar = consts_1.NAMESPACE) { +function convertToID(input, splitChar = consts_1.NAMESPACE) { const index = input.indexOf(splitChar); if (index >= 0) { const pathContents = input.substring(index + splitChar.length, input.length); // Path contents should not have a : in the contents, however this is to be checked higher up. // This simplifies using the parsed result when parsing known statics - // Related: https://bugs.mojang.com/browse/MC-91245 (Fixed) if (index >= 1) { return { @@ -274,7 +273,14 @@ function convertToNamespace(input, splitChar = consts_1.NAMESPACE) { } } -exports.convertToNamespace = convertToNamespace; +exports.convertToID = convertToID; + +function stringArrayToIDs(strings) { + // tslint:disable-next-line:no-unnecessary-callback-wrapper this is a false positive - see https://github.com/palantir/tslint/issues/2430 + return strings.map(v => convertToID(v)); +} + +exports.stringArrayToIDs = stringArrayToIDs; },{"../consts":"xb+0"}],"23cw":[function(require,module,exports) { "use strict"; @@ -282,7 +288,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); -const namespace_1 = require("./namespace"); +const id_1 = require("./id"); function getResourcesofType(resources, type) { return getResourcesSplit(type, resources.globalData, resources.localData); @@ -323,7 +329,7 @@ function getMatching(resources, value) { const results = []; for (const resource of resources) { - if (namespace_1.namespacesEqual(resource, value)) { + if (id_1.idsEqual(resource, value)) { results.push(resource); } } @@ -332,7 +338,45 @@ function getMatching(resources, value) { } exports.getMatching = getMatching; -},{"./namespace":"CWtC"}],"aP4V":[function(require,module,exports) { +},{"./id":"SoFr"}],"cNAA":[function(require,module,exports) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +function createExtensionFileError(filePath, expected, actual) { + return { + filePath, + group: "extension", + kind: "FileError", + message: `File has incorrect extension: Expected ${expected}, got ${actual}.` + }; +} + +exports.createExtensionFileError = createExtensionFileError; + +function createJSONFileError(filePath, error) { + return { + filePath, + group: "json", + kind: "FileError", + message: `JSON parsing failed: '${error}'` + }; +} + +exports.createJSONFileError = createJSONFileError; + +function createFileClear(filePath, group) { + return { + kind: "ClearError", + filePath, + group + }; +} + +exports.createFileClear = createFileClear; +},{}],"aP4V":[function(require,module,exports) { "use strict"; Object.defineProperty(exports, "__esModule", { @@ -394,200 +438,7 @@ function fillBlankError(err, start, end) { } exports.fillBlankError = fillBlankError; -},{"../misc-functions":"KBGm"}],"AgEN":[function(require,module,exports) { -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -const __1 = require(".."); - -const errors_1 = require("../../brigadier/errors"); - -const consts_1 = require("../../consts"); - -const namespace_1 = require("../namespace"); - -const NAMESPACEEXCEPTIONS = { - invalid_id: new errors_1.CommandErrorBuilder("argument.id.invalid", "The seperator '%s' should not be repeated in the ID '%s'") -}; -exports.namespaceChars = /^[0-9a-z_:/\.-]$/; -const allowedInSections = /^[0-9a-z_/\.-]$/; - -function stringArrayToNamespaces(strings) { - // tslint:disable-next-line:no-unnecessary-callback-wrapper this is a false positive - see https://github.com/palantir/tslint/issues/2430 - return strings.map(v => __1.convertToNamespace(v)); -} - -exports.stringArrayToNamespaces = stringArrayToNamespaces; - -function readNamespaceText(reader, seperator = consts_1.NAMESPACE, stopAfterFirst = false) { - let found = false; - return reader.readWhileFunction(c => { - if (c === seperator) { - if (found && stopAfterFirst) { - return false; - } - - found = true; - return true; - } - - return allowedInSections.test(c); - }); -} - -exports.readNamespaceText = readNamespaceText; -/** - * Does `base`(eg minecraft:stone) start with `test` (e.g. sto) [Y] - */ - -function namespaceStart(base, test) { - if (test.namespace === undefined) { - return namespace_1.isNamespaceDefault(base) && base.path.startsWith(test.path) || !!base.namespace && base.namespace.startsWith(test.path); - } else { - return namespace_1.namesEqual(base, test) && base.path.startsWith(test.path); - } -} - -exports.namespaceStart = namespaceStart; - -function namespaceSuggestions(options, value, start) { - const result = []; - - for (const option of options) { - if (namespaceStart(option, value)) { - result.push({ - text: __1.stringifyNamespace(option), - start - }); - } - } - - return result; -} - -exports.namespaceSuggestions = namespaceSuggestions; - -function namespaceSuggestionString(options, value, start) { - return namespaceSuggestions(stringArrayToNamespaces(options), value, start); -} - -exports.namespaceSuggestionString = namespaceSuggestionString; - -function parseNamespace(reader, seperator = consts_1.NAMESPACE, stopAfterFirst = false) { - const helper = new __1.ReturnHelper(); - const start = reader.cursor; - const text = readNamespaceText(reader, seperator, stopAfterFirst); - - const namespace = __1.convertToNamespace(text, seperator); - - let next = 0; - let failed = false; // Give an error for each invalid character - - while (true) { - next = namespace.path.indexOf(seperator, next + 1); - - if (next !== -1) { - const i = text.indexOf(seperator) + 1 + next + start; - failed = true; - helper.addErrors(NAMESPACEEXCEPTIONS.invalid_id.create(i, i + seperator.length, seperator, text)); - } else { - break; - } - } - - if (failed) { - return helper.fail(); - } else { - return helper.succeed(namespace); - } -} - -exports.parseNamespace = parseNamespace; - -function parseNamespaceOption(reader, options, completionKind, seperator = consts_1.NAMESPACE, stopAfterFirst = false) { - const helper = new __1.ReturnHelper(); - const start = reader.cursor; - const namespace = parseNamespace(reader, seperator, stopAfterFirst); - - if (helper.merge(namespace)) { - const results = processParsedNamespaceOption(namespace.data, options, !reader.canRead(), start, completionKind, seperator); - helper.merge(results); - - if (results.data.length > 0) { - return helper.succeed({ - literal: namespace.data, - values: results.data - }); - } else { - return helper.failWithData(namespace.data); - } - } else { - return helper.failWithData(undefined); - } -} - -exports.parseNamespaceOption = parseNamespaceOption; - -function processParsedNamespaceOption(namespace, options, suggest, start, completionKind, seperator = consts_1.NAMESPACE) { - const results = []; - const helper = new __1.ReturnHelper(); - - for (const val of options) { - if (__1.namespacesEqual(val, namespace)) { - results.push(val); - } - - if (suggest && namespaceStart(val, namespace)) { - helper.addSuggestion(start, __1.stringifyNamespace(val, seperator), completionKind); - } - } - - return helper.succeed(results); -} - -exports.processParsedNamespaceOption = processParsedNamespaceOption; -},{"..":"KBGm","../../brigadier/errors":"aP4V","../../consts":"xb+0","../namespace":"CWtC"}],"cNAA":[function(require,module,exports) { -"use strict"; - -Object.defineProperty(exports, "__esModule", { - value: true -}); - -function createExtensionFileError(filePath, expected, actual) { - return { - filePath, - group: "extension", - kind: "FileError", - message: `File has incorrect extension: Expected ${expected}, got ${actual}.` - }; -} - -exports.createExtensionFileError = createExtensionFileError; - -function createJSONFileError(filePath, error) { - return { - filePath, - group: "json", - kind: "FileError", - message: `JSON parsing failed: '${error}'` - }; -} - -exports.createJSONFileError = createJSONFileError; - -function createFileClear(filePath, group) { - return { - kind: "ClearError", - filePath, - group - }; -} - -exports.createFileClear = createFileClear; -},{}],"UL96":[function(require,module,exports) { +},{"../misc-functions":"KBGm"}],"UL96":[function(require,module,exports) { "use strict"; Object.defineProperty(exports, "__esModule", { @@ -702,15 +553,12 @@ class ReturnHelper { } addSuggestion(start, text, kind, description) { - if (this.suggestMode === undefined || this.suggestMode) { - this.addSuggestions({ - description, - kind, - start, - text - }); - } - + this.addSuggestions({ + description, + kind, + start, + text + }); return this; } @@ -938,9 +786,7 @@ const consts_1 = require("../consts"); const group_resources_1 = require("./group-resources"); -const namespace_1 = require("./namespace"); - -const namespace_2 = require("./parsing/namespace"); +const id_1 = require("./id"); const promisified_fs_1 = require("./promisified-fs"); @@ -1037,19 +883,19 @@ exports.resourceTypes = { entity_tags: { extension: ".json", mapFunction: async (v, packroot, globalData, packsInfo) => readTag(v, packroot, "entity_tags", group_resources_1.getResourcesSplit("entity_tags", globalData, packsInfo), s => group_resources_1.getMatching( // TODO: This is horrifically inefficient - namespace_2.stringArrayToNamespaces([...globalData.registries["minecraft:entity_type"]]), namespace_1.convertToNamespace(s)).length > 0), + id_1.stringArrayToIDs([...globalData.registries["minecraft:entity_type"]]), id_1.convertToID(s)).length > 0), path: ["tags", "entity_types"] }, fluid_tags: { extension: ".json", - mapFunction: async (v, packroot, globalData, packsInfo) => readTag(v, packroot, "fluid_tags", group_resources_1.getResourcesSplit("fluid_tags", globalData, packsInfo), s => group_resources_1.getMatching(namespace_2.stringArrayToNamespaces([...globalData.registries["minecraft:entity_type"]]), namespace_1.convertToNamespace(s)).length > 0), + mapFunction: async (v, packroot, globalData, packsInfo) => readTag(v, packroot, "fluid_tags", group_resources_1.getResourcesSplit("fluid_tags", globalData, packsInfo), s => group_resources_1.getMatching(id_1.stringArrayToIDs([...globalData.registries["minecraft:entity_type"]]), id_1.convertToID(s)).length > 0), path: ["tags", "fluids"] }, function_tags: { extension: ".json", mapFunction: async (v, packroot, globalData, packsInfo) => { const functions = group_resources_1.getResourcesSplit("functions", globalData, packsInfo); - return readTag(v, packroot, "function_tags", group_resources_1.getResourcesSplit("function_tags", globalData, packsInfo), s => group_resources_1.getMatching(functions, namespace_1.convertToNamespace(s)).length > 0); + return readTag(v, packroot, "function_tags", group_resources_1.getResourcesSplit("function_tags", globalData, packsInfo), s => group_resources_1.getMatching(functions, id_1.convertToID(s)).length > 0); }, path: ["tags", "functions"] }, @@ -1147,8 +993,8 @@ async function readTag(resource, packRoot, type, options, isKnown) { for (const v of tag.data.values) { if (v.startsWith(consts_1.TAG_START)) { const tagText = v.slice(1); - const tagNamespace = namespace_1.convertToNamespace(tagText); - const tagName = namespace_1.stringifyNamespace(tagNamespace); + const tagNamespace = id_1.convertToID(tagText); + const tagName = id_1.stringifyID(tagNamespace); const tagString = `#${tagName}`; const result = group_resources_1.getMatching(options, tagNamespace); @@ -1162,8 +1008,8 @@ async function readTag(resource, packRoot, type, options, isKnown) { seen.add(tagString); } else { - const valueNamespace = namespace_1.convertToNamespace(v); - const value = namespace_1.stringifyNamespace(valueNamespace); + const valueNamespace = id_1.convertToID(v); + const value = id_1.stringifyID(valueNamespace); if (seen.has(value)) { duplicates.add(value); @@ -1189,7 +1035,7 @@ async function readTag(resource, packRoot, type, options, isKnown) { return helper.succeed(resource); } -},{"../consts":"xb+0","./group-resources":"23cw","./namespace":"CWtC","./parsing/namespace":"AgEN","./promisified-fs":"tSk9","./return-helper":"p3gl","./third_party/typed-keys":"kca+"}],"4xli":[function(require,module,exports) { +},{"../consts":"xb+0","./group-resources":"23cw","./id":"SoFr","./promisified-fs":"tSk9","./return-helper":"p3gl","./third_party/typed-keys":"kca+"}],"4xli":[function(require,module,exports) { "use strict"; Object.defineProperty(exports, "__esModule", { @@ -1503,7 +1349,140 @@ function setup_logging(connection) { } exports.setup_logging = setup_logging; -},{}],"lcVC":[function(require,module,exports) { +},{}],"AgEN":[function(require,module,exports) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); + +const __1 = require(".."); + +const errors_1 = require("../../brigadier/errors"); + +const consts_1 = require("../../consts"); + +const id_1 = require("../id"); + +const NAMESPACEEXCEPTIONS = { + invalid_id: new errors_1.CommandErrorBuilder("argument.id.invalid", "The seperator '%s' should not be repeated in the ID '%s'") +}; +exports.namespaceChars = /^[0-9a-z_:/\.-]$/; +const allowedInSections = /^[0-9a-z_/\.-]$/; + +function readNamespaceText(reader, seperator = consts_1.NAMESPACE, stopAfterFirst = false) { + let found = false; + return reader.readWhileFunction(c => { + if (c === seperator) { + if (found && stopAfterFirst) { + return false; + } + + found = true; + return true; + } + + return allowedInSections.test(c); + }); +} + +exports.readNamespaceText = readNamespaceText; + +function namespaceSuggestions(options, start) { + const result = []; + + for (const option of options) { + result.push({ + text: __1.stringifyID(option), + start + }); + } + + return result; +} + +exports.namespaceSuggestions = namespaceSuggestions; + +function namespaceSuggestionString(options, start) { + return namespaceSuggestions(id_1.stringArrayToIDs(options), start); +} + +exports.namespaceSuggestionString = namespaceSuggestionString; + +function parseNamespace(reader, seperator = consts_1.NAMESPACE, stopAfterFirst = false) { + const helper = new __1.ReturnHelper(); + const start = reader.cursor; + const text = readNamespaceText(reader, seperator, stopAfterFirst); + + const namespace = __1.convertToID(text, seperator); + + let next = 0; + let failed = false; // Give an error for each invalid character + + while (true) { + next = namespace.path.indexOf(seperator, next + 1); + + if (next !== -1) { + const i = text.indexOf(seperator) + 1 + next + start; + failed = true; + helper.addErrors(NAMESPACEEXCEPTIONS.invalid_id.create(i, i + seperator.length, seperator, text)); + } else { + break; + } + } + + if (failed) { + return helper.fail(); + } else { + return helper.succeed(namespace); + } +} + +exports.parseNamespace = parseNamespace; + +function parseNamespaceOption(reader, options, completionKind, seperator = consts_1.NAMESPACE, stopAfterFirst = false) { + const helper = new __1.ReturnHelper(); + const start = reader.cursor; + const namespace = parseNamespace(reader, seperator, stopAfterFirst); + + if (helper.merge(namespace)) { + const results = processParsedNamespaceOption(namespace.data, options, !reader.canRead(), start, completionKind, seperator); + helper.merge(results); + + if (results.data.length > 0) { + return helper.succeed({ + literal: namespace.data, + values: results.data + }); + } else { + return helper.failWithData(namespace.data); + } + } else { + return helper.failWithData(undefined); + } +} + +exports.parseNamespaceOption = parseNamespaceOption; + +function processParsedNamespaceOption(namespace, options, suggest, start, completionKind, seperator = consts_1.NAMESPACE) { + const results = []; + const helper = new __1.ReturnHelper(); + + for (const val of options) { + if (__1.idsEqual(val, namespace)) { + results.push(val); + } + + if (suggest) { + helper.addSuggestion(start, __1.stringifyID(val, seperator), completionKind); + } + } + + return helper.succeed(results); +} + +exports.processParsedNamespaceOption = processParsedNamespaceOption; +},{"..":"KBGm","../../brigadier/errors":"aP4V","../../consts":"xb+0","../id":"SoFr"}],"lcVC":[function(require,module,exports) { "use strict"; Object.defineProperty(exports, "__esModule", { @@ -1588,15 +1567,15 @@ function getLowestForTag(tag, options) { for (const tagMember of tag.data.values) { if (tagMember[0] === consts_1.TAG_START) { - const namespace = __1.convertToNamespace(tagMember.substring(1)); + const namespace = __1.convertToID(tagMember.substring(1)); for (const option of options) { - if (__1.namespacesEqual(namespace, option)) { + if (__1.idsEqual(namespace, option)) { results.push(...getLowestForTag(option, options)); } } } else { - results.push(__1.convertToNamespace(tagMember)); + results.push(__1.convertToID(tagMember)); } } @@ -1657,7 +1636,7 @@ tslib_1.__exportStar(require("./group-resources"), exports); tslib_1.__exportStar(require("./lsp-conversions"), exports); -tslib_1.__exportStar(require("./namespace"), exports); +tslib_1.__exportStar(require("./id"), exports); tslib_1.__exportStar(require("./node-tree"), exports); @@ -1674,7 +1653,7 @@ tslib_1.__exportStar(require("./translation"), exports); tslib_1.__exportStar(require("./parsing/namespace"), exports); tslib_1.__exportStar(require("./parsing/nmsp-tag"), exports); -},{"./context":"UbUk","./creators":"NmKP","./datapack-folder":"MG6C","./file-errors":"cNAA","./group-resources":"23cw","./lsp-conversions":"BiFC","./namespace":"CWtC","./node-tree":"H7Mf","./promisified-fs":"tSk9","./return-helper":"p3gl","./security":"tvqu","./setup":"8ePp","./translation":"4xli","./parsing/namespace":"AgEN","./parsing/nmsp-tag":"lcVC"}],"iLhI":[function(require,module,exports) { +},{"./context":"UbUk","./creators":"NmKP","./datapack-folder":"MG6C","./file-errors":"cNAA","./group-resources":"23cw","./lsp-conversions":"BiFC","./id":"SoFr","./node-tree":"H7Mf","./promisified-fs":"tSk9","./return-helper":"p3gl","./security":"tvqu","./setup":"8ePp","./translation":"4xli","./parsing/namespace":"AgEN","./parsing/nmsp-tag":"lcVC"}],"iLhI":[function(require,module,exports) { "use strict"; Object.defineProperty(exports, "__esModule", { @@ -1725,14 +1704,10 @@ class StringReader { expect(str) { const helper = new misc_functions_1.ReturnHelper(); - - if (str.startsWith(this.getRemaining())) { - helper.addSuggestions({ - start: this.cursor, - text: str - }); - } - + helper.addSuggestions({ + start: this.cursor, + text: str + }); const sub = this.string.substr(this.cursor, str.length); if (sub !== str) { @@ -1880,8 +1855,7 @@ class StringReader { suggestions: false })) { if (result.data && !this.canRead()) { - const bestEffort = result.data; - helper.addSuggestions(...options.filter(option => option.startsWith(bestEffort)).map(v => completionForString(v, start, quoteKind, completion))); + helper.addSuggestions(...options.map(v => completionForString(v, start, quoteKind, completion))); } return helper.fail(); @@ -1890,22 +1864,12 @@ class StringReader { const valid = options.some(opt => opt === result.data); if (!this.canRead()) { - helper.addSuggestions(...options.filter(opt => opt.startsWith(result.data)).map(v => completionForString(v, start, quoteKind, completion))); + helper.addSuggestions(...options.map(v => completionForString(v, start, quoteKind, completion))); } if (valid) { return helper.succeed(result.data); } else { - /* if (addError) { - helper.addErrors( - EXCEPTIONS.EXPECTED_STRING_FROM.create( - start, - this.cursor, - JSON.stringify(options), - result.data - ) - ); - } */ return helper.failWithData(result.data); } } @@ -2320,20 +2284,23 @@ exports.literalParser = { const begin = reader.cursor; const literal = properties.path[properties.path.length - 1]; - if (properties.suggesting && literal.startsWith(reader.getRemaining())) { - helper.addSuggestions(literal); - } - if (reader.canRead(literal.length)) { const end = begin + literal.length; if (reader.string.substring(begin, end) === literal) { reader.cursor = end; - if (reader.peek() === " " || !reader.canRead()) { + if (reader.peek() === " ") { + return helper.succeed(); + } + + if (!reader.canRead()) { + helper.addSuggestion(begin, literal); return helper.succeed(); } } + } else { + helper.addSuggestion(begin, literal); } return helper.fail(); @@ -2355,9 +2322,6 @@ exports.pack_segments = { packsFolder: "", rest: "" }; -exports.succeeds = { - succeeds: true -}; exports.emptyRange = () => ({ start: 0, @@ -3151,17 +3115,15 @@ class NBTTagCompound extends nbt_tag_1.NBTTag { if (part.closeIdx === undefined) { for (const childName of Object.keys(children)) { - if (childName.startsWith(key)) { - const thisChild = children[childName]; - const text = thisChild && nbt_util_1.getStartSuggestion(thisChild.node); - keyHelper.addSuggestions({ - description: nbt_util_1.getHoverText(children[childName].node), - kind: vscode_languageserver_1.CompletionItemKind.Field, - label: childName, - start: part.keyRange.start, - text: string_reader_1.quoteIfNeeded(childName) + (text ? `: ${text}` : "") - }); - } + const thisChild = children[childName]; + const text = thisChild && nbt_util_1.getStartSuggestion(thisChild.node); + keyHelper.addSuggestions({ + description: nbt_util_1.getHoverText(children[childName].node), + kind: vscode_languageserver_1.CompletionItemKind.Field, + label: childName, + start: part.keyRange.start, + text: string_reader_1.quoteIfNeeded(childName) + (text ? `: ${text}` : "") + }); } } @@ -3648,9 +3610,7 @@ class NBTTagTypedList extends lists_1.BaseList { const toCheck = `[${type["0"]};`; if (this.remaining) { - if (toCheck.startsWith(this.remaining)) { - helper.addSuggestion(this.startIndex, toCheck); - } + helper.addSuggestion(this.startIndex, toCheck); } } @@ -4027,370 +3987,7 @@ class NBTWalker { } NBTWalker.root = "root.json"; -exports.NBTWalker = NBTWalker; // Old version -// #interface ContextData< -// # N extends NBTNode = NBTNode, -// # T extends NBTTag = NBTTag -// #> { -// # readonly finalValidation: boolean; -// # readonly node: N; -// # readonly path: string; -// # readonly reader: ArrayReader; -// # readonly tag?: T; -// # readonly useReferences: boolean; -// #} -// # -// #// tslint:disable:cyclomatic-complexity -// #// tslint:disable-next-line:max-classes-per-file -// #export class NBTValidator { -// # private readonly docs: NBTDocs; -// # private readonly extraChildren: boolean; -// # private readonly parsed: NBTTag; -// # private readonly root: string; -// # private readonly validateNBT: boolean; -// # -// # public constructor( -// # parsed: NBTTag, -// # docs: NBTDocs, -// # extraChild: boolean, -// # nbtvalidation: boolean = true, -// # root: string = "root.json" -// # ) { -// # this.docs = docs; -// # this.parsed = parsed; -// # this.extraChildren = extraChild; -// # this.root = root; -// # this.validateNBT = nbtvalidation; -// # } -// # -// # public walkThenValidate(nbtpath: string[]): ReturnedInfo { -// # const node = this.docs.get(this.root) as RootNode; -// # const reader = new ArrayReader(nbtpath); -// # // tslint:disable-next-line:helper-return -// # return this.walkNextNode({ -// # finalValidation: true, -// # node, -// # path: this.root, -// # reader, -// # tag: this.validateNBT ? this.parsed : undefined, -// # useReferences: false -// # }); -// # } -// # -// # private mergeChildRef(data: ContextData): CompoundNode { -// # const { node, path: currentPath } = data; -// # if (!node.child_ref) { -// # return node; -// # } -// # const helper = new ReturnHelper(); -// # const newChildren = JSON.parse( -// # JSON.stringify(node.children || {}) -// # ) as Exclude; -// # for (const ref of node.child_ref) { -// # const [nextPath] = parseRefPath(ref, currentPath); -// # const refNode = this.walkRef(ref, currentPath, data); -// # if (!helper.merge(refNode)) { -// # continue; -// # } else if (isCompoundNode(refNode.data)) { -// # const evalNode = this.mergeChildRef({ -// # ...data, -// # node: refNode.data, -// # path: nextPath -// # }); -// # if (evalNode.children) { -// # for (const child of Object.keys(evalNode.children)) { -// # newChildren[child] = evalNode.children[child]; -// # } -// # } -// # } -// # } -// # return { -// # children: newChildren, -// # description: node.description, -// # suggestions: node.suggestions, -// # type: "compound" -// # }; -// # } -// # -// # private walkCompoundNode( -// # data: ContextData -// # ): ReturnedInfo { -// # const { node, reader, path, tag } = data; -// # const helper = new ReturnHelper(); -// # const next = reader.read(); -// # if (node.children && next in node.children) { -// # /* -// # * It is safe to assume that next is in the tag -// # * val because the path is based off of the tag -// # */ -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: node.children[next], -// # tag: tag ? tag.getVal()[next] : undefined -// # }) -// # ); -// # } else if (node.child_ref) { -// # for (const c of node.child_ref) { -// # const [nextPath] = parseRefPath(c, path); -// # const cnode = this.walkRef(c, path, data); -// # if ( -// # helper.merge(cnode) && -// # isCompoundNode(cnode.data) && -// # cnode.data.children && -// # next in cnode.data.children -// # ) { -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: cnode.data.children[next], -// # path: nextPath, -// # tag: tag ? tag.getVal()[next] : undefined -// # }) -// # ); -// # } -// # } -// # } -// # return helper.fail(); -// # } -// # -// # private walkFunctionNode( -// # data: ContextData -// # ): ReturnedInfo { -// # const { node, reader, path } = data; -// # const helper = new ReturnHelper(); -// # const ref = runNodeFunction(this.parsed, reader.getRead(), node); -// # const [nextPath] = parseRefPath(ref, path); -// # const newNode = this.walkRef(ref, path, data); -// # if (!helper.merge(newNode)) { -// # return helper.fail(); -// # } -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: newNode.data, -// # path: nextPath -// # }) -// # ); -// # } -// # -// # private walkListNode( -// # data: ContextData -// # ): ReturnedInfo { -// # const { node, reader, tag } = data; -// # const next = reader.read(); -// # const helper = new ReturnHelper(); -// # if (!/\d+/.test(next)) { -// # return helper.fail( -// # tag -// # ? VALIDATION_ERRORS.badIndex.create( -// # tag.getRange().start, -// # tag.getRange().end -// # ) -// # : undefined -// # ); -// # } -// # const nextTag = tag -// # ? tag.getVal()[Number.parseInt(next, 10)] -// # : undefined; -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: node.item, -// # tag: nextTag -// # }) -// # ); -// # } -// # -// # private walkNextNode(data: ContextData): ReturnedInfo { -// # const { reader, node, tag, useReferences, finalValidation } = data; -// # const helper = new ReturnHelper(); -// # if (reader.onLast()) { -// # if (isRefNode(node)) { -// # return helper.return( -// # this.walkRefNode(data as ContextData) -// # ); -// # } else if (isFunctionNode(node)) { -// # return helper.return( -// # this.walkFunctionNode(data as ContextData) -// # ); -// # } else if (isCompoundNode(node)) { -// # if (finalValidation && this.validateNBT && tag) { -// # const valres = tag.valideAgainst(node, { -// # compoundMerge: () => -// # this.mergeChildRef(data as ContextData< -// # CompoundNode, -// # NBTTagCompound -// # >), -// # extraChildren: this.extraChildren -// # }); -// # if (!helper.merge(valres)) { -// # return helper.fail(); -// # } -// # } -// # return helper.succeed( -// # finalValidation -// # ? this.mergeChildRef(data as ContextData) -// # : node -// # ); -// # } else { -// # if (finalValidation && this.validateNBT && tag) { -// # const valres = tag.valideAgainst(node); -// # if (!helper.merge(valres)) { -// # return helper.fail(); -// # } -// # } -// # return helper.succeed(node); -// # } -// # } else if ( -// # useReferences && -// # node.references && -// # reader.peek() in node.references -// # ) { -// # const next = reader.read(); -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: node.references[next] -// # }) -// # ); -// # } else if (isTypedNode(node)) { -// # if (isCompoundNode(node)) { -// # if (this.validateNBT && tag) { -// # const valres = tag.valideAgainst(node, { -// # compoundMerge: () => -// # this.mergeChildRef(data as ContextData< -// # CompoundNode -// # >), -// # extraChildren: this.extraChildren -// # }); -// # if (!helper.merge(valres)) { -// # return helper.fail(); -// # } -// # } -// # if (tag && !(tag instanceof NBTTagCompound)) { -// # return helper.fail(); -// # } -// # return helper.return( -// # this.walkCompoundNode(data as ContextData< -// # CompoundNode, -// # NBTTagCompound -// # >) -// # ); -// # } else if (isListNode(node)) { -// # if (this.validateNBT && tag) { -// # const valres = tag.valideAgainst(node); -// # if (!helper.merge(valres)) { -// # return helper.fail(); -// # } -// # } -// # if (tag && !(tag instanceof NBTTagList)) { -// # return helper.fail(); -// # } -// # return helper.return( -// # this.walkListNode(data as ContextData) -// # ); -// # } else if (isRootNode(node)) { -// # return helper.return( -// # this.walkRootNode(data as ContextData) -// # ); -// # } else { -// # if (tag) { -// # const valres = tag.valideAgainst(node); -// # helper.merge(valres); -// # } -// # return helper.fail(); -// # } -// # } else { -// # if (isRefNode(node)) { -// # return helper.return( -// # this.walkRefNode(data as ContextData) -// # ); -// # } else if (isFunctionNode(node)) { -// # return helper.return( -// # this.walkFunctionNode(data as ContextData) -// # ); -// # } -// # } -// # return helper.fail(); -// # } -// # -// # private walkRef( -// # ref: string, -// # path: string, -// # data: ContextData -// # ): ReturnedInfo { -// # const [nextPath, fragPath] = parseRefPath(ref, path); -// # const reader = new ArrayReader(fragPath); -// # const node = this.docs.get(nextPath) as NBTNode; -// # // tslint:disable-next-line:helper-return -// # return this.walkNextNode({ -// # useReferences: true, -// # finalValidation: false, -// # node, -// # path: nextPath, -// # reader, -// # tag: data.tag -// # }); -// # } -// # -// # private walkRefNode(data: ContextData): ReturnedInfo { -// # const { node, path } = data; -// # const helper = new ReturnHelper(); -// # const [nextPath] = parseRefPath(node.ref, path); -// # const nnode = this.walkRef(node.ref, path, data); -// # if (helper.merge(nnode)) { -// # const out = this.walkNextNode({ -// # ...data, -// # node: nnode.data, -// # path: nextPath -// # }); -// # if (helper.merge(out)) { -// # return helper.succeed(out.data); -// # } else { -// # return helper.fail(); -// # } -// # } else { -// # return helper.fail(); -// # } -// # } -// # -// # private walkRootNode(data: ContextData): ReturnedInfo { -// # const { node, reader, path } = data; -// # const next = reader.read(); -// # const helper = new ReturnHelper(); -// # if (next in node.children) { -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: node.children[next] -// # }) -// # ); -// # } else { -// # for (const key of Object.keys(node.children)) { -// # if (key.startsWith("$")) { -// # const ref = key.substring(1); -// # const [nextPath] = parseRefPath(ref, path); -// # const list = (this.docs.get(nextPath) as any) as ValueList; -// # if ( -// # list.find( -// # v => (isString(v) ? v === next : v.value === next) -// # ) -// # ) { -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: node.children[key] -// # }) -// # ); -// # } -// # } -// # } -// # } -// # return helper.fail(); -// # } -// #} -// # +exports.NBTWalker = NBTWalker; },{"./doc-walker-func":"51vC","./util/array-reader":"PoZp","./util/doc-walker-util":"km+2"}],"JpDU":[function(require,module,exports) { "use strict"; @@ -4425,7 +4022,7 @@ const paths = [{ }, { data: args => ({ ids: args.otherEntity && args.otherEntity.ids && // tslint:disable-next-line:no-unnecessary-callback-wrapper - args.otherEntity.ids.map(v => misc_functions_1.stringifyNamespace(v)) || [], + args.otherEntity.ids.map(v => misc_functions_1.stringifyID(v)) || [], kind: "entity" }), path: ["summon", "entity"] // TODO - handle nbt_tag in /data modify @@ -4627,7 +4224,7 @@ function parseBlockArgument(reader, info, tags) { const parsedResult = parsed.data; if (parsedResult.resolved && parsedResult.values) { - stringifiedName = `#${misc_functions_1.stringifyNamespace(parsedResult.parsed)}`; + stringifiedName = `#${misc_functions_1.stringifyID(parsedResult.parsed)}`; helper.merge(nmsp_tag_1.buildTagActions(parsedResult.values, start + 1, reader.cursor, "block_tags", info.data.localData)); const props = constructProperties(parsedResult.resolved, info.data.globalData.blocks); const propsResult = parseProperties(reader, props || {}, exceptions.tag_properties, stringifiedName); @@ -4639,7 +4236,7 @@ function parseBlockArgument(reader, info, tags) { if (reader.peek() === "{") { const nbt = nbt_1.validateParse(reader, info, { // tslint:disable-next-line:no-unnecessary-callback-wrapper - ids: (parsedResult.resolved || []).map(v => misc_functions_1.stringifyNamespace(v)), + ids: (parsedResult.resolved || []).map(v => misc_functions_1.stringifyID(v)), kind: "block" }); @@ -4650,10 +4247,10 @@ function parseBlockArgument(reader, info, tags) { helper.addSuggestion(reader.cursor, "{"); } } else { - stringifiedName = misc_functions_1.stringifyNamespace(parsed.data.parsed); + stringifiedName = misc_functions_1.stringifyID(parsed.data.parsed); if (info.suggesting && !reader.canRead()) { - helper.addSuggestions(...misc_functions_1.namespaceSuggestionString(Object.keys(info.data.globalData.blocks), parsed.data.parsed, start)); + helper.addSuggestions(...misc_functions_1.namespaceSuggestionString(Object.keys(info.data.globalData.blocks), start)); } const props = info.data.globalData.blocks[stringifiedName]; @@ -4683,8 +4280,8 @@ function parseBlockArgument(reader, info, tags) { } } else { if (parsed.data) { - helper.addErrors(exceptions.unknown_tag.create(start, reader.cursor, misc_functions_1.stringifyNamespace(parsed.data))); - stringifiedName = `#${misc_functions_1.stringifyNamespace(parsed.data)}`; + helper.addErrors(exceptions.unknown_tag.create(start, reader.cursor, misc_functions_1.stringifyID(parsed.data))); + stringifiedName = `#${misc_functions_1.stringifyID(parsed.data)}`; const propsResult = parseProperties(reader, {}, exceptions.unknown_properties, stringifiedName); if (!helper.merge(propsResult)) { @@ -4809,7 +4406,7 @@ function constructProperties(options, blocks) { const result = {}; for (const blockName of options) { - const stringified = misc_functions_1.stringifyNamespace(blockName); + const stringified = misc_functions_1.stringifyID(blockName); const block = blocks[stringified]; if (block) { @@ -5073,7 +4670,7 @@ const errors_1 = require("../../brigadier/errors"); const misc_functions_1 = require("../../misc-functions"); -class NamespaceListParser { +class RegistryListParser { constructor(registryType, errorBuilder, context) { this.registryType = registryType; this.error = errorBuilder; @@ -5083,7 +4680,7 @@ class NamespaceListParser { parse(reader, info) { const helper = new misc_functions_1.ReturnHelper(info); const start = reader.cursor; - const result = misc_functions_1.parseNamespaceOption(reader, misc_functions_1.stringArrayToNamespaces([...info.data.globalData.registries[this.registryType]])); + const result = misc_functions_1.parseNamespaceOption(reader, misc_functions_1.stringArrayToIDs([...info.data.globalData.registries[this.registryType]])); if (helper.merge(result)) { if (this.resultFunction) { @@ -5094,7 +4691,7 @@ class NamespaceListParser { } } else { if (result.data) { - return helper.addErrors(this.error.create(start, reader.cursor, misc_functions_1.stringifyNamespace(result.data))).succeed(); + return helper.addErrors(this.error.create(start, reader.cursor, misc_functions_1.stringifyID(result.data))).succeed(); } else { return helper.fail(); } @@ -5103,19 +4700,19 @@ class NamespaceListParser { } -exports.NamespaceListParser = NamespaceListParser; +exports.RegistryListParser = RegistryListParser; exports.summonError = new errors_1.CommandErrorBuilder("entity.notFound", "Unknown entity: %s"); -exports.summonParser = new NamespaceListParser("minecraft:entity_type", exports.summonError, (context, ids) => context.otherEntity = { +exports.summonParser = new RegistryListParser("minecraft:entity_type", exports.summonError, (context, ids) => context.otherEntity = { ids }); const enchantmentError = new errors_1.CommandErrorBuilder("enchantment.unknown", "Unknown enchantment: %s"); -exports.enchantmentParser = new NamespaceListParser("minecraft:enchantment", enchantmentError); +exports.enchantmentParser = new RegistryListParser("minecraft:enchantment", enchantmentError); const mobEffectError = new errors_1.CommandErrorBuilder("effect.effectNotFound", "Unknown effect: %s"); -exports.mobEffectParser = new NamespaceListParser("minecraft:mob_effect", mobEffectError); +exports.mobEffectParser = new RegistryListParser("minecraft:mob_effect", mobEffectError); const particleError = new errors_1.CommandErrorBuilder("particle.notFound", "Unknown particle: %s"); -exports.particleParser = new NamespaceListParser("minecraft:particle_type", particleError); +exports.particleParser = new RegistryListParser("minecraft:particle_type", particleError); const dimensionError = new errors_1.CommandErrorBuilder("argument.dimension.invalid", "Unknown dimension: '%s'"); -exports.dimensionParser = new NamespaceListParser("minecraft:dimension_type", dimensionError); +exports.dimensionParser = new RegistryListParser("minecraft:dimension_type", dimensionError); },{"../../brigadier/errors":"aP4V","../../misc-functions":"KBGm"}],"1Kfp":[function(require,module,exports) { "use strict"; @@ -5508,10 +5105,10 @@ function parseAdvancements(reader, info) { if (!res.data) { return helper.fail(); } else { - advname = misc_functions_1.stringifyNamespace(res.data); + advname = misc_functions_1.stringifyID(res.data); } } else { - advname = misc_functions_1.stringifyNamespace(res.data.literal); + advname = misc_functions_1.stringifyID(res.data.literal); res.data.values.map(v => v.data).filter(v => !!v).forEach(v => criteriaOptions.push(...v)); } @@ -5862,7 +5459,7 @@ exports.argParsers = { if (!helper.merge(parsedType)) { if (parsedType.data) { - helper.addErrors(errors.unknown_tag.create(start, reader.cursor, misc_functions_1.stringifyNamespace(parsedType.data))); + helper.addErrors(errors.unknown_tag.create(start, reader.cursor, misc_functions_1.stringifyID(parsedType.data))); return helper.succeed(); } @@ -5870,11 +5467,11 @@ exports.argParsers = { } if (!parsedType.data.resolved) { - const postProcess = misc_functions_1.processParsedNamespaceOption(parsedType.data.parsed, misc_functions_1.stringArrayToNamespaces([...info.data.globalData.registries["minecraft:entity_type"]]), info.suggesting && !reader.canRead(), start, vscode_languageserver_1.CompletionItemKind.Event); + const postProcess = misc_functions_1.processParsedNamespaceOption(parsedType.data.parsed, misc_functions_1.stringArrayToIDs([...info.data.globalData.registries["minecraft:entity_type"]]), info.suggesting && !reader.canRead(), start, vscode_languageserver_1.CompletionItemKind.Event); helper.merge(postProcess); if (postProcess.data.length === 0) { - helper.addErrors(namespace_list_1.summonError.create(start, reader.cursor, misc_functions_1.stringifyNamespace(parsedType.data.parsed))); + helper.addErrors(namespace_list_1.summonError.create(start, reader.cursor, misc_functions_1.stringifyID(parsedType.data.parsed))); } } @@ -5888,7 +5485,7 @@ exports.argParsers = { unset } = typeInfo; // tslint:disable-next-line:no-unnecessary-callback-wrapper - const stringifiedTypes = parsedTypes.map(v => misc_functions_1.stringifyNamespace(v)); + const stringifiedTypes = parsedTypes.map(v => misc_functions_1.stringifyID(v)); if (!negated) { if (stringifiedTypes.every(set.has.bind(set))) { @@ -5974,7 +5571,7 @@ class EntityBase { limit: 1, type: { set: new Set( // tslint:disable-next-line:no-unnecessary-callback-wrapper - ((info.context.executor || {}).ids || []).map(v => misc_functions_1.stringifyNamespace(v))), + ((info.context.executor || {}).ids || []).map(v => misc_functions_1.stringifyID(v))), unset: blankSet } } @@ -6121,7 +5718,7 @@ function getContextChange(context, path) { for (const item of context.type.set.values()) { if (!context.type.unset.has(item)) { - result.push(misc_functions_1.convertToNamespace(item)); + result.push(misc_functions_1.convertToID(item)); } } @@ -6185,10 +5782,10 @@ class ItemParser { }); } else { if (properties.suggesting && !reader.canRead()) { - helper.addSuggestions(...misc_functions_1.namespaceSuggestionString([...properties.data.globalData.registries["minecraft:item"]], parsed.data.parsed, start)); + helper.addSuggestions(...misc_functions_1.namespaceSuggestionString([...properties.data.globalData.registries["minecraft:item"]], start)); } - const name = misc_functions_1.stringifyNamespace(parsed.data.parsed); + const name = misc_functions_1.stringifyID(parsed.data.parsed); if (!properties.data.globalData.registries["minecraft:item"].has(name)) { helper.addErrors(UNKNOWNITEM.create(start, reader.cursor, name)); @@ -6208,7 +5805,7 @@ class ItemParser { } } else { if (parsed.data) { - helper.addErrors(UNKNOWNTAG.create(start, reader.cursor, misc_functions_1.stringifyNamespace(parsed.data))); + helper.addErrors(UNKNOWNTAG.create(start, reader.cursor, misc_functions_1.stringifyID(parsed.data))); if (reader.peek() === "{") { const nbt = nbt_1.validateParse(reader, properties, { @@ -6429,7 +6026,7 @@ function entityDataPath(path) { data: c => ({ contextInfo: { ids: c.otherEntity && c.otherEntity.ids && // tslint:disable-next-line:no-unnecessary-callback-wrapper - c.otherEntity.ids.map(v => misc_functions_1.stringifyNamespace(v)) || "none", + c.otherEntity.ids.map(v => misc_functions_1.stringifyID(v)) || "none", kind: "entity" }, resultType: c.nbt_path @@ -6750,7 +6347,7 @@ exports.functionParser = { const postProcess = misc_functions_1.processParsedNamespaceOption(data.parsed, options, info.suggesting && !reader.canRead(), start, vscode_languageserver_1.CompletionItemKind.Method); if (postProcess.data.length === 0) { - helper.addErrors(exceptions.unknown_function.create(start, reader.cursor, misc_functions_1.stringifyNamespace(data.parsed))); + helper.addErrors(exceptions.unknown_function.create(start, reader.cursor, misc_functions_1.stringifyID(data.parsed))); } if (localData) { @@ -6774,7 +6371,7 @@ exports.functionParser = { if (!parsed.data) { return helper.fail(); } else { - return helper.addErrors(exceptions.unknown_tag.create(start, reader.cursor, misc_functions_1.stringifyNamespace(parsed.data))).succeed(); + return helper.addErrors(exceptions.unknown_tag.create(start, reader.cursor, misc_functions_1.stringifyID(parsed.data))).succeed(); } } } @@ -6789,14 +6386,14 @@ const bossbarParser = { if (info.data.localData && info.data.localData.nbt.level) { const start = reader.cursor; const bars = info.data.localData.nbt.level.Data.CustomBossEvents; - const options = misc_functions_1.stringArrayToNamespaces(Object.keys(bars)); + const options = misc_functions_1.stringArrayToIDs(Object.keys(bars)); const result = misc_functions_1.parseNamespaceOption(reader, options); if (helper.merge(result)) { return helper.succeed(); } else { if (result.data) { - return helper.addErrors(exceptions.nobossbar.create(start, reader.cursor, misc_functions_1.stringifyNamespace(result.data))).succeed(); + return helper.addErrors(exceptions.nobossbar.create(start, reader.cursor, misc_functions_1.stringifyID(result.data))).succeed(); } else { return helper.fail(); } @@ -6869,7 +6466,7 @@ exports.resourceParser = { } else { if (result.data) { return helper.addErrors( // @ts-ignore type inference failure - kind.issue.create(start, reader.cursor, misc_functions_1.stringifyNamespace(result.data))).succeed(); + kind.issue.create(start, reader.cursor, misc_functions_1.stringifyID(result.data))).succeed(); } else { return helper.fail(); } @@ -6891,9 +6488,9 @@ const misc_functions_1 = require("../../misc-functions"); exports.verbatimCriteria = new Set(["air", "armor", "deathCount", "dummy", "food", "health", "level", "playerKillCount", "totalKillCount", "trigger", "xp"]); exports.colorCriteria = ["teamkill.", "killedByTeam."]; -exports.itemCriteria = misc_functions_1.stringArrayToNamespaces(["minecraft:broken", "minecraft:crafted", "minecraft:dropped", "minecraft:picked_up", "minecraft:used"]); -exports.blockCriteria = misc_functions_1.stringArrayToNamespaces(["minecraft:mined"]); -exports.entityCriteria = misc_functions_1.stringArrayToNamespaces(["minecraft:killed_by", "minecraft:killed"]); +exports.itemCriteria = misc_functions_1.stringArrayToIDs(["minecraft:broken", "minecraft:crafted", "minecraft:dropped", "minecraft:picked_up", "minecraft:used"]); +exports.blockCriteria = misc_functions_1.stringArrayToIDs(["minecraft:mined"]); +exports.entityCriteria = misc_functions_1.stringArrayToIDs(["minecraft:killed_by", "minecraft:killed"]); },{"../../misc-functions":"KBGm"}],"tZ+1":[function(require,module,exports) { "use strict"; @@ -7047,7 +6644,7 @@ ${JSON.stringify(team, undefined, 4)} } }; const UNKNOWN_CRITERIA = new errors_1.CommandErrorBuilder("argument.criteria.invalid", "Unknown criteria '%s'"); -const customPrefix = misc_functions_1.convertToNamespace("minecraft:custom"); +const customPrefix = misc_functions_1.convertToID("minecraft:custom"); exports.criteriaParser = { parse: (reader, info) => { // tslint:disable:no-shadowed-variable @@ -7106,8 +6703,8 @@ exports.criteriaParser = { for (const choice of criteria_1.entityCriteria) { reader.cursor = postStart; - if (misc_functions_1.namespacesEqual(data.literal, choice)) { - const result = misc_functions_1.parseNamespaceOption(reader, misc_functions_1.stringArrayToNamespaces([...info.data.globalData.registries["minecraft:entity_type"]]), vscode_languageserver_1.CompletionItemKind.Reference, "."); + if (misc_functions_1.idsEqual(data.literal, choice)) { + const result = misc_functions_1.parseNamespaceOption(reader, misc_functions_1.stringArrayToIDs([...info.data.globalData.registries["minecraft:entity_type"]]), vscode_languageserver_1.CompletionItemKind.Reference, "."); if (helper.merge(result)) { return helper.succeed(); @@ -7118,8 +6715,8 @@ exports.criteriaParser = { for (const choice of criteria_1.blockCriteria) { reader.cursor = postStart; - if (misc_functions_1.namespacesEqual(data.literal, choice)) { - const result = misc_functions_1.parseNamespaceOption(reader, misc_functions_1.stringArrayToNamespaces([...info.data.globalData.registries["minecraft:block"]]), vscode_languageserver_1.CompletionItemKind.Reference, "."); + if (misc_functions_1.idsEqual(data.literal, choice)) { + const result = misc_functions_1.parseNamespaceOption(reader, misc_functions_1.stringArrayToIDs([...info.data.globalData.registries["minecraft:block"]]), vscode_languageserver_1.CompletionItemKind.Reference, "."); if (helper.merge(result)) { return helper.succeed(); @@ -7130,8 +6727,8 @@ exports.criteriaParser = { for (const choice of criteria_1.itemCriteria) { reader.cursor = postStart; - if (misc_functions_1.namespacesEqual(data.literal, choice)) { - const result = misc_functions_1.parseNamespaceOption(reader, misc_functions_1.stringArrayToNamespaces([...info.data.globalData.registries["minecraft:entity_type"]]), vscode_languageserver_1.CompletionItemKind.Reference, "."); + if (misc_functions_1.idsEqual(data.literal, choice)) { + const result = misc_functions_1.parseNamespaceOption(reader, misc_functions_1.stringArrayToIDs([...info.data.globalData.registries["minecraft:entity_type"]]), vscode_languageserver_1.CompletionItemKind.Reference, "."); if (helper.merge(result)) { return helper.succeed(); @@ -7139,8 +6736,8 @@ exports.criteriaParser = { } } - if (misc_functions_1.namespacesEqual(data.literal, customPrefix)) { - const result = misc_functions_1.parseNamespaceOption(reader, misc_functions_1.stringArrayToNamespaces([...info.data.globalData.registries["minecraft:custom_stat"]]), vscode_languageserver_1.CompletionItemKind.Reference, "."); + if (misc_functions_1.idsEqual(data.literal, customPrefix)) { + const result = misc_functions_1.parseNamespaceOption(reader, misc_functions_1.stringArrayToIDs([...info.data.globalData.registries["minecraft:custom_stat"]]), vscode_languageserver_1.CompletionItemKind.Reference, "."); if (helper.merge(result)) { return helper.succeed(); @@ -7858,10 +7455,9 @@ function getNodeTree(line) { return undefined; } -function getWorkspaceSymbols(manager, query) { +function getWorkspaceSymbols(manager) { const result = []; const worlds = manager.packData; - const namespace = misc_functions_1.convertToNamespace(query); for (const worldPath of Object.keys(worlds)) { const world = worlds[worldPath]; @@ -7875,16 +7471,14 @@ function getWorkspaceSymbols(manager, query) { if (val) { for (const item of val) { - if (misc_functions_1.namespaceStart(item, namespace)) { - result.push({ - kind: symbolKindForResource(type), - location: { - range: blanks_1.blankRange, - uri: vscode_uri_1.default.file(misc_functions_1.buildPath(item, world, type)).toString() - }, - name: misc_functions_1.stringifyNamespace(item) - }); - } + result.push({ + kind: symbolKindForResource(type), + location: { + range: blanks_1.blankRange, + uri: vscode_uri_1.default.file(misc_functions_1.buildPath(item, world, type)).toString() + }, + name: misc_functions_1.stringifyID(item) + }); } } } @@ -8817,7 +8411,7 @@ class DataManager { for (let i = 0; i < contents.length; i++) { const element = contents[i]; - if (misc_functions_1.namespacesEqual(element, namespace.location)) { + if (misc_functions_1.idsEqual(element, namespace.location)) { contents.splice(i, 1); break; } @@ -9498,8 +9092,8 @@ connection.onCompletion(params => { connection.onDefinition(prepare(actions_1.definitionProvider, [])); // #connection.onDocumentHighlight(); // #connection.onDocumentSymbol(); // This is for sections - there are none in mcfunctions -connection.onWorkspaceSymbol(query => actions_1.getWorkspaceSymbols(manager, query.query)); -connection.onHover(prepare(actions_1.hoverProvider, undefined)); +connection.onWorkspaceSymbol(() => actions_1.getWorkspaceSymbols(manager)); +connection.onHover(prepare(actions_1.hoverProvider)); connection.onSignatureHelp(prepare(actions_1.signatureHelpProvider)); function prepare(func, fallback) { diff --git a/package.json b/package.json index f7e80cc..91e1a53 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "node-interval-tree": "^1.3.3", "request": "^2.88.0", "request-promise-native": "^1.0.5", + "snap-shot-it": "^6.2.10", "sprintf-js": "^1.1.2", "synchronous-promise": "^2.0.6", "tslib": "^1.9.3", @@ -54,16 +55,14 @@ "@types/mocha": "5.2.6", "@types/node": "10.12.27", "@types/request-promise-native": "1.0.15", - "@types/rimraf": "2.0.2", "@types/sprintf-js": "1.1.2", "husky": "1.3.1", "mocha": "6.0.2", "parcel": "1.11.0", "prettier": "1.16.4", "pretty-quick": "1.10.0", - "rimraf": "2.6.3", "ts-mocha": "6.0.0", - "tslint": "5.13.0", + "tslint": "5.14.0", "tslint-config-prettier": "1.18.0", "typescript": "3.3.3333", "vscode-languageserver-types": "3.14.0" diff --git a/src/actions.ts b/src/actions.ts index 1c2025a..68c5acc 100644 --- a/src/actions.ts +++ b/src/actions.ts @@ -14,22 +14,17 @@ import URI from "vscode-uri"; import { getAllNodes } from "./completions"; import { COMMENT_START } from "./consts"; import { DataManager } from "./data/manager"; -import { CommandNode, CommandTree, MCNode, Resources } from "./data/types"; -import { - buildPath, - convertToNamespace, - followPath, - getNextNode, - namespaceStart, - stringifyNamespace -} from "./misc-functions"; +import { followPath, getNextNode } from "./misc-functions"; import { typed_keys } from "./misc-functions/third_party/typed-keys"; -import { blankRange } from "./test/blanks"; import { CommandLine, + CommandNode, + CommandTree, FunctionInfo, JSONDocInfo, + MCNode, ParseNode, + Resources, SubAction } from "./types"; @@ -66,7 +61,7 @@ export function hoverProvider( character: pos.character - json[0].low, line: 0 }; - manager.globalData.jsonService + manager.commandData.jsonService .doHover(doc.text, position, doc.json) .then(v => (result = v)); if (result) { @@ -99,7 +94,7 @@ export function hoverProvider( value: i .map(node => { const data = followPath( - manager.globalData.commands, + manager.commandData.commands, node.path ) as CommandNode; return `${ @@ -274,7 +269,7 @@ function getSignatureHelp( path: string[], manager: DataManager ): SignatureInformation[] { - const commands = manager.globalData.commands; + const commands = manager.commandData.commands; const next = getNextNode(followPath(commands, path), path, commands); const options = buildSignatureHelpForChildren( next.node, @@ -345,38 +340,28 @@ function getNodeTree(line: CommandLine): IntervalTree | undefined { return undefined; } -export function getWorkspaceSymbols( - manager: DataManager, - query: string -): SymbolInformation[] { +export function getWorkspaceSymbols(manager: DataManager): SymbolInformation[] { const result: SymbolInformation[] = []; - const worlds = manager.packData; - const namespace = convertToNamespace(query); - for (const worldPath of Object.keys(worlds)) { - const world = worlds[worldPath]; - for (const packID in world.packs) { - if (world.packs.hasOwnProperty(packID)) { - const pack = world.packs[packID]; - for (const type of typed_keys(pack.data)) { - const val = pack.data[type]; - if (val) { - for (const item of val) { - if (namespaceStart(item, namespace)) { - result.push({ - kind: symbolKindForResource(type), - location: { - range: blankRange, - uri: URI.file(buildPath( - item, - world, - type - ) as any).toString() - }, - name: stringifyNamespace(item) - }); - } - } - } + const resources = manager.commandData.resources; + for (const type of typed_keys(resources)) { + const map = resources[type]; + for (const [, /* name */ resource] of map) { + if (resource.has(undefined) ? resource.size > 1 : true) { + for (const _pack of resource.keys()) { + // TODO: reorganise to key all packs on a unique DatapackReference. + // I.e. a world stores a Map and we store a central Map + /* result.push({ + kind: symbolKindForResource(type), + location: { + range: blankRange, + uri: URI.file(buildPath( + name, + world, + type + ) as any).toString() + }, + name: stringifyID(name) + }); */ } } } diff --git a/src/brigadier/string-reader.ts b/src/brigadier/string-reader.ts index d37288c..fe5d6f6 100644 --- a/src/brigadier/string-reader.ts +++ b/src/brigadier/string-reader.ts @@ -56,7 +56,6 @@ const EXCEPTIONS = { const QUOTE = '"'; const SINGLE_QUOTE = "'"; const ESCAPE = "\\"; -export type QuotingKind = "both" | "yes" | RegExp; export interface QuotingInfo { /** @@ -96,12 +95,10 @@ export class StringReader { */ public expect(str: string): ReturnedInfo { const helper = new ReturnHelper(); - if (str.startsWith(this.getRemaining())) { - helper.addSuggestions({ - start: this.cursor, - text: str - }); - } + helper.addSuggestions({ + start: this.cursor, + text: str + }); const sub = this.string.substr(this.cursor, str.length); if (sub !== str) { return helper.fail( @@ -276,13 +273,10 @@ export class StringReader { // Reading failed, which must be due to an invalid quoted string if (!helper.merge(result, { suggestions: false })) { if (result.data && !this.canRead()) { - const bestEffort = result.data; helper.addSuggestions( - ...options - .filter(option => option.startsWith(bestEffort)) - .map(v => - completionForString(v, start, quoteKind, completion) - ) + ...options.map(v => + completionForString(v, start, quoteKind, completion) + ) ); } return helper.fail(); @@ -290,26 +284,14 @@ export class StringReader { const valid = options.some(opt => opt === result.data); if (!this.canRead()) { helper.addSuggestions( - ...options - .filter(opt => opt.startsWith(result.data)) - .map(v => - completionForString(v, start, quoteKind, completion) - ) + ...options.map(v => + completionForString(v, start, quoteKind, completion) + ) ); } if (valid) { return helper.succeed(result.data as T); } else { - /* if (addError) { - helper.addErrors( - EXCEPTIONS.EXPECTED_STRING_FROM.create( - start, - this.cursor, - JSON.stringify(options), - result.data - ) - ); - } */ return helper.failWithData(result.data); } } diff --git a/src/completions.ts b/src/completions.ts index bfe7594..40ec574 100644 --- a/src/completions.ts +++ b/src/completions.ts @@ -31,7 +31,7 @@ export function computeCompletions( return CompletionList.create([], true); } const commandData: CommandData = { - globalData: data.globalData, + globalData: data.commandData, localData: data.getPackFolderData(document.pack_segments) }; const nodes = line.parseInfo ? line.parseInfo.nodes : []; diff --git a/src/data/cache.ts b/src/data/cache.ts index a1ddea3..76cdcdb 100644 --- a/src/data/cache.ts +++ b/src/data/cache.ts @@ -1,5 +1,6 @@ import * as path from "path"; +import { SerializedIDMap } from "../misc-functions/id-map"; import { mkdirAsync, readJSONRaw, @@ -7,74 +8,177 @@ import { writeJSON } from "../misc-functions/promisified-fs"; import { typed_keys } from "../misc-functions/third_party/typed-keys"; -import { WorkspaceSecurity } from "../types"; +import { + Blocks, + CommandData, + CommandTree, + RegistryData, + RegistryNames, + Resources, + ResourcesMap, + WorkspaceSecurity +} from "../types"; -import { Cacheable, RegistriesData } from "./types"; +import { + createBlockMap, + createRegistryMap, + createResourceMap, + createTagMap +} from "./maps"; if (!process.env.MCFUNCTION_CACHE_DIR) { throw new Error("Environment variable MCFUNCTION_CACHE_DIR must be set"); } export const cacheFolder = process.env.MCFUNCTION_CACHE_DIR; -const cacheFileNames: Record, string> = { +export type CacheHandled = Pick< + CommandData, + "blocks" | "commands" | "data_info" | "registries" | "resources" +>; + +type CachedRegistries = Record>; +type CachedResources = Record>; + +const caches = { blocks: "blocks.json", commands: "commands.json", - meta_info: "meta_info.json", + data_info: "data_info.json", + registries: "registries.json", resources: "resources.json" }; +for (const key of typed_keys(caches)) { + caches[key] = path.join(cacheFolder, caches[key]); +} -const registriesCacheFile = "registries.json"; - -export async function readCache(): Promise { - const data: Cacheable = {} as Cacheable; - const keys = typed_keys(cacheFileNames); +export async function readCache(): Promise { + const data: CacheHandled = {} as CacheHandled; await Promise.all([ - ...keys.map(async key => { - const cacheDir = cacheFileNames[key]; - data[key] = await readJSONRaw( - path.join(cacheFolder, cacheDir) - ); - }), - readRegistries(data) + readJSONRaw(caches.commands).then( + commands => (data.commands = commands) + ), + readJSONRaw(caches.data_info).then( + dataInfo => (data.data_info = dataInfo) + ), + readBlocks().then(blocks => (data.blocks = blocks)), + readRegistries().then(registries => (data.registries = registries)), + readResources().then(resources => (data.resources = resources)) ]); return data; } -type CachedRegistries = Record; +type SerializedBlocks = Array<[string, string[]]>; -async function readRegistries(data: Cacheable): Promise { - const read = await readJSONRaw( - path.join(cacheFolder, registriesCacheFile) +async function readBlocks(): Promise { + const blocks = await readJSONRaw>( + caches.blocks + ); + const result = createBlockMap(); + result.addSerialized( + blocks, + values => + new Map( + values.map<[string, Set]>(([k, v]) => [k, new Set(v)]) + ) ); - data.registries = {} as any; + + return result; +} + +async function readRegistries(): Promise { + const read = await readJSONRaw(caches.registries); + const result: RegistryData = {} as any; + for (const key of typed_keys(read)) { + const newSet = createRegistryMap(); + newSet.addSerialized(read[key]); + result[key] = newSet; + } + return result; +} + +const resourceNames: Partial> = { + block_tags: "Block Tags", + entity_tags: "Entity Tags", + fluid_tags: "Fluid Tags", + function_tags: "Function Tags", + item_tags: "Item Tags" +}; + +async function readResources(): Promise { + const read = await readJSONRaw(caches.registries); + const result: Resources = {} as any; for (const key of typed_keys(read)) { - data.registries[key] = new Set(read[key]); + switch (key) { + case "block_tags": + case "entity_tags": + case "fluid_tags": + case "function_tags": + case "item_tags": + const newTags = createTagMap(resourceNames[key] || key); + newTags.addSerialized( + read[key], + value => new Map([[undefined, value]]) + ); + result[key] = newTags; + break; + + default: + const newSet = createResourceMap(resourceNames[key] || key); + newSet.addSerialized( + read[key], + value => new Map([undefined, value]) + ); + result[key] = newSet; + } } + return result; } -export async function cacheData(data: Cacheable): Promise { +export async function cacheData(data: CacheHandled): Promise { try { await mkdirAsync(cacheFolder, "777"); } catch (_) { // Don't use the error, which is normally thrown if the folder already exists } - const keys: Array = typed_keys(cacheFileNames); + const { blocks, commands, data_info, registries, resources } = data; await Promise.all([ - ...keys.map(async key => - writeJSON(path.join(cacheFolder, cacheFileNames[key]), data[key]) - ), - cacheRegistry(data.registries) + cacheRegistry(registries), + cacheResources(resources), + writeJSON(caches.data_info, data_info), + writeJSON(caches.commands, commands), + cacheBlocks(blocks) ]); } -async function cacheRegistry(registries: RegistriesData): Promise { +async function cacheRegistry( + registries: CacheHandled["registries"] +): Promise { const toWrite = {} as CachedRegistries; for (const key of typed_keys(registries)) { - toWrite[key] = [...registries[key]]; + toWrite[key] = registries[key].serialize(); } - await writeJSON(path.join(cacheFolder, registriesCacheFile), toWrite); + await writeJSON(caches.registries, toWrite); } +async function cacheResources( + resources: CacheHandled["resources"] +): Promise { + const toWrite = {} as CachedResources; + for (const key of typed_keys(resources)) { + const value: ResourcesMap = resources[key]; + toWrite[key] = value.serialize( + map => map.has(undefined) && map.get(undefined) + ); + } + await writeJSON(caches.resources, toWrite); +} +async function cacheBlocks(blocks: CacheHandled["blocks"]): Promise { + await writeJSON( + caches.blocks, + blocks.serialize(a => + [...a].map<[string, string[]]>(v => [v[0], [...v[1]]]) + ) + ); +} export async function storeSecurity( security: WorkspaceSecurity ): Promise { diff --git a/src/data/datapack-resources.ts b/src/data/datapack-resources.ts index 0873d93..a0cf7c2 100644 --- a/src/data/datapack-resources.ts +++ b/src/data/datapack-resources.ts @@ -10,26 +10,27 @@ import { walkDir } from "../misc-functions/promisified-fs"; import { typed_keys } from "../misc-functions/third_party/typed-keys"; -import { ReturnSuccess } from "../types"; - -import { mapPacksInfo } from "./extractor/mapfunctions"; -import { loadNBT } from "./nbt/nbt-cache"; import { Datapack, DataPackID, - GlobalData, + DataPackReference, McmetaFile, - MinecraftResource, Resources, + ReturnSuccess, WorldInfo -} from "./types"; +} from "../types"; + +import { loadWorldNBT } from "./nbt/nbt-cache"; export async function getNamespaceResources( namespace: string, dataFolder: string, - id: DataPackID | undefined, - result: Resources = {} -): Promise> { + id: DataPackReference, + accumulator: Resources +): Promise> { + const initialContents: Array<[DataPackReference, undefined]> = [ + [id, undefined] + ]; const helper = new ReturnHelper(); const namespaceFolder = path.join(dataFolder, namespace); const subDirs = await subDirectories(namespaceFolder); @@ -47,7 +48,6 @@ export async function getNamespaceResources( if (files.length === 0) { return; } - const nameSpaceContents = result[type] || []; await Promise.all( files.map(async file => { const realExtension = path.extname(file); @@ -61,21 +61,21 @@ export async function getNamespaceResources( ); } const internalUri = path.relative(dataContents, file); - const newResource: MinecraftResource = { + const newResource = { namespace, - pack: id, path: internalUri .slice(0, -realExtension.length) .replace(SLASHREPLACEREGEX, SLASH) }; - nameSpaceContents.push(newResource); + accumulator[type].add( + newResource, + new Map(initialContents) + ); }) ); - result[type] = nameSpaceContents; }) ); - - return helper.succeed(result); + return helper.succeed(); } async function buildDataPack( @@ -84,13 +84,10 @@ async function buildDataPack( packName: string ): Promise> { const helper = new ReturnHelper(); - const dataFolder = path.join(packFolder, DATAFOLDER); - const [mcmeta, packResources] = await Promise.all([ - readJSON(path.join(packFolder, MCMETAFILE)), - getPackResources(dataFolder, id) - ]); - const result: Datapack = { id, data: packResources.data, name: packName }; - helper.merge(packResources); + const mcmeta = await readJSON( + path.join(packFolder, MCMETAFILE) + ); + const result: Datapack = { id, name: packName }; if (helper.merge(mcmeta)) { result.mcmeta = mcmeta.data; } @@ -99,47 +96,53 @@ async function buildDataPack( async function getPackResources( dataFolder: string, - id: DataPackID -): Promise> { + id: DataPackID, + resources: Resources +): Promise> { const helper = new ReturnHelper(); const namespaces = await subDirectories(dataFolder); - const result: Resources = {}; await Promise.all( namespaces.map(async namespace => { - const resources = await getNamespaceResources( + const result = await getNamespaceResources( namespace, dataFolder, id, - result + resources ); - helper.merge(resources); - return resources.data; + helper.merge(result); }) ); - return helper.succeed(result); + return helper.succeed(); } export async function getPacksInfo( - location: string, - globalData: GlobalData + datapacksFolder: string, + resources: Resources ): Promise> { - const packNames = await subDirectories(location); + const packNames = await subDirectories(datapacksFolder); const helper = new ReturnHelper(); const packs = [...packNames.entries()]; - const nbt = await loadNBT(path.resolve(location, "../")); - const result: WorldInfo = { location, packnamesmap: {}, packs: {}, nbt }; + const nbt = await loadWorldNBT(path.join(datapacksFolder, "..")); + const result: WorldInfo = { + datapacksFolder, + nbt, + packnamesmap: new Map(), + packs: new Map() + }; const promises: Array> = packs.map( async ([packID, packName]) => { - const loc = path.join(location, packName); - const packData = await buildDataPack(loc, packID, packName); + const loc = path.join(datapacksFolder, packName); + const [packData] = await Promise.all([ + buildDataPack(loc, packID, packName), + getPackResources(path.join(loc, DATAFOLDER), packID, resources) + ]); helper.merge(packData); - result.packs[packID] = packData.data; - result.packnamesmap[packName] = packID; + result.packs.set(packID, packData.data); + result.packnamesmap.set(packName, packID); } ); await Promise.all(promises); - const otherResult = await mapPacksInfo(result, globalData); - return helper.mergeChain(otherResult).succeed(otherResult.data); + return helper.succeed(result); } async function subDirectories(baseFolder: string): Promise { diff --git a/src/data/extractor/collect-data.ts b/src/data/extractor/collect-data.ts index aae3b1c..4395cbc 100644 --- a/src/data/extractor/collect-data.ts +++ b/src/data/extractor/collect-data.ts @@ -3,49 +3,50 @@ import * as path from "path"; import { promisify } from "util"; import { DATAFOLDER } from "../../consts"; -import { ReturnHelper } from "../../misc-functions"; +import { convertToID, ReturnHelper } from "../../misc-functions"; +import { IDMap } from "../../misc-functions/id-map"; import { typed_keys } from "../../misc-functions/third_party/typed-keys"; -import { ReturnSuccess } from "../../types"; -import { getNamespaceResources } from "../datapack-resources"; import { - BlocksPropertyInfo, CommandTree, - GlobalData, - RegistriesData, - RegistryNames -} from "../types"; + RegistryNames, + Resources, + ReturnSuccess +} from "../../types"; +import { CacheHandled } from "../cache"; +import { getNamespaceResources } from "../datapack-resources"; +import { createRegistryMap, createResourceMap, createTagMap } from "../maps"; import { runMapFunctions } from "./mapfunctions"; const readFileAsync = promisify(fs.readFile); -type DataSaveResult = [T, GlobalData[T]]; - +/** + * Extract the globally useful data from the minecraft jar in the directory datadir + */ export async function collectData( version: string, dataDir: string -): Promise> { +): Promise> { const helper = new ReturnHelper(); - const result: GlobalData = { meta_info: { version } } as GlobalData; - const cleanups = await Promise.all([ - getBlocks(dataDir), - getRegistries(dataDir), - getCommands(dataDir), - getResources(dataDir) + const result: CacheHandled = { data_info: { version } } as CacheHandled; + await Promise.all([ + getBlocks(dataDir).then(blocks => (result.blocks = blocks)), + getRegistries(dataDir).then( + registries => (result.registries = registries) + ), + getCommands(dataDir).then(commands => (result.commands = commands)), + getResources(dataDir).then( + resourceInfo => (result.resources = resourceInfo) + ) ]); - for (const dataType of cleanups) { - result[dataType[0]] = dataType[1]; - } const resources = await runMapFunctions(result.resources, result, dataDir); return helper .mergeChain(resources) .succeed({ ...result, resources: resources.data }); } -//#region Resources - async function getRegistries( dataDir: string -): Promise> { +): Promise { interface ProtocolID { protocol_id: number; } @@ -62,46 +63,50 @@ async function getRegistries( path.join(dataDir, "reports", "registries.json") )).toString() ); - const result = {} as RegistriesData; + const result = {} as CacheHandled["registries"]; for (const key of typed_keys(registries)) { const registry = registries[key]; if (registry.entries) { - const set = new Set(); + const set = createRegistryMap(); for (const entry of Object.keys(registry.entries)) { - set.add(entry); + set.add(convertToID(entry)); } result[key] = set; } } - return ["registries", result]; + return result; } async function getResources( dataDir: string -): Promise> { +): Promise { const dataFolder = path.join(dataDir, DATAFOLDER); - const resources = await getNamespaceResources( - "minecraft", - dataFolder, - undefined - ); - return ["resources", resources.data]; + const resources: Resources = { + advancements: createResourceMap("Advancement"), + block_tags: createTagMap("Block tag"), + entity_tags: createTagMap("Entity tag"), + fluid_tags: createTagMap("Fluid tags"), + function_tags: createTagMap("Function Tags"), + functions: createResourceMap("Function"), + item_tags: createTagMap("Item Tag"), + loot_tables: createResourceMap("Loot Table"), + recipes: createResourceMap("Recipe"), + structures: createResourceMap("Structure") + }; + await getNamespaceResources("minecraft", dataFolder, undefined, resources); + return resources; } -//#endregion -async function getCommands( - dataDir: string -): Promise> { +async function getCommands(dataDir: string): Promise { const tree: CommandTree = JSON.parse( (await readFileAsync( path.join(dataDir, "reports", "commands.json") )).toString() ); - return ["commands", tree]; + return tree; } -//#region Blocks -async function getBlocks(dataDir: string): Promise> { +async function getBlocks(dataDir: string): Promise { interface BlocksJson { [id: string]: { properties?: { @@ -110,26 +115,20 @@ async function getBlocks(dataDir: string): Promise> { }; } - function cleanBlocks(blocks: BlocksJson): BlocksPropertyInfo { - const result: BlocksPropertyInfo = {}; - for (const blockName in blocks) { - if (blocks.hasOwnProperty(blockName)) { - const blockInfo = blocks[blockName]; - result[blockName] = {}; - if (!!blockInfo.properties) { - Object.assign(result[blockName], blockInfo.properties); - } - } - } - return result; - } - const blocksData: BlocksJson = JSON.parse( (await readFileAsync( path.join(dataDir, "reports", "blocks.json") )).toString() ); - return ["blocks", cleanBlocks(blocksData)]; + const result: CacheHandled["blocks"] = new IDMap(); + for (const block of Object.keys(blocksData)) { + const props = blocksData[block]; + if (props.properties) { + const propsMap = Object.entries(props.properties).map< + [string, Set] + >(v => [v[0], new Set(v[1])]); + result.add(convertToID(block), new Map(propsMap)); + } + } + return result; } - -//#endregion diff --git a/src/data/extractor/index.ts b/src/data/extractor/index.ts index 87c7107..c8d468e 100644 --- a/src/data/extractor/index.ts +++ b/src/data/extractor/index.ts @@ -5,8 +5,7 @@ import { promisify } from "util"; import { ReturnHelper } from "../../misc-functions"; import { ReturnSuccess } from "../../types"; -import { cacheData } from "../cache"; -import { Cacheable } from "../types"; +import { cacheData, CacheHandled } from "../cache"; import { collectData } from "./collect-data"; import { getPathToJar } from "./download"; @@ -34,7 +33,7 @@ const mkdtmpAsync = promisify(fs.mkdtemp); */ export async function collectGlobalData( currentversion: string = "" -): Promise | undefined> { +): Promise | undefined> { if (mcLangSettings.data.enabled) { const javaPath = await checkJavaPath(); mcLangLog(`Using java at path ${javaPath}`); @@ -49,7 +48,7 @@ export async function collectGlobalData( mcLangLog("Data collected, caching data"); await cacheData(data.data); mcLangLog("Caching complete"); - return helper.mergeChain(data).succeed(data.data); + return helper.return(data); } else { return undefined; } diff --git a/src/data/extractor/mapfunctions.ts b/src/data/extractor/mapfunctions.ts index f143349..47edba8 100644 --- a/src/data/extractor/mapfunctions.ts +++ b/src/data/extractor/mapfunctions.ts @@ -2,7 +2,7 @@ import { join } from "path"; import { resourceTypes, ReturnHelper } from "../../misc-functions"; import { typed_keys } from "../../misc-functions/third_party/typed-keys"; -import { ReturnSuccess } from "../../types"; +import { Resources, ReturnSuccess } from "../../types"; import { GlobalData, Resources, WorldInfo } from "../types"; export async function runMapFunctions( @@ -17,7 +17,7 @@ export async function runMapFunctions( for (const type of typed_keys(resources)) { type resourcesType = NonNullable; const val = (result[type] = [] as resourcesType); - const data = resources[type] as resourcesType; + const data = resources[type]; // tslint:disable-next-line:no-unbound-method We control this function, so we know it won't use the this keyword. const mapFunction = resourceTypes[type].mapFunction; if (mapFunction) { diff --git a/src/data/lists/criteria.ts b/src/data/lists/criteria.ts index 59f280d..6e71137 100644 --- a/src/data/lists/criteria.ts +++ b/src/data/lists/criteria.ts @@ -1,4 +1,4 @@ -import { stringArrayToNamespaces } from "../../misc-functions"; +import { stringArrayToIDs } from "../../misc-functions"; export const verbatimCriteria = new Set([ "air", @@ -16,7 +16,10 @@ export const verbatimCriteria = new Set([ export const colorCriteria = ["teamkill.", "killedByTeam."]; -export const itemCriteria = stringArrayToNamespaces([ +/** + * @todo - Move to IDMap + */ +export const itemCriteria = stringArrayToIDs([ "minecraft:broken", "minecraft:crafted", "minecraft:dropped", @@ -24,9 +27,9 @@ export const itemCriteria = stringArrayToNamespaces([ "minecraft:used" ]); -export const blockCriteria = stringArrayToNamespaces(["minecraft:mined"]); +export const blockCriteria = stringArrayToIDs(["minecraft:mined"]); -export const entityCriteria = stringArrayToNamespaces([ +export const entityCriteria = stringArrayToIDs([ "minecraft:killed_by", "minecraft:killed" ]); diff --git a/src/data/manager.ts b/src/data/manager.ts index b5c5edc..53a2eef 100644 --- a/src/data/manager.ts +++ b/src/data/manager.ts @@ -1,53 +1,36 @@ -import { ok } from "assert"; -import { extname, join } from "path"; -import { - DidChangeWatchedFilesParams, - FileChangeType -} from "vscode-languageserver"; +import * as assert from "assert"; +import { DidChangeWatchedFilesParams } from "vscode-languageserver"; -import { MCMETAFILE } from "../consts"; +import { PackLocationSegments, ReturnHelper } from "../misc-functions"; import { - getKindAndNamespace, - namespacesEqual, - PackLocationSegments, - parseDataPath, - resourceTypes, - ReturnHelper -} from "../misc-functions"; -import { createExtensionFileError } from "../misc-functions/file-errors"; -import { readJSON } from "../misc-functions/promisified-fs"; -import { ReturnedInfo, ReturnSuccess } from "../types"; + CommandData, + LocalData, + ReturnedInfo, + ReturnSuccess, + WorldInfo +} from "../types"; import { readCache } from "./cache"; import { getPacksInfo } from "./datapack-resources"; import { collectGlobalData } from "./extractor"; import { loadNonCached } from "./noncached"; -import { - Datapack, - DataPackID, - GlobalData, - LocalData, - McmetaFile, - MinecraftResource, - WorldInfo -} from "./types"; export class DataManager { /** * Create a datamanager using Dummy Data for running tests. */ public static newWithData( - dummyGlobal?: DataManager["globalDataInternal"], + dummyData?: DataManager["_commandData"], dummyPacks?: DataManager["packDataComplete"] ): DataManager { const manager = new DataManager(); - manager.globalDataInternal = dummyGlobal || manager.globalDataInternal; + manager._commandData = dummyData || manager._commandData; Object.assign(manager.packDataComplete, dummyPacks); return manager; } - //#region Data Management - private globalDataInternal: GlobalData = {} as GlobalData; + + private _commandData: CommandData = {} as CommandData; private readonly packDataComplete: { [root: string]: WorldInfo } = {}; @@ -55,15 +38,13 @@ export class DataManager { [root: string]: Promise>; } = {}; - public get globalData(): GlobalData { - return this.globalDataInternal; + public get commandData(): CommandData { + return this._commandData; } public get packData(): DataManager["packDataComplete"] { return this.packDataComplete; } - //#endregion - //#region Constructor - //#endregion + public getPackFolderData( folder: PackLocationSegments | undefined ): LocalData | undefined { @@ -73,14 +54,20 @@ export class DataManager { ) { const info = this.packDataComplete[folder.packsFolder]; - return { ...info, current: info.packnamesmap[folder.pack] }; + return { ...info, current: info.packnamesmap.get(folder.pack) }; } return undefined; } + // tslint:disable-next-line: prefer-function-over-method public async handleChanges( - event: DidChangeWatchedFilesParams + _event: DidChangeWatchedFilesParams ): Promise> { + return new ReturnHelper().succeed(); + /* TODO - this is not an MVP feature. + We have also received reports that this doesn't work, so it needs some debugging + or implementation work. The original implementation is below: */ + /* const helper = new ReturnHelper(false); const firsts = new Set(); const promises = event.changes.map(async change => { @@ -135,7 +122,7 @@ export class DataManager { for (let i = 0; i < contents.length; i++) { const element = contents[i]; if ( - namespacesEqual( + idsEqual( element, namespace.location ) @@ -154,7 +141,7 @@ export class DataManager { namespace.kind ] = []; } - const newResource: MinecraftResource = { + const newResource = { ...namespace.location, pack: packID }; @@ -173,7 +160,7 @@ export class DataManager { parsedPath.packsFolder, parsedPath.pack ), - this.globalData, + this._commandData, data ); if (helper.merge(result)) { @@ -208,31 +195,32 @@ export class DataManager { }); await Promise.all(promises); return helper.succeed(); + */ } public async loadGlobalData(): Promise { let version: string | undefined; - if (!!this.globalData.meta_info) { - version = this.globalData.meta_info.version; + if (!!this._commandData.data_info) { + version = this._commandData.data_info.version; } try { const helper = new ReturnHelper(); const data = await collectGlobalData(version); if (data) { helper.merge(data); - if (this.globalDataInternal) { - this.globalDataInternal = { - ...this.globalDataInternal, + if (this._commandData) { + this._commandData = { + ...this._commandData, ...data.data }; } else { - this.globalDataInternal = { + this._commandData = { ...(await loadNonCached()), ...data.data }; } } - ok(this.globalDataInternal); + assert.ok(this._commandData); return false; } catch (error) { return `Error loading global data: ${error.stack || @@ -244,7 +232,7 @@ export class DataManager { try { const cache = await readCache(); const noncache = await loadNonCached(); - this.globalDataInternal = { ...cache, ...noncache }; + this._commandData = { ...cache, ...noncache }; mcLangLog("Cache Successfully read"); return true; } catch (error) { @@ -265,7 +253,7 @@ export class DataManager { if (!this.packDataPromises.hasOwnProperty(folder)) { this.packDataPromises[folder] = getPacksInfo( folder, - this.globalData + this._commandData.resources ); const result = await this.packDataPromises[folder]; this.packDataComplete[folder] = result.data; diff --git a/src/data/maps.ts b/src/data/maps.ts new file mode 100644 index 0000000..38c012b --- /dev/null +++ b/src/data/maps.ts @@ -0,0 +1,90 @@ +import { convertToID, stringifyID } from "../misc-functions"; +import { IDMap, IdSet } from "../misc-functions/id-map"; +import { Blocks, PackMap, ResourcesMap, Tag, TagMap } from "../types"; + +export function createBlockMap(): Blocks { + const blocks: Blocks = new IDMap(); + blocks.suggestionDescriptionFunction = (v, id) => + `Block ${stringifyID(id)} with properties: +${[...v].map( + ([prop, options]) => ` - ${prop}: +${[...options].map( + o => ` - ${o}; +` +)}` +)}}`; + return blocks; +} + +const resolveTags: TagMap["resolver"] = ( + options: PackMap, + map: TagMap +) => { + const finals = new IDMap(); + const tags = new IDMap(); + const resolved = new IDMap(); + for (const [, tag] of options) { + if (tag) { + for (const value of tag.values) { + if (value.startsWith("#")) { + const tagId = convertToID(value.substring(1)); + tags.add(tagId); + const newTags = map.get(tagId); + if (newTags) { + resolved.addFrom(newTags.resolved.resolved); + } + } else { + const finalId = convertToID(value); + finals.add(finalId); + resolved.add(finalId); + } + } + } + } + return { + finals, + resolved, + tags + }; +}; + +function resourceDescriber( + type: string, + prefix: string = "" +): ResourcesMap["suggestionDescriptionFunction"] { + return (value, id, _, context) => `${type} ${prefix}${stringifyID( + id + )} defined in: +${[...value.keys()] + .map(packID => { + if (packID === undefined) { + return "Vanilla"; + } else { + const pack = + context.localData && context.localData.packs.get(packID); + if (pack) { + return pack.name; + } + return "Unknown"; + } + }) + .map(s => `- ${s}\n`)} +`; +} + +export function createTagMap(type: string): TagMap { + const tagMap: TagMap = new IDMap(resolveTags); + tagMap.suggestionDescriptionFunction = resourceDescriber(type, "#"); + return tagMap; +} + +export function createResourceMap(type: string): ResourcesMap { + const result: ResourcesMap = new IDMap(); + result.suggestionDescriptionFunction = resourceDescriber(type); + return result; +} + +export function createRegistryMap(): IdSet { + // Could add more information + return new IDMap(); +} diff --git a/src/data/nbt/nbt-cache.ts b/src/data/nbt/nbt-cache.ts index 9257e3e..dd4143e 100644 --- a/src/data/nbt/nbt-cache.ts +++ b/src/data/nbt/nbt-cache.ts @@ -1,29 +1,31 @@ import * as path from "path"; import { readFileAsync } from "../../misc-functions"; -import { WorldNBT } from "../types"; +import { WorldNBT } from "../../types"; import { Level, Scoreboard } from "./nbt-types"; import { parse } from "./parser"; -export async function loadNBT(worldLoc: string): Promise { - const nbt: WorldNBT = {} as WorldNBT; +export async function loadWorldNBT(worldLoc: string): Promise { + const nbt: WorldNBT = {}; - const levelpath = path.resolve(worldLoc, "./level.dat"); - try { - const levelbuf: Buffer = await readFileAsync(levelpath); - nbt.level = await parse(levelbuf); - } catch (e) { - // Level doesn't exist - } + await Promise.all([ + loadNBT(path.join(worldLoc, "./level.dat")).then( + level => (nbt.level = level) + ), + loadNBT(path.join(worldLoc, "./data/scoreboard.dat")).then( + scoreboard => (nbt.scoreboard = scoreboard) + ) + ]); + + return nbt; +} - const scpath = path.resolve(worldLoc, "./data/scoreboard.dat"); +async function loadNBT(loc: string): Promise { try { - const scoreboardbuf: Buffer = await readFileAsync(scpath); - nbt.scoreboard = await parse(scoreboardbuf); + const data: Buffer = await readFileAsync(loc); + return await parse(data); } catch (e) { - // Scoreboard file doesn't exist + return undefined; } - - return nbt; } diff --git a/src/data/noncached.ts b/src/data/noncached.ts index 5865f41..2daaab3 100644 --- a/src/data/noncached.ts +++ b/src/data/noncached.ts @@ -5,7 +5,9 @@ import { SchemaRequestService } from "vscode-json-languageservice"; -import { NBTDocs, NonCacheable } from "./types"; +import { CommandData, NBTDocs } from "../types"; + +import { CacheHandled } from "./cache"; export function loadNBTDocs(): NBTDocs { const nbtData = new Map(); @@ -15,7 +17,12 @@ export function loadNBTDocs(): NBTDocs { const textComponentSchema = "https://raw.githubusercontent.com/Levertion/minecraft-json-schema/master/java/shared/text_component.json"; -export async function loadNonCached(): Promise { +export type NotCached = Pick< + CommandData, + Exclude +>; + +export async function loadNonCached(): Promise { const schemas: { [key: string]: string } = { [textComponentSchema]: JSON.stringify( // FIXME: parcel breaks require.resolve so we need to use plain require to get the correct path diff --git a/src/data/types.ts b/src/data/types.ts deleted file mode 100644 index 5434ed8..0000000 --- a/src/data/types.ts +++ /dev/null @@ -1,197 +0,0 @@ -import { NBTNode, ValueList } from "mc-nbt-paths"; -import { LanguageService } from "vscode-json-languageservice"; - -import { Level, Scoreboard } from "./nbt/nbt-types"; - -/** - * Types for data - */ -//#region Namespace -export interface NamespacedName { - namespace?: string; - path: string; -} - -//#endregion -export type NBTDocs = Map; -export interface GlobalData { - blocks: BlocksPropertyInfo; - commands: CommandTree; - jsonService: LanguageService; - meta_info: { version: string }; - nbt_docs: NBTDocs; - registries: RegistriesData; - resources: Resources; -} - -export type Cacheable = Pick< - GlobalData, - "commands" | "blocks" | "registries" | "meta_info" | "resources" ->; - -export type RegistryNames = - | "minecraft:sound_event" - | "minecraft:fluid" - | "minecraft:mob_effect" - | "minecraft:block" - | "minecraft:enchantment" - | "minecraft:entity_type" - | "minecraft:item" - | "minecraft:potion" - | "minecraft:carver" - | "minecraft:surface_builder" - | "minecraft:feature" - | "minecraft:decorator" - | "minecraft:biome" - | "minecraft:particle_type" - | "minecraft:biome_source_type" - | "minecraft:block_entity_type" - | "minecraft:chunk_generator_type" - | "minecraft:dimension_type" - | "minecraft:motive" - | "minecraft:custom_stat" - | "minecraft:chunk_status" - | "minecraft:structure_feature" - | "minecraft:structure_piece" - | "minecraft:rule_test" - | "minecraft:structure_processor" - | "minecraft:structure_pool_element" - | "minecraft:menu" - | "minecraft:recipe_type" - | "minecraft:recipe_serializer" - | "minecraft:stat_type" - | "minecraft:villager_type" - | "minecraft:villager_profession"; - -export type RegistriesData = Record>; - -export interface WorldNBT { - level?: Level; - scoreboard?: Scoreboard; -} - -export type NonCacheable = Pick< - GlobalData, - Exclude ->; - -//#region Command Tree -/** - * A node with children. - */ -export interface MCNode { - children?: { [id: string]: T }; -} - -/** - * The root of the commands. - */ -export interface CommandTree extends MCNode { - type: "root"; -} - -/** - * The Path which describes the route taken to get to a node. - */ -export type CommandNodePath = string[]; - -/** - * A node in the command tree. - * See for the format - */ -export interface CommandNode extends MCNode { - executable?: boolean; - /** - * The parser for this node. Only Applicable if type of argument - */ - parser?: string; - properties?: Dictionary; - redirect?: CommandNodePath; - type: "literal" | "argument"; -} - -//#endregion Command Tree -//#region BlockInfo -/** - * Available blocks - */ -export interface BlocksPropertyInfo { - [blockID: string]: SingleBlockPropertyInfo; -} - -export interface SingleBlockPropertyInfo { - [property: string]: string[]; -} -//#endregion -//#region Items -/** - * The items which can be obtained. - * All have the `minecraft:` prefix in default setup. - */ -export type AvailableItems = string[]; -//#endregion -//#region Resources -export interface Datapack { - data: Resources; - id: DataPackID; - mcmeta?: McmetaFile; - name: string; -} - -export interface McmetaFile { - pack?: { description?: string; pack_format?: number }; -} - -export type DataPackID = number; - -export interface WorldInfo { - location: string; - nbt: WorldNBT; - packnamesmap: { [name: string]: DataPackID }; - packs: { [packID: number]: Datapack }; -} - -export interface LocalData extends WorldInfo { - current: DataPackID; -} - -export interface MinecraftResource extends NamespacedName { - /** - * Namespace will be defined for a MinecraftResource - */ - namespace: string; - /** - * The datapack this resource is related to. - * If undefined, must be the vanilla datapack - */ - pack?: DataPackID; -} - -export interface Resources { - advancements?: MinecraftResource[]; - block_tags?: MinecraftResource[]; - entity_tags?: MinecraftResource[]; - fluid_tags?: MinecraftResource[]; - function_tags?: MinecraftResource[]; - functions?: MinecraftResource[]; - item_tags?: MinecraftResource[]; - loot_tables?: MinecraftResource[]; - recipes?: MinecraftResource[]; - structures?: MinecraftResource[]; -} - -export interface DataResource extends MinecraftResource { - data?: T; -} - -//#region Resource file description -export interface Tag { - replace?: boolean; - values: string[]; -} - -export interface Advancement { - criteria: Dictionary; -} -//#endregion -//#endregion diff --git a/src/declaration.d.ts b/src/declaration.d.ts index c802b04..c63c3e6 100644 --- a/src/declaration.d.ts +++ b/src/declaration.d.ts @@ -9,6 +9,9 @@ declare namespace NodeJS { } /** * The settings which this server has been run with. + * + * @todo Pass this down the stack rather than storing globally. + * Each file should have different settings */ declare const mcLangSettings: McFunctionSettings; diff --git a/src/index.ts b/src/index.ts index 61c927b..1c883a9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -336,10 +336,8 @@ connection.onCompletion(params => { connection.onDefinition(prepare(definitionProvider, [])); // #connection.onDocumentHighlight(); // #connection.onDocumentSymbol(); // This is for sections - there are none in mcfunctions -connection.onWorkspaceSymbol(query => - getWorkspaceSymbols(manager, query.query) -); -connection.onHover(prepare(hoverProvider, undefined)); +connection.onWorkspaceSymbol(() => getWorkspaceSymbols(manager)); +connection.onHover(prepare(hoverProvider)); connection.onSignatureHelp(prepare(signatureHelpProvider)); function prepare( diff --git a/src/misc-functions/context.ts b/src/misc-functions/context.ts index 4d6b8ca..22d6655 100644 --- a/src/misc-functions/context.ts +++ b/src/misc-functions/context.ts @@ -1,4 +1,4 @@ -import { CommandNodePath } from "../data/types"; +import { CommandNodePath } from "../types"; export interface ContextPath { data: T; diff --git a/src/misc-functions/creators.ts b/src/misc-functions/creators.ts index 5dc1216..c28acb0 100644 --- a/src/misc-functions/creators.ts +++ b/src/misc-functions/creators.ts @@ -1,5 +1,11 @@ -import { CommandNode, CommandNodePath } from "../data/types"; -import { CommandContext, CommandData, CommandLine, ParserInfo } from "../types"; +import { + CommandContext, + CommandData, + CommandLine, + CommandNode, + CommandNodePath, + ParserInfo +} from "../types"; /** * Build parser info from the data required diff --git a/src/misc-functions/datapack-folder.ts b/src/misc-functions/datapack-folder.ts index a5aa9cf..90022a8 100644 --- a/src/misc-functions/datapack-folder.ts +++ b/src/misc-functions/datapack-folder.ts @@ -8,21 +8,18 @@ import { SLASHREPLACEREGEX, TAG_START } from "../consts"; + import { + ReturnSuccess, Advancement, - DataResource, - GlobalData, - MinecraftResource, - NamespacedName, + ID, Resources, Tag, WorldInfo -} from "../data/types"; -import { ReturnSuccess } from "../types"; +} from "../types"; import { getMatching, getResourcesSplit } from "./group-resources"; -import { convertToNamespace, stringifyNamespace } from "./namespace"; -import { stringArrayToNamespaces } from "./parsing/namespace"; +import { convertToID, stringArrayToIDs, stringifyID } from "./id"; import { readJSON, readJSONRaw } from "./promisified-fs"; import { ReturnHelper } from "./return-helper"; import { typed_keys } from "./third_party/typed-keys"; @@ -115,7 +112,7 @@ export const resourceTypes: { [T in keyof Resources]-?: ResourceInfo } = { return helper.succeed({ ...v, data: Object.keys(advancement.criteria) - } as DataResource); + } as DataID); } catch (e) { return helper.succeed(v); } @@ -145,10 +142,10 @@ export const resourceTypes: { [T in keyof Resources]-?: ResourceInfo } = { s => getMatching( // TODO: This is horrifically inefficient - stringArrayToNamespaces([ + stringArrayToIDs([ ...globalData.registries["minecraft:entity_type"] ]), - convertToNamespace(s) + convertToID(s) ).length > 0 ), path: ["tags", "entity_types"] @@ -163,10 +160,10 @@ export const resourceTypes: { [T in keyof Resources]-?: ResourceInfo } = { getResourcesSplit("fluid_tags", globalData, packsInfo), s => getMatching( - stringArrayToNamespaces([ + stringArrayToIDs([ ...globalData.registries["minecraft:entity_type"] ]), - convertToNamespace(s) + convertToID(s) ).length > 0 ), path: ["tags", "fluids"] @@ -184,7 +181,7 @@ export const resourceTypes: { [T in keyof Resources]-?: ResourceInfo } = { packroot, "function_tags", getResourcesSplit("function_tags", globalData, packsInfo), - s => getMatching(functions, convertToNamespace(s)).length > 0 + s => getMatching(functions, convertToID(s)).length > 0 ); }, path: ["tags", "functions"] @@ -209,7 +206,7 @@ export const resourceTypes: { [T in keyof Resources]-?: ResourceInfo } = { interface KindNamespace { kind: keyof Resources; - location: NamespacedName & { namespace: string }; + location: ID & { namespace: string }; } export function getKindAndNamespace( @@ -252,7 +249,7 @@ export function getKindAndNamespace( } export function getPath( - resource: MinecraftResource, + resource: ResourceID, packroot: string, kind: keyof Resources, path: PathModule = defaultPath @@ -269,7 +266,7 @@ export function getPath( } export function buildPath( - resource: MinecraftResource, + resource: ResourceID, packs: WorldInfo, kind: keyof Resources, path: PathModule = defaultPath @@ -278,7 +275,7 @@ export function buildPath( const pack = packs.packs[resource.pack]; return getPath( resource, - path.join(packs.location, pack.name), + path.join(packs.datapacksFolder, pack.name), kind, path ); @@ -288,12 +285,12 @@ export function buildPath( } async function readTag( - resource: MinecraftResource, + resource: ResourceID, packRoot: string, type: keyof Resources, - options: MinecraftResource[], + options: ResourceID[], isKnown: (value: string) => boolean -): Promise | MinecraftResource>> { +): Promise | ResourceID>> { const helper = new ReturnHelper(); const filePath = getPath(resource, packRoot, type); const tag = await readJSON(filePath); @@ -333,8 +330,8 @@ async function readTag( for (const v of tag.data.values) { if (v.startsWith(TAG_START)) { const tagText = v.slice(1); - const tagNamespace = convertToNamespace(tagText); - const tagName = stringifyNamespace(tagNamespace); + const tagNamespace = convertToID(tagText); + const tagName = stringifyID(tagNamespace); const tagString = `#${tagName}`; const result = getMatching(options, tagNamespace); if (result.length === 0) { @@ -345,8 +342,8 @@ async function readTag( } seen.add(tagString); } else { - const valueNamespace = convertToNamespace(v); - const value = stringifyNamespace(valueNamespace); + const valueNamespace = convertToID(v); + const value = stringifyID(valueNamespace); if (seen.has(value)) { duplicates.add(value); } diff --git a/src/misc-functions/group-resources.ts b/src/misc-functions/group-resources.ts deleted file mode 100644 index bd1f01f..0000000 --- a/src/misc-functions/group-resources.ts +++ /dev/null @@ -1,57 +0,0 @@ -import { - GlobalData, - MinecraftResource, - NamespacedName, - Resources, - WorldInfo -} from "../data/types"; -import { CommandData } from "../types"; - -import { namespacesEqual } from "./namespace"; - -export function getResourcesofType< - T extends MinecraftResource = MinecraftResource ->(resources: CommandData, type: keyof Resources): T[] { - return getResourcesSplit( - type, - resources.globalData, - resources.localData - ); -} - -export function getResourcesSplit< - T extends MinecraftResource = MinecraftResource ->(type: keyof Resources, globalData: GlobalData, packsInfo?: WorldInfo): T[] { - const results: MinecraftResource[] = []; - const globalResources = globalData.resources[type]; - if (!!globalResources) { - results.push(...globalResources); - } - if (packsInfo) { - for (const packId in packsInfo.packs) { - if (packsInfo.packs.hasOwnProperty(packId)) { - const pack = packsInfo.packs[packId]; - if (pack.data.hasOwnProperty(type)) { - const data = pack.data[type]; - if (!!data) { - results.push(...data); - } - } - } - } - } - return results as T[]; -} - -export function getMatching( - resources: T[], - value: T -): T[] { - const results: T[] = []; - for (const resource of resources) { - if (namespacesEqual(resource, value)) { - results.push(resource); - } - } - return results; -} diff --git a/src/misc-functions/id-map.ts b/src/misc-functions/id-map.ts new file mode 100644 index 0000000..0a81c53 --- /dev/null +++ b/src/misc-functions/id-map.ts @@ -0,0 +1,313 @@ +import { CompletionItemKind } from "vscode-languageserver"; + +import { StringReader } from "../brigadier/string-reader"; +import { DEFAULT_NAMESPACE, NAMESPACE } from "../consts"; +import { CE, ID, ReturnedInfo } from "../types"; + +import { readNamespaceSegment, ReturnHelper } from "."; +import { stringArrayToIDs, stringifyID } from "./id"; + +export type SerializedIDMap = Array<[string, Array<[string, T]>]>; + +export interface MaybeResolved { + raw: T; + resolved?: R; +} + +export interface Resolved extends MaybeResolved { + resolved: R; +} + +export interface NamespaceMapParseResult extends Resolved { + id: ID; +} + +export type IdMapGetType = R extends undefined ? T : Resolved; + +export class IDMap { + /** + * The function which should be used when creating a description for a suggestion + */ + public set suggestionDescriptionFunction( + v: undefined | ((value: T, id: ID, resolved: R, context: C) => string) + ) { + this._descriptionFunction = v; + } + + public static fromIDs(...ids: ID[]): IdSet { + const result = new IDMap(); + for (const id of ids) { + result.add(id); + } + return result; + } + + public static fromStringArray(strings: string[]): IdSet { + return IDMap.fromIDs(...stringArrayToIDs(strings)); + } + + private _descriptionFunction: + | undefined + | ((value: T, id: ID, resolved: R, context: C) => string); + + private readonly map: Map< + string, + Map> + > = new Map(); + + private readonly resolver: R extends undefined + ? undefined + : (input: T, map: IDMap) => R; + + public constructor( + ...resolver: R extends undefined ? [] : [IDMap["resolver"]] + ) { + this.resolver = resolver[0] as any; + } + + public *[Symbol.iterator](): IterableIterator<[ID, T]> { + for (const [namespace, map] of this.map) { + for (const [path, maybeResolved] of map) { + yield [{ namespace, path }, maybeResolved.raw]; + } + } + } + + public add( + id: ID, + // Hack to improve the ergonomics of IdSet + ...value: T extends undefined ? [] : [T] + ): void { + const innerMap = this.namespaceMapOrDefault(id.namespace); + innerMap.set(id.path, { raw: value[0] as T }); + } + + public addFrom(other: IDMap): void { + other.map.forEach((v, k) => { + const map = this.namespaceMapOrDefault(k); + v.forEach((value, key) => { + map.set(key, value); + }); + }); + } + + public addSerialized(serialised: SerializedIDMap): void; + public addSerialized( + serialised: SerializedIDMap, + mapfunction?: (val: A) => T + ): void; + public addSerialized( + serialised: SerializedIDMap, + mapfunction?: (val: A) => T + ): void { + for (const [namespace, map] of serialised) { + const namespaceMap = this.namespaceMapOrDefault(namespace); + for (const [path, raw] of map) { + namespaceMap.set(path, { + raw: mapfunction + ? mapfunction(raw) + : ((raw as unknown) as T) + }); + } + } + } + + public edit(id: ID): T | undefined { + const val = this.getRaw(id); + if (val) { + val.resolved = undefined; + return val.raw; + } + return undefined; + } + + public get(id: ID): IdMapGetType | undefined { + const val = this.getResolved(id); + if (val) { + if (typeof val.resolved === "undefined") { + return val as IdMapGetType; + } else { + return val.raw as IdMapGetType; + } + } + return undefined; + } + + public getUnresolved(id: ID): T | undefined { + const value = this.getRaw(id); + return value && value.raw; + } + + public has(id: ID): boolean { + const innerMap = this.namespaceMap(id.namespace); + return !!(innerMap && innerMap.has(id.path)); + } + + /** + * Parse an ID from this map. This will not add any errors, it is left to the caller + * to check that there is nothing following. + * + * @param namespaceCharacter The character to use as the namespace character + * + * @returns ReturnSuccess containing the wrapped T and the ID or ReturnFailure with the best effort parsed ID + */ + public parse( + reader: StringReader, + context: C, + namespaceCharacter: string = NAMESPACE + ): ReturnedInfo, CE, ID> { + const helper = new ReturnHelper(); + const start = reader.cursor; + const firstSegment = readNamespaceSegment(reader, namespaceCharacter); + if (reader.canRead() && reader.peek() === namespaceCharacter) { + reader.skip(); + const pathStart = reader.cursor; + const path = readNamespaceSegment(reader, namespaceCharacter); + const id: ID = { namespace: firstSegment, path }; + if (!reader.canRead()) { + const map = this.namespaceMap(firstSegment); + if (map) { + map.forEach((maybeResolved, key) => { + helper.addSuggestion( + pathStart, + key, + CompletionItemKind.EnumMember, + this._descriptionFunction + ? this._descriptionFunction( + maybeResolved.raw, + { + namespace: firstSegment, + path + }, + maybeResolved.resolved || + (this.resolver && + this.resolver( + maybeResolved.raw, + this + )), + context + ) + : undefined + ); + }); + } + } + const value = this.getResolved(id); + if (typeof value !== "undefined") { + return helper.succeed>({ + ...value, + id + }); + } + return helper.failWithData(id); + } else { + if (!reader.canRead()) { + helper.addSuggestion(reader.cursor, namespaceCharacter); + this.map.forEach((_, key) => { + helper.addSuggestion( + start, + key + namespaceCharacter, + CompletionItemKind.EnumMember + ); + }); + const defaultMap = this.namespaceMap(); + if (defaultMap) { + defaultMap.forEach((_, key) => { + const v = this.getResolved({ path: key }); + if (v) { + helper.addSuggestion( + start, + stringifyID({ path: key }, namespaceCharacter), + CompletionItemKind.Constant, + this._descriptionFunction && + this._descriptionFunction( + v.raw, + { + path: firstSegment + }, + v.resolved, + context + ) + ); + } + }); + } + } + const id: ID = { path: firstSegment }; + const value = this.getResolved(id); + if (typeof value !== "undefined") { + return helper.succeed>({ + ...value, + id + }); + } + return helper.failWithData(id); + } + } + + public remove(id: ID): boolean { + const map = this.namespaceMap(id.namespace); + if (map) { + const result = map.delete(id.path); + if (map.size === 0) { + this.map.delete(id.namespace || DEFAULT_NAMESPACE); + } + return result; + } + return false; + } + + public serialize(): SerializedIDMap; + public serialize( + mapfunction?: (val: T) => A | false + ): SerializedIDMap; + public serialize( + mapfunction?: (val: T) => A | false + ): SerializedIDMap { + return [...this.map].map(([namespace, inner]) => [ + namespace, + [...inner] + .map(v => [ + v[0], + mapfunction ? mapfunction(v[1].raw) : v[1].raw + ]) + .filter(v => v[1] !== false) + ]) as SerializedIDMap; + } + + private getRaw(id: ID): MaybeResolved | undefined { + const innerMap = this.namespaceMap(id.namespace); + return innerMap && innerMap.get(id.path); + } + + private getResolved(id: ID): Resolved | undefined { + const val = this.getRaw(id); + if (val) { + val.resolved = + val.resolved || (this.resolver && this.resolver(val.raw, this)); + return val as Resolved; + } + return undefined; + } + + private namespaceMap( + namespace: string = DEFAULT_NAMESPACE + ): Map> | undefined { + return this.map.get(namespace); + } + + private namespaceMapOrDefault( + namespace: string = DEFAULT_NAMESPACE + ): Map> { + const result = this.map.get(namespace); + if (typeof result !== "undefined") { + return result; + } + const newMap = new Map(); + this.map.set(namespace, newMap); + return newMap; + } +} + +// TODO: Make this its own class +export type IdSet = IDMap; diff --git a/src/misc-functions/namespace.ts b/src/misc-functions/id.ts similarity index 50% rename from src/misc-functions/namespace.ts rename to src/misc-functions/id.ts index 955b5c5..9616741 100644 --- a/src/misc-functions/namespace.ts +++ b/src/misc-functions/id.ts @@ -1,29 +1,23 @@ import { DEFAULT_NAMESPACE, NAMESPACE } from "../consts"; -import { NamespacedName } from "../data/types"; +import { ID } from "../types"; -export function namespacesEqual( - first: NamespacedName, - second: NamespacedName -): boolean { - return namesEqual(first, second) && first.path === second.path; +export function idsEqual(first: ID, second: ID): boolean { + return sameNamespace(first, second) && first.path === second.path; } -export function namesEqual( - first: NamespacedName, - second: NamespacedName -): boolean { +function sameNamespace(first: ID, second: ID): boolean { return ( first.namespace === second.namespace || (isNamespaceDefault(first) && isNamespaceDefault(second)) ); } -export function isNamespaceDefault(name: NamespacedName): boolean { +export function isNamespaceDefault(name: ID): boolean { return name.namespace === undefined || name.namespace === DEFAULT_NAMESPACE; } -export function stringifyNamespace( - namespace: NamespacedName, +export function stringifyID( + namespace: ID, seperator: string = NAMESPACE ): string { return ( @@ -34,23 +28,18 @@ export function stringifyNamespace( } /** - * Convert a string into a `NamespacedName`. This should only be called directly on strings which are known to be valid - * The behaviour on invalid strings is to leave the second seperator in the path + * Convert a string into an `ID`. This should only be called directly on valid, static strings. + * This does not do any error reporting */ -export function convertToNamespace( - input: string, - splitChar: string = NAMESPACE -): NamespacedName { +export function convertToID(input: string, splitChar: string = NAMESPACE): ID { const index = input.indexOf(splitChar); if (index >= 0) { const pathContents = input.substring( index + splitChar.length, input.length ); - // Path contents should not have a : in the contents, however this is to be checked higher up. + // Path contents should not have a splitChar in the contents, however this is unchecked // This simplifies using the parsed result when parsing known statics - - // Related: https://bugs.mojang.com/browse/MC-91245 (Fixed) if (index >= 1) { return { namespace: input.substring(0, index), path: pathContents }; } else { @@ -60,3 +49,9 @@ export function convertToNamespace( return { path: input }; } } + +// TODO: Prefer `IDMap.fromStringArray(strings)` in almost all cases +export function stringArrayToIDs(strings: string[]): ID[] { + // tslint:disable-next-line:no-unnecessary-callback-wrapper this is a false positive - see https://github.com/palantir/tslint/issues/2430 + return strings.map(v => convertToID(v)); +} diff --git a/src/misc-functions/index.ts b/src/misc-functions/index.ts index dda7b19..407c4c6 100644 --- a/src/misc-functions/index.ts +++ b/src/misc-functions/index.ts @@ -4,7 +4,7 @@ export * from "./datapack-folder"; export * from "./file-errors"; export * from "./group-resources"; export * from "./lsp-conversions"; -export * from "./namespace"; +export * from "./id"; export * from "./node-tree"; export * from "./promisified-fs"; export * from "./return-helper"; @@ -12,5 +12,5 @@ export * from "./security"; export * from "./setup"; export * from "./translation"; -export * from "./parsing/namespace"; +export * from "./parsing/id"; export * from "./parsing/nmsp-tag"; diff --git a/src/misc-functions/lsp-conversions.ts b/src/misc-functions/lsp-conversions.ts index 5b98d20..93748a3 100644 --- a/src/misc-functions/lsp-conversions.ts +++ b/src/misc-functions/lsp-conversions.ts @@ -38,11 +38,12 @@ export function runChanges( changes: DidChangeTextDocumentParams, functionInfo: FunctionInfo ): number[] { - const changed: number[] = []; + const changedLines: number[] = []; for (const change of changes.contentChanges) { - if (!!change.range) { + const range = change.range; + if (range) { // Appease the compiler, as the change interface seems to have range optional - const { start, end }: Range = change.range; + const { start, end }: Range = range; const newLineContent = functionInfo.lines[start.line].text .substring(0, start.character) .concat( @@ -52,12 +53,12 @@ export function runChanges( const difference = end.line - start.line + 1; const newLines = splitLines(newLineContent); functionInfo.lines.splice(start.line, difference, ...newLines); - changed.forEach((v, i) => { + changedLines.forEach((v, i) => { if (v > start.line) { - changed[i] = v - difference + newLines.length; + changedLines[i] = v - difference + newLines.length; } }); - changed.push( + changedLines.push( ...Array.from( new Array(newLines.length), (_, i) => start.line + i @@ -65,7 +66,7 @@ export function runChanges( ); } } - const unique = changed.filter( + const unique = changedLines.filter( (value, index, self) => self.indexOf(value) === index ); unique.sort((a, b) => a - b); diff --git a/src/misc-functions/node-tree.ts b/src/misc-functions/node-tree.ts index fec42d3..98a7eb5 100644 --- a/src/misc-functions/node-tree.ts +++ b/src/misc-functions/node-tree.ts @@ -1,9 +1,4 @@ -import { - CommandNode, - CommandNodePath, - CommandTree, - MCNode -} from "../data/types"; +import { CommandNode, CommandNodePath, CommandTree, MCNode } from "../types"; export function followPath>( tree: MCNode, diff --git a/src/misc-functions/parsing/namespace.ts b/src/misc-functions/parsing/id.ts similarity index 66% rename from src/misc-functions/parsing/namespace.ts rename to src/misc-functions/parsing/id.ts index 36c8ce3..20f3b1a 100644 --- a/src/misc-functions/parsing/namespace.ts +++ b/src/misc-functions/parsing/id.ts @@ -1,17 +1,11 @@ import { CompletionItemKind } from "vscode-languageserver/lib/main"; -import { - convertToNamespace, - namespacesEqual, - ReturnHelper, - stringifyNamespace -} from ".."; +import { convertToID, idsEqual, ReturnHelper, stringifyID } from ".."; import { CommandErrorBuilder } from "../../brigadier/errors"; import { StringReader } from "../../brigadier/string-reader"; import { NAMESPACE } from "../../consts"; -import { NamespacedName } from "../../data/types"; -import { CE, ReturnedInfo, ReturnSuccess, Suggestion } from "../../types"; -import { isNamespaceDefault, namesEqual } from "../namespace"; +import { CE, ID, ReturnedInfo, ReturnSuccess, Suggestion } from "../../types"; +import { stringArrayToIDs } from "../id"; const NAMESPACEEXCEPTIONS = { invalid_id: new CommandErrorBuilder( @@ -23,9 +17,16 @@ const NAMESPACEEXCEPTIONS = { export const namespaceChars = /^[0-9a-z_:/\.-]$/; const allowedInSections = /^[0-9a-z_/\.-]$/; -export function stringArrayToNamespaces(strings: string[]): NamespacedName[] { - // tslint:disable-next-line:no-unnecessary-callback-wrapper this is a false positive - see https://github.com/palantir/tslint/issues/2430 - return strings.map(v => convertToNamespace(v)); +export function readNamespaceSegment( + reader: StringReader, + seperator: string = NAMESPACE +): string { + return reader.readWhileFunction(c => { + if (c === seperator) { + return false; + } + return allowedInSections.test(c); + }); } export function readNamespaceText( @@ -46,54 +47,33 @@ export function readNamespaceText( }); } -/** - * Does `base`(eg minecraft:stone) start with `test` (e.g. sto) [Y] - */ -export function namespaceStart( - base: NamespacedName, - test: NamespacedName -): boolean { - if (test.namespace === undefined) { - return ( - (isNamespaceDefault(base) && base.path.startsWith(test.path)) || - (!!base.namespace && base.namespace.startsWith(test.path)) - ); - } else { - return namesEqual(base, test) && base.path.startsWith(test.path); - } -} - export function namespaceSuggestions( - options: NamespacedName[], - value: NamespacedName, + options: ID[], start: number ): Suggestion[] { const result: Suggestion[] = []; for (const option of options) { - if (namespaceStart(option, value)) { - result.push({ text: stringifyNamespace(option), start }); - } + result.push({ text: stringifyID(option), start }); } return result; } export function namespaceSuggestionString( options: string[], - value: NamespacedName, start: number ): Suggestion[] { - return namespaceSuggestions(stringArrayToNamespaces(options), value, start); + return namespaceSuggestions(stringArrayToIDs(options), start); } export function parseNamespace( reader: StringReader, seperator: string = NAMESPACE, stopAfterFirst: boolean = false -): ReturnedInfo { +): ReturnedInfo { const helper = new ReturnHelper(); const start = reader.cursor; const text = readNamespaceText(reader, seperator, stopAfterFirst); - const namespace = convertToNamespace(text, seperator); + const namespace = convertToID(text, seperator); let next = 0; let failed = false; // Give an error for each invalid character @@ -122,17 +102,17 @@ export function parseNamespace( } interface OptionResult { - literal: NamespacedName; + literal: ID; values: T[]; } -export function parseNamespaceOption( +export function parseNamespaceOption( reader: StringReader, options: T[], completionKind?: CompletionItemKind, seperator: string = NAMESPACE, stopAfterFirst: boolean = false -): ReturnedInfo, CE, NamespacedName | undefined> { +): ReturnedInfo, CE, ID | undefined> { const helper = new ReturnHelper(); const start = reader.cursor; const namespace = parseNamespace(reader, seperator, stopAfterFirst); @@ -159,8 +139,8 @@ export function parseNamespaceOption( } } -export function processParsedNamespaceOption( - namespace: NamespacedName, +export function processParsedNamespaceOption( + namespace: ID, options: T[], suggest: boolean, start: number, @@ -170,13 +150,13 @@ export function processParsedNamespaceOption( const results: T[] = []; const helper = new ReturnHelper(); for (const val of options) { - if (namespacesEqual(val, namespace)) { + if (idsEqual(val, namespace)) { results.push(val); } - if (suggest && namespaceStart(val, namespace)) { + if (suggest) { helper.addSuggestion( start, - stringifyNamespace(val, seperator), + stringifyID(val, seperator), completionKind ); } diff --git a/src/misc-functions/parsing/nmsp-tag.ts b/src/misc-functions/parsing/nmsp-tag.ts index 912db1e..aa2ee5a 100644 --- a/src/misc-functions/parsing/nmsp-tag.ts +++ b/src/misc-functions/parsing/nmsp-tag.ts @@ -2,33 +2,31 @@ import { CompletionItemKind } from "vscode-languageserver/lib/main"; import { buildPath, - convertToNamespace, + convertToID, getResourcesofType, - namespacesEqual, + idsEqual, + parseNamespace, + parseNamespaceOption, + readNamespaceText, ReturnHelper } from ".."; import { CommandErrorBuilder } from "../../brigadier/errors"; import { StringReader } from "../../brigadier/string-reader"; import { TAG_START } from "../../consts"; +import { DataID, ID, Resources, Tag, WorldInfo } from "../../data/types"; import { - DataResource, - NamespacedName, - Resources, - Tag, - WorldInfo -} from "../../data/types"; -import { CE, ParserInfo, ReturnedInfo, ReturnSuccess } from "../../types"; - -import { - parseNamespace, - parseNamespaceOption, - readNamespaceText -} from "./namespace"; + CE, + ParserInfo, + ReturnedInfo, + ReturnSuccess, + TagMap +} from "../../types"; +import { IDMap, NamespaceMapParseResult } from "../id-map"; export interface TagParseResult { - parsed: NamespacedName; - resolved?: NamespacedName[]; - values?: Array>; + parsed: ID; + resolved?: ID[]; + values?: Array>; } /** @@ -43,35 +41,18 @@ export interface TagParseResult { export function parseNamespaceOrTag( reader: StringReader, info: ParserInfo, - taghandling: keyof Resources | CommandErrorBuilder -): ReturnedInfo { + taghandling: TagMap | CommandErrorBuilder +): ReturnedInfo { const helper = new ReturnHelper(info); const start = reader.cursor; if (reader.peek() === TAG_START) { reader.skip(); - if (typeof taghandling === "string") { - const tags: Array> = getResourcesofType( - info.data, - taghandling - ); - const parsed = parseNamespaceOption( - reader, - tags, - CompletionItemKind.Folder - ); - if (helper.merge(parsed)) { - const values = parsed.data.values; - const resolved: NamespacedName[] = []; - for (const value of values) { - resolved.push(...getLowestForTag(value, tags)); - } - return helper.succeed({ - parsed: parsed.data.literal, - resolved, - values - }); - } else { - return helper.failWithData(parsed.data); + if (taghandling instanceof IDMap) { + const result = taghandling.parse(reader, info.data); + if (helper.merge(result)) { + const { id, raw, resolved } = result.data; + + return helper.succeed({ parsed: id }); } } else { readNamespaceText(reader); @@ -94,31 +75,8 @@ export function parseNamespaceOrTag( } } -function getLowestForTag( - tag: DataResource, - options: Array> -): NamespacedName[] { - if (!tag.data) { - return []; - } - const results: NamespacedName[] = []; - for (const tagMember of tag.data.values) { - if (tagMember[0] === TAG_START) { - const namespace = convertToNamespace(tagMember.substring(1)); - for (const option of options) { - if (namespacesEqual(namespace, option)) { - results.push(...getLowestForTag(option, options)); - } - } - } else { - results.push(convertToNamespace(tagMember)); - } - } - return results; -} - export function buildTagActions( - tags: Array>, + tags: Array>, low: number, high: number, type: keyof Resources, diff --git a/src/misc-functions/return-helper.ts b/src/misc-functions/return-helper.ts index e30d001..1e88636 100644 --- a/src/misc-functions/return-helper.ts +++ b/src/misc-functions/return-helper.ts @@ -129,14 +129,13 @@ export class ReturnHelper { kind?: Suggestion["kind"], description?: string ): this { - if (this.suggestMode === undefined || this.suggestMode) { - this.addSuggestions({ - description, - kind, - start, - text - }); - } + this.addSuggestions({ + description, + kind, + start, + text + }); + return this; } public addSuggestions(...suggestions: SuggestResult[]): this { @@ -197,13 +196,15 @@ export class ReturnHelper { return this; } - public return( - other: ReturnedInfo - ): ReturnedInfo { + public return< + T = undefined, + E = undefined, + R extends ReturnedInfo = ReturnedInfo + >(other: R): R { if (this.merge(other)) { - return this.succeed(other.data); + return this.succeed(other.data) as R; } else { - return this.failWithData(other.data); + return this.failWithData(other.data) as R; } } diff --git a/src/misc-functions/security.ts b/src/misc-functions/security.ts index bcde6e7..5aec988 100644 --- a/src/misc-functions/security.ts +++ b/src/misc-functions/security.ts @@ -5,6 +5,8 @@ import { WorkspaceSecurity } from "../types"; /** * Check if the given change requires security confirmation + * + * @todo - stop caring about this security - it's safe so long as the dangerous settings are set globally */ export function securityIssues( settings: McFunctionSettings, diff --git a/src/misc-functions/setup.ts b/src/misc-functions/setup.ts index 68f8c0c..441decd 100644 --- a/src/misc-functions/setup.ts +++ b/src/misc-functions/setup.ts @@ -4,7 +4,7 @@ export function setup_logging(connection: IConnection): void { const log = (message: string) => { connection.console.log(message); }; - // tslint:disable-next-line:prefer-object-spread + // tslint:disable-next-line:prefer-object-spread - Object spread does not work on functions global.mcLangLog = Object.assign(log, { internal: (m: string) => { if (mcLangSettings.trace.internalLogging) { diff --git a/src/misc-functions/third_party/child-dir.ts b/src/misc-functions/third_party/child-dir.ts deleted file mode 100644 index e614166..0000000 --- a/src/misc-functions/third_party/child-dir.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { sep } from "path"; - -/** - * Calculates if a path is inside another path. Taken from: - * - * https://stackoverflow.com/a/42355848/8728461 - * @param child The child path - * @param parent The parent path. - */ -export function isChildOf( - child: string, - parent: string, - seperator: string = sep -): boolean { - if (child === parent) { - return false; - } - const parentTokens = parent.split(seperator).filter(i => i.length); // Has same effect as len>0 - const splitChild = child.split(seperator).filter(i => i.length); - return parentTokens.every((t, i) => splitChild[i] === t); -} diff --git a/src/misc-functions/third_party/merge-deep.ts b/src/misc-functions/third_party/merge-deep.ts index 8ac8d27..5dd7e29 100644 --- a/src/misc-functions/third_party/merge-deep.ts +++ b/src/misc-functions/third_party/merge-deep.ts @@ -18,6 +18,8 @@ export function isObject(item: any): item is AnyDict { /** * Deep merge two objects. + * + * @todo: Stop using this and just use workspace/configuration request */ export function mergeDeep(target: AnyDict, ...sources: AnyDict[]): AnyDict { if (!sources.length) { diff --git a/src/misc-functions/translation.ts b/src/misc-functions/translation.ts index 56c8c22..82f0219 100644 --- a/src/misc-functions/translation.ts +++ b/src/misc-functions/translation.ts @@ -4,6 +4,11 @@ export function shouldTranslate(): boolean { return mcLangSettings.translation.lang.toLowerCase() !== "en-us"; } +/** + * @todo - make this work with translations + * We need to handle translating both the vanilla translations and the errors + * not supported in vanilla. + */ export function MCFormat(base: string, ...substitutions: string[]): string { return vsprintf(base, substitutions); } diff --git a/src/parse.ts b/src/parse.ts index 0185282..bd74e3d 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -241,7 +241,7 @@ export function parseLines( current: packsInfo.packnamesmap[document.pack_segments.pack] }; } - const result = parseCommand(line.text, data.globalData, localData); + const result = parseCommand(line.text, data.commandData, localData); line.parseInfo = result ? result : false; line.actions = undefined; line.nodes = undefined; diff --git a/src/parsers/literal.ts b/src/parsers/literal.ts index a105e75..f2efeb0 100644 --- a/src/parsers/literal.ts +++ b/src/parsers/literal.ts @@ -9,20 +9,20 @@ export const literalParser: Parser = { const helper = new ReturnHelper(properties); const begin = reader.cursor; const literal = properties.path[properties.path.length - 1]; - if ( - properties.suggesting && - literal.startsWith(reader.getRemaining()) - ) { - helper.addSuggestions(literal); - } if (reader.canRead(literal.length)) { const end = begin + literal.length; if (reader.string.substring(begin, end) === literal) { reader.cursor = end; - if (reader.peek() === " " || !reader.canRead()) { + if (reader.peek() === " ") { + return helper.succeed(); + } + if (!reader.canRead()) { + helper.addSuggestion(begin, literal); return helper.succeed(); } } + } else { + helper.addSuggestion(begin, literal); } return helper.fail(); } diff --git a/src/parsers/minecraft/block.ts b/src/parsers/minecraft/block.ts index bb5d4d7..67f8169 100644 --- a/src/parsers/minecraft/block.ts +++ b/src/parsers/minecraft/block.ts @@ -1,25 +1,10 @@ -import { - CompletionItemKind, - DiagnosticSeverity -} from "vscode-languageserver/lib/main"; +import { CompletionItemKind } from "vscode-languageserver/lib/main"; import { CommandErrorBuilder } from "../../brigadier/errors"; import { StringReader } from "../../brigadier/string-reader"; -import { - BlocksPropertyInfo, - NamespacedName, - SingleBlockPropertyInfo -} from "../../data/types"; -import { - namespaceSuggestionString, - ReturnHelper, - stringifyNamespace -} from "../../misc-functions"; -import { - buildTagActions, - parseNamespaceOrTag -} from "../../misc-functions/parsing/nmsp-tag"; -import { Parser, ParserInfo, ReturnedInfo } from "../../types"; +import { TAG_START } from "../../consts"; +import { ReturnHelper, stringifyID } from "../../misc-functions"; +import { ID, Parser, ParserInfo, ReturnedInfo } from "../../types"; import { validateParse } from "./nbt/nbt"; @@ -33,9 +18,9 @@ export const stateParser: Parser = { interface PropertyExceptions { duplicate: CommandErrorBuilder; - invalid: CommandErrorBuilder; - novalue: CommandErrorBuilder; - unknown: CommandErrorBuilder; + invalid?: CommandErrorBuilder; + novalue?: CommandErrorBuilder; + unknown?: CommandErrorBuilder; } const exceptions = { @@ -86,21 +71,7 @@ const exceptions = { unknown_properties: { duplicate: new CommandErrorBuilder( "argument.unknown_block_tag.property.duplicate", - "Property '%s' can only be set once for unknown block tag %s" - ), - invalid: new CommandErrorBuilder( - "argument.unknown_block_tag.property.invalid", - "Unknown block tag %s might not accept '%s' for %s property", - DiagnosticSeverity.Warning - ), - novalue: new CommandErrorBuilder( - "argument.unknown_block_tag.property.novalue", - "Expected value for property '%s' on unknown block tag %s" - ), - unknown: new CommandErrorBuilder( - "argument.unknown_block_tag.property.unknown", - "Unknown block tag %s might not have property '%s'", - DiagnosticSeverity.Warning + "Property '%s' can only be set once for unrecognised block tag '%s'" ) }, @@ -123,130 +94,113 @@ export function parseBlockArgument( ): ReturnedInfo { const helper = new ReturnHelper(info); const start = reader.cursor; - const tagHandling = tags ? "block_tags" : exceptions.no_tags; - const parsed = parseNamespaceOrTag(reader, info, tagHandling); - let stringifiedName: string | undefined; - if (helper.merge(parsed)) { - const parsedResult = parsed.data; - if (parsedResult.resolved && parsedResult.values) { - stringifiedName = `#${stringifyNamespace(parsedResult.parsed)}`; - helper.merge( - buildTagActions( - parsedResult.values, - start + 1, - reader.cursor, - "block_tags", - info.data.localData - ) - ); - const props = constructProperties( - parsedResult.resolved, - info.data.globalData.blocks - ); - const propsResult = parseProperties( + const properties: Map> = new Map(); + const ids: ID[] = []; + let parseBlocks = true; + let knownTag = false; + // Always reassigned - used for tag error reporting + let fullName = ""; + if (reader.peek() === TAG_START) { + parseBlocks = false; + reader.skip(); + if (!tags) { + helper.addErrors(exceptions.no_tags.create(start, reader.cursor)); + // Error case - we continue parsing as if the # wasn't there + parseBlocks = true; + } else { + const result = info.data.resources.block_tags.parse( reader, - props || {}, - exceptions.tag_properties, - stringifiedName + info.data ); - if (!helper.merge(propsResult)) { - return helper.fail(); - } - if (reader.peek() === "{") { - const nbt = validateParse(reader, info, { - // tslint:disable-next-line:no-unnecessary-callback-wrapper - ids: (parsedResult.resolved || []).map(v => - stringifyNamespace(v) - ), - kind: "block" - }); - if (!helper.merge(nbt)) { - return helper.fail(); + if (helper.merge(result)) { + knownTag = true; + fullName = `#${stringifyID(result.data.id)}`; + for (const [id] of result.data.resolved.finals) { + ids.push(id); + const block = info.data.blocks.get(id); + if (block) { + mergeProps(properties, block); + } } } else { - helper.addSuggestion(reader.cursor, "{"); - } - } else { - stringifiedName = stringifyNamespace(parsed.data.parsed); - if (info.suggesting && !reader.canRead()) { - helper.addSuggestions( - ...namespaceSuggestionString( - Object.keys(info.data.globalData.blocks), - parsed.data.parsed, - start - ) - ); - } - const props = info.data.globalData.blocks[stringifiedName]; - if (!props) { + fullName = `#${stringifyID(result.data)}`; helper.addErrors( - exceptions.invalid_block.create(start, reader.cursor) + exceptions.unknown_tag.create( + start, + reader.cursor, + fullName + ) ); } - const result = parseProperties( - reader, - props || {}, - exceptions.block_properties, - stringifiedName - ); - if (!helper.merge(result)) { - return helper.fail(); - } - if (reader.peek() === "{") { - const nbt = validateParse(reader, info, { - ids: props ? stringifiedName : "none", - kind: "block" - }); - if (!helper.merge(nbt)) { - return helper.fail(); - } - } else { - helper.addSuggestion(reader.cursor, "{"); - } } - } else { - if (parsed.data) { - helper.addErrors( - exceptions.unknown_tag.create( - start, - reader.cursor, - stringifyNamespace(parsed.data) - ) - ); - stringifiedName = `#${stringifyNamespace(parsed.data)}`; - const propsResult = parseProperties( - reader, - {}, - exceptions.unknown_properties, - stringifiedName - ); - if (!helper.merge(propsResult)) { - return helper.fail(); - } - if (reader.peek() === "{") { - const nbt = validateParse(reader, info, { - ids: "none", - kind: "block" - }); - if (!helper.merge(nbt)) { - return helper.fail(); - } - } else { - helper.addSuggestion(reader.cursor, "{"); + } + if (parseBlocks) { + const result = info.data.blocks.parse(reader, undefined); + if (helper.merge(result)) { + fullName = stringifyID(result.data.id); + for (const [key, value] of result.data.raw) { + // Properties is never otherwise edited, so this does not lose data + properties.set(key, value); } + ids.push(result.data.id); } else { - // Parsing of the namespace failed + fullName = stringifyID(result.data); + helper.addErrors( + exceptions.invalid_block.create(start, reader.cursor, fullName) + ); + } + } + + const propsResult = parseProperties( + reader, + properties, + parseBlocks + ? exceptions.block_properties + : knownTag + ? exceptions.tag_properties + : exceptions.unknown_properties, + fullName + ); + if (!helper.merge(propsResult)) { + return helper.fail(); + } + + if (reader.peek() === "{") { + const nbt = validateParse(reader, info, { + // tslint:disable-next-line:no-unnecessary-callback-wrapper + ids: ids.map(v => stringifyID(v)), + kind: "block" + }); + if (!helper.merge(nbt)) { return helper.fail(); } + } else { + helper.addSuggestion(reader.cursor, "{"); } return helper.succeed(); } +function mergeProps( + current: Map>, + additions: Map> +): void { + for (const [key, value] of additions) { + const set = current.get(key); + if (typeof set === "undefined") { + current.set(key, value); + } else { + for (const propValue of value) { + set.add(propValue); + } + } + } +} + // Ugly call signature. Need to see how upstream handles tag properties. // At the moment, it is very broken function parseProperties( reader: StringReader, - options: SingleBlockPropertyInfo, + options: Map>, errors: PropertyExceptions, name: string ): ReturnedInfo> { @@ -254,7 +208,7 @@ function parseProperties( const result = new Map(); const start = reader.cursor; if (helper.merge(reader.expect("["), { errors: false })) { - const props = Object.keys(options); + const props = [...options.keys()]; reader.skipWhitespace(); if (helper.merge(reader.expectOption("]"), { errors: false })) { return helper.succeed(result); @@ -280,7 +234,7 @@ function parseProperties( exceptions.unclosed_props.create(start, reader.cursor) ); } - if (!propSuccessful) { + if (!propSuccessful && errors.unknown) { helper.addErrors( errors.unknown.create( propStart, @@ -304,18 +258,19 @@ function parseProperties( reader.skipWhitespace(); if (!helper.merge(reader.expect("="), { errors: false })) { return helper.fail( - errors.novalue.create( - propStart, - reader.cursor, - propKey, - name - ) + errors.novalue && + errors.novalue.create( + propStart, + reader.cursor, + propKey, + name + ) ); } reader.skipWhitespace(); const valueStart = reader.cursor; const valueParse = reader.readOption( - options[propKey] || [], + [...(options.get(propKey) || [])], undefined, CompletionItemKind.EnumMember ); @@ -324,16 +279,20 @@ function parseProperties( if (value === undefined) { return helper.fail(); } - const error = errors.invalid.create( - valueStart, - reader.cursor, - name, - value, - propKey - ); + const error = + (errors.invalid && [ + errors.invalid.create( + valueStart, + reader.cursor, + name, + value, + propKey + ) + ]) || + []; const adderrorIf = (b: boolean) => b && propSuccessful && !valueSuccessful - ? helper.addErrors(error) + ? helper.addErrors(...error) : undefined; adderrorIf(value.length > 0); result.set(propKey, value); @@ -353,24 +312,3 @@ function parseProperties( } return helper.succeed(result); } - -function constructProperties( - options: NamespacedName[], - blocks: BlocksPropertyInfo -): SingleBlockPropertyInfo { - const result: SingleBlockPropertyInfo = {}; - for (const blockName of options) { - const stringified = stringifyNamespace(blockName); - const block = blocks[stringified]; - if (block) { - for (const prop in block) { - if (block.hasOwnProperty(prop)) { - result[prop] = Array.from( - new Set((result[prop] || []).concat(block[prop])) - ); - } - } - } - } - return result; -} diff --git a/src/parsers/minecraft/component.ts b/src/parsers/minecraft/component.ts index 3e5ab3c..eac1dd8 100644 --- a/src/parsers/minecraft/component.ts +++ b/src/parsers/minecraft/component.ts @@ -20,7 +20,7 @@ export const jsonParser: Parser = { uri: "file://text-component.json", version: 0 }; - const service = info.data.globalData.jsonService; + const service = info.data.jsonService; // tslint:disable-next-line:no-inferred-empty-object-type const json = service.parseJSONDocument(text); service.doValidation(text, json).then(diagnostics => { diff --git a/src/parsers/minecraft/entity.ts b/src/parsers/minecraft/entity.ts index aa2f79f..20f8635 100644 --- a/src/parsers/minecraft/entity.ts +++ b/src/parsers/minecraft/entity.ts @@ -4,21 +4,25 @@ import { CommandErrorBuilder } from "../../brigadier/errors"; import { StringReader } from "../../brigadier/string-reader"; import { NONWHITESPACE } from "../../consts"; import { Scoreboard } from "../../data/nbt/nbt-types"; -import { DataResource, NamespacedName } from "../../data/types"; import { - convertToNamespace, - getResourcesofType, + convertToID, getReturned, parseNamespaceOption, parseNamespaceOrTag, processParsedNamespaceOption, ReturnHelper, stringArrayEqual, - stringArrayToNamespaces, - stringifyNamespace + stringArrayToIDs, + stringifyID } from "../../misc-functions"; import { typed_keys } from "../../misc-functions/third_party/typed-keys"; -import { ContextChange, Parser, ParserInfo, ReturnedInfo } from "../../types"; +import { + Advancement, + ContextChange, + Parser, + ParserInfo, + ReturnedInfo +} from "../../types"; import { summonError } from "./namespace-list"; import { validateParse } from "./nbt/nbt"; @@ -370,7 +374,6 @@ export function parseAdvancements( reader: StringReader, info: ParserInfo ): ReturnedInfo> { - const advancements = getResourcesofType(info.data, "advancements"); const helper = new ReturnHelper(); if (!helper.merge(reader.expect("{"))) { return helper.fail(); @@ -379,22 +382,15 @@ export function parseAdvancements( while (true) { let advname: string; const criteriaOptions: string[] = []; - const res = parseNamespaceOption>( - reader, - advancements - ); + const res = info.data.resources.advancements.parse(reader, info.data); if (!helper.merge(res)) { - if (!res.data) { - return helper.fail(); - } else { - advname = stringifyNamespace(res.data); - } + advname = stringifyID(res.data); } else { - advname = stringifyNamespace(res.data.literal); - res.data.values - .map(v => v.data) - .filter(v => !!v) - .forEach(v => criteriaOptions.push(...(v as string[]))); + advname = stringifyID(res.data.id); + [...res.data.raw] + .map(v => v[1]) + .filter((v): v is Advancement => !!v) + .forEach(v => criteriaOptions.push(...v.criteria)); } if (!helper.merge(reader.expect("="))) { return helper.fail(); @@ -753,6 +749,21 @@ export const argParsers: { [K in ArgumentType]: OptionParser } = { const helper = new ReturnHelper(); const start = reader.cursor; const negated = isNegated(reader, helper); + if (reader.peek() === "#") { + reader.skip(); + const tagResult = info.data.resources.entity_tags.parse( + reader, + info.data + ); + if (helper.merge(tagResult)) { + tagResult.data.resolved.resolved; + } + } else { + const notTagResult = info.data.registries[ + "minecraft:entity_type" + ].parse(reader, undefined); + notTagResult; + } const parsedType = parseNamespaceOrTag(reader, info, "entity_tags"); if (!helper.merge(parsedType)) { if (parsedType.data) { @@ -760,7 +771,7 @@ export const argParsers: { [K in ArgumentType]: OptionParser } = { errors.unknown_tag.create( start, reader.cursor, - stringifyNamespace(parsedType.data) + stringifyID(parsedType.data) ) ); return helper.succeed(); @@ -770,7 +781,7 @@ export const argParsers: { [K in ArgumentType]: OptionParser } = { if (!parsedType.data.resolved) { const postProcess = processParsedNamespaceOption( parsedType.data.parsed, - stringArrayToNamespaces([ + stringArrayToIDs([ ...info.data.globalData.registries["minecraft:entity_type"] ]), info.suggesting && !reader.canRead(), @@ -783,7 +794,7 @@ export const argParsers: { [K in ArgumentType]: OptionParser } = { summonError.create( start, reader.cursor, - stringifyNamespace(parsedType.data.parsed) + stringifyID(parsedType.data.parsed) ) ); } @@ -794,7 +805,7 @@ export const argParsers: { [K in ArgumentType]: OptionParser } = { const typeInfo = context.type || { set: new Set(), unset: new Set() }; const { set, unset } = typeInfo; // tslint:disable-next-line:no-unnecessary-callback-wrapper - const stringifiedTypes = parsedTypes.map(v => stringifyNamespace(v)); + const stringifiedTypes = parsedTypes.map(v => stringifyID(v)); if (!negated) { if (stringifiedTypes.every(set.has.bind(set))) { helper.addErrors( @@ -876,7 +887,7 @@ export class EntityBase implements Parser { set: new Set( // tslint:disable-next-line:no-unnecessary-callback-wrapper ((info.context.executor || {}).ids || []).map(v => - stringifyNamespace(v) + stringifyID(v) ) ), unset: blankSet @@ -1021,10 +1032,10 @@ function getContextChange( path: string[] ): ContextChange | undefined { if (context.type) { - const result: NamespacedName[] = []; + const result: ID[] = []; for (const item of context.type.set.values()) { if (!context.type.unset.has(item)) { - result.push(convertToNamespace(item)); + result.push(convertToID(item)); } } if (stringArrayEqual(path, ["execute", "as", "entity"])) { diff --git a/src/parsers/minecraft/item.ts b/src/parsers/minecraft/item.ts index 678bd05..f95e60c 100644 --- a/src/parsers/minecraft/item.ts +++ b/src/parsers/minecraft/item.ts @@ -5,7 +5,7 @@ import { namespaceSuggestionString, parseNamespaceOrTag, ReturnHelper, - stringifyNamespace + stringifyID } from "../../misc-functions"; import { Parser, ParserInfo, ReturnedInfo } from "../../types"; @@ -68,12 +68,11 @@ export class ItemParser implements Parser { "minecraft:item" ] ], - parsed.data.parsed, start ) ); } - const name = stringifyNamespace(parsed.data.parsed); + const name = stringifyID(parsed.data.parsed); if ( !properties.data.globalData.registries[ "minecraft:item" @@ -100,7 +99,7 @@ export class ItemParser implements Parser { UNKNOWNTAG.create( start, reader.cursor, - stringifyNamespace(parsed.data) + stringifyID(parsed.data) ) ); if (reader.peek() === "{") { diff --git a/src/parsers/minecraft/namespace-list.ts b/src/parsers/minecraft/namespace-list.ts index 51a8e44..23efceb 100644 --- a/src/parsers/minecraft/namespace-list.ts +++ b/src/parsers/minecraft/namespace-list.ts @@ -1,25 +1,25 @@ import { CommandErrorBuilder } from "../../brigadier/errors"; import { StringReader } from "../../brigadier/string-reader"; -import { NamespacedName, RegistryNames } from "../../data/types"; +import { ID, RegistryNames } from "../../data/types"; import { parseNamespaceOption, ReturnHelper, - stringArrayToNamespaces, - stringifyNamespace + stringArrayToIDs, + stringifyID } from "../../misc-functions"; import { CommandContext, Parser, ParserInfo, ReturnedInfo } from "../../types"; -export class NamespaceListParser implements Parser { +export class RegistryListParser implements Parser { private readonly error: CommandErrorBuilder; private readonly registryType: RegistryNames; private readonly resultFunction?: ( context: CommandContext, - result: NamespacedName[] + result: ID[] ) => void; public constructor( registryType: RegistryNames, errorBuilder: CommandErrorBuilder, - context?: NamespaceListParser["resultFunction"] + context?: RegistryListParser["resultFunction"] ) { this.registryType = registryType; this.error = errorBuilder; @@ -33,7 +33,7 @@ export class NamespaceListParser implements Parser { const start = reader.cursor; const result = parseNamespaceOption( reader, - stringArrayToNamespaces([ + stringArrayToIDs([ ...info.data.globalData.registries[this.registryType] ]) ); @@ -51,7 +51,7 @@ export class NamespaceListParser implements Parser { this.error.create( start, reader.cursor, - stringifyNamespace(result.data) + stringifyID(result.data) ) ) .succeed(); @@ -66,7 +66,7 @@ export const summonError = new CommandErrorBuilder( "entity.notFound", "Unknown entity: %s" ); -export const summonParser = new NamespaceListParser( +export const summonParser = new RegistryListParser( "minecraft:entity_type", summonError, (context, ids) => (context.otherEntity = { ids }) @@ -76,7 +76,7 @@ const enchantmentError = new CommandErrorBuilder( "enchantment.unknown", "Unknown enchantment: %s" ); -export const enchantmentParser = new NamespaceListParser( +export const enchantmentParser = new RegistryListParser( "minecraft:enchantment", enchantmentError ); @@ -85,7 +85,7 @@ const mobEffectError = new CommandErrorBuilder( "effect.effectNotFound", "Unknown effect: %s" ); -export const mobEffectParser = new NamespaceListParser( +export const mobEffectParser = new RegistryListParser( "minecraft:mob_effect", mobEffectError ); @@ -94,7 +94,7 @@ const particleError = new CommandErrorBuilder( "particle.notFound", "Unknown particle: %s" ); -export const particleParser = new NamespaceListParser( +export const particleParser = new RegistryListParser( "minecraft:particle_type", particleError ); @@ -104,7 +104,7 @@ const dimensionError = new CommandErrorBuilder( "Unknown dimension: '%s'" ); -export const dimensionParser = new NamespaceListParser( +export const dimensionParser = new RegistryListParser( "minecraft:dimension_type", dimensionError ); diff --git a/src/parsers/minecraft/nbt-path.ts b/src/parsers/minecraft/nbt-path.ts index beac57a..718b8d0 100644 --- a/src/parsers/minecraft/nbt-path.ts +++ b/src/parsers/minecraft/nbt-path.ts @@ -9,7 +9,7 @@ import { ContextPath, ReturnHelper, startPaths, - stringifyNamespace + stringifyID } from "../../misc-functions"; import { CommandContext, @@ -75,7 +75,7 @@ function entityDataPath( (c.otherEntity && c.otherEntity.ids && // tslint:disable-next-line:no-unnecessary-callback-wrapper - c.otherEntity.ids.map(v => stringifyNamespace(v))) || + c.otherEntity.ids.map(v => stringifyID(v))) || "none", kind: "entity" }, diff --git a/src/parsers/minecraft/nbt/nbt.ts b/src/parsers/minecraft/nbt/nbt.ts index 766bbde..ce97f41 100644 --- a/src/parsers/minecraft/nbt/nbt.ts +++ b/src/parsers/minecraft/nbt/nbt.ts @@ -1,6 +1,6 @@ import { CommandError } from "../../../brigadier/errors"; import { StringReader } from "../../../brigadier/string-reader"; -import { ReturnHelper, stringifyNamespace } from "../../../misc-functions"; +import { ReturnHelper, stringifyID } from "../../../misc-functions"; import { ContextPath, startPaths } from "../../../misc-functions/context"; import { CommandContext, @@ -44,7 +44,7 @@ const paths: Array> = [ (args.otherEntity && args.otherEntity.ids && // tslint:disable-next-line:no-unnecessary-callback-wrapper - args.otherEntity.ids.map(v => stringifyNamespace(v))) || + args.otherEntity.ids.map(v => stringifyID(v))) || [], kind: "entity" }), diff --git a/src/parsers/minecraft/nbt/tag/compound-tag.ts b/src/parsers/minecraft/nbt/tag/compound-tag.ts index 073e82e..4b5aaa5 100644 --- a/src/parsers/minecraft/nbt/tag/compound-tag.ts +++ b/src/parsers/minecraft/nbt/tag/compound-tag.ts @@ -167,20 +167,17 @@ export class NBTTagCompound extends NBTTag { const key = part.key || ""; if (part.closeIdx === undefined) { for (const childName of Object.keys(children)) { - if (childName.startsWith(key)) { - const thisChild = children[childName]; - const text = - thisChild && getStartSuggestion(thisChild.node); - keyHelper.addSuggestions({ - description: getHoverText(children[childName].node), - kind: CompletionItemKind.Field, - label: childName, - start: part.keyRange.start, - text: - quoteIfNeeded(childName) + - (text ? `: ${text}` : "") - }); - } + const thisChild = children[childName]; + const text = + thisChild && getStartSuggestion(thisChild.node); + keyHelper.addSuggestions({ + description: getHoverText(children[childName].node), + kind: CompletionItemKind.Field, + label: childName, + start: part.keyRange.start, + text: + quoteIfNeeded(childName) + (text ? `: ${text}` : "") + }); } } const child = children[key]; diff --git a/src/parsers/minecraft/nbt/tag/typed-list-tag.ts b/src/parsers/minecraft/nbt/tag/typed-list-tag.ts index 234951a..d6dbb2e 100644 --- a/src/parsers/minecraft/nbt/tag/typed-list-tag.ts +++ b/src/parsers/minecraft/nbt/tag/typed-list-tag.ts @@ -48,9 +48,7 @@ export class NBTTagTypedList extends BaseList { ); const toCheck = `[${type["0"]};`; if (this.remaining) { - if (toCheck.startsWith(this.remaining)) { - helper.addSuggestion(this.startIndex, toCheck); - } + helper.addSuggestion(this.startIndex, toCheck); } } return helper.succeed(); diff --git a/src/parsers/minecraft/nbt/walker.ts b/src/parsers/minecraft/nbt/walker.ts index 178d570..48501a9 100644 --- a/src/parsers/minecraft/nbt/walker.ts +++ b/src/parsers/minecraft/nbt/walker.ts @@ -250,368 +250,3 @@ export class NBTWalker { return undefined; } } - -// Old version -// #interface ContextData< -// # N extends NBTNode = NBTNode, -// # T extends NBTTag = NBTTag -// #> { -// # readonly finalValidation: boolean; -// # readonly node: N; -// # readonly path: string; -// # readonly reader: ArrayReader; -// # readonly tag?: T; -// # readonly useReferences: boolean; -// #} -// # -// #// tslint:disable:cyclomatic-complexity -// #// tslint:disable-next-line:max-classes-per-file -// #export class NBTValidator { -// # private readonly docs: NBTDocs; -// # private readonly extraChildren: boolean; -// # private readonly parsed: NBTTag; -// # private readonly root: string; -// # private readonly validateNBT: boolean; -// # -// # public constructor( -// # parsed: NBTTag, -// # docs: NBTDocs, -// # extraChild: boolean, -// # nbtvalidation: boolean = true, -// # root: string = "root.json" -// # ) { -// # this.docs = docs; -// # this.parsed = parsed; -// # this.extraChildren = extraChild; -// # this.root = root; -// # this.validateNBT = nbtvalidation; -// # } -// # -// # public walkThenValidate(nbtpath: string[]): ReturnedInfo { -// # const node = this.docs.get(this.root) as RootNode; -// # const reader = new ArrayReader(nbtpath); -// # // tslint:disable-next-line:helper-return -// # return this.walkNextNode({ -// # finalValidation: true, -// # node, -// # path: this.root, -// # reader, -// # tag: this.validateNBT ? this.parsed : undefined, -// # useReferences: false -// # }); -// # } -// # -// # private mergeChildRef(data: ContextData): CompoundNode { -// # const { node, path: currentPath } = data; -// # if (!node.child_ref) { -// # return node; -// # } -// # const helper = new ReturnHelper(); -// # const newChildren = JSON.parse( -// # JSON.stringify(node.children || {}) -// # ) as Exclude; -// # for (const ref of node.child_ref) { -// # const [nextPath] = parseRefPath(ref, currentPath); -// # const refNode = this.walkRef(ref, currentPath, data); -// # if (!helper.merge(refNode)) { -// # continue; -// # } else if (isCompoundNode(refNode.data)) { -// # const evalNode = this.mergeChildRef({ -// # ...data, -// # node: refNode.data, -// # path: nextPath -// # }); -// # if (evalNode.children) { -// # for (const child of Object.keys(evalNode.children)) { -// # newChildren[child] = evalNode.children[child]; -// # } -// # } -// # } -// # } -// # return { -// # children: newChildren, -// # description: node.description, -// # suggestions: node.suggestions, -// # type: "compound" -// # }; -// # } -// # -// # private walkCompoundNode( -// # data: ContextData -// # ): ReturnedInfo { -// # const { node, reader, path, tag } = data; -// # const helper = new ReturnHelper(); -// # const next = reader.read(); -// # if (node.children && next in node.children) { -// # /* -// # * It is safe to assume that next is in the tag -// # * val because the path is based off of the tag -// # */ -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: node.children[next], -// # tag: tag ? tag.getVal()[next] : undefined -// # }) -// # ); -// # } else if (node.child_ref) { -// # for (const c of node.child_ref) { -// # const [nextPath] = parseRefPath(c, path); -// # const cnode = this.walkRef(c, path, data); -// # if ( -// # helper.merge(cnode) && -// # isCompoundNode(cnode.data) && -// # cnode.data.children && -// # next in cnode.data.children -// # ) { -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: cnode.data.children[next], -// # path: nextPath, -// # tag: tag ? tag.getVal()[next] : undefined -// # }) -// # ); -// # } -// # } -// # } -// # return helper.fail(); -// # } -// # -// # private walkFunctionNode( -// # data: ContextData -// # ): ReturnedInfo { -// # const { node, reader, path } = data; -// # const helper = new ReturnHelper(); -// # const ref = runNodeFunction(this.parsed, reader.getRead(), node); -// # const [nextPath] = parseRefPath(ref, path); -// # const newNode = this.walkRef(ref, path, data); -// # if (!helper.merge(newNode)) { -// # return helper.fail(); -// # } -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: newNode.data, -// # path: nextPath -// # }) -// # ); -// # } -// # -// # private walkListNode( -// # data: ContextData -// # ): ReturnedInfo { -// # const { node, reader, tag } = data; -// # const next = reader.read(); -// # const helper = new ReturnHelper(); -// # if (!/\d+/.test(next)) { -// # return helper.fail( -// # tag -// # ? VALIDATION_ERRORS.badIndex.create( -// # tag.getRange().start, -// # tag.getRange().end -// # ) -// # : undefined -// # ); -// # } -// # const nextTag = tag -// # ? tag.getVal()[Number.parseInt(next, 10)] -// # : undefined; -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: node.item, -// # tag: nextTag -// # }) -// # ); -// # } -// # -// # private walkNextNode(data: ContextData): ReturnedInfo { -// # const { reader, node, tag, useReferences, finalValidation } = data; -// # const helper = new ReturnHelper(); -// # if (reader.onLast()) { -// # if (isRefNode(node)) { -// # return helper.return( -// # this.walkRefNode(data as ContextData) -// # ); -// # } else if (isFunctionNode(node)) { -// # return helper.return( -// # this.walkFunctionNode(data as ContextData) -// # ); -// # } else if (isCompoundNode(node)) { -// # if (finalValidation && this.validateNBT && tag) { -// # const valres = tag.valideAgainst(node, { -// # compoundMerge: () => -// # this.mergeChildRef(data as ContextData< -// # CompoundNode, -// # NBTTagCompound -// # >), -// # extraChildren: this.extraChildren -// # }); -// # if (!helper.merge(valres)) { -// # return helper.fail(); -// # } -// # } -// # return helper.succeed( -// # finalValidation -// # ? this.mergeChildRef(data as ContextData) -// # : node -// # ); -// # } else { -// # if (finalValidation && this.validateNBT && tag) { -// # const valres = tag.valideAgainst(node); -// # if (!helper.merge(valres)) { -// # return helper.fail(); -// # } -// # } -// # return helper.succeed(node); -// # } -// # } else if ( -// # useReferences && -// # node.references && -// # reader.peek() in node.references -// # ) { -// # const next = reader.read(); -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: node.references[next] -// # }) -// # ); -// # } else if (isTypedNode(node)) { -// # if (isCompoundNode(node)) { -// # if (this.validateNBT && tag) { -// # const valres = tag.valideAgainst(node, { -// # compoundMerge: () => -// # this.mergeChildRef(data as ContextData< -// # CompoundNode -// # >), -// # extraChildren: this.extraChildren -// # }); -// # if (!helper.merge(valres)) { -// # return helper.fail(); -// # } -// # } -// # if (tag && !(tag instanceof NBTTagCompound)) { -// # return helper.fail(); -// # } -// # return helper.return( -// # this.walkCompoundNode(data as ContextData< -// # CompoundNode, -// # NBTTagCompound -// # >) -// # ); -// # } else if (isListNode(node)) { -// # if (this.validateNBT && tag) { -// # const valres = tag.valideAgainst(node); -// # if (!helper.merge(valres)) { -// # return helper.fail(); -// # } -// # } -// # if (tag && !(tag instanceof NBTTagList)) { -// # return helper.fail(); -// # } -// # return helper.return( -// # this.walkListNode(data as ContextData) -// # ); -// # } else if (isRootNode(node)) { -// # return helper.return( -// # this.walkRootNode(data as ContextData) -// # ); -// # } else { -// # if (tag) { -// # const valres = tag.valideAgainst(node); -// # helper.merge(valres); -// # } -// # return helper.fail(); -// # } -// # } else { -// # if (isRefNode(node)) { -// # return helper.return( -// # this.walkRefNode(data as ContextData) -// # ); -// # } else if (isFunctionNode(node)) { -// # return helper.return( -// # this.walkFunctionNode(data as ContextData) -// # ); -// # } -// # } -// # return helper.fail(); -// # } -// # -// # private walkRef( -// # ref: string, -// # path: string, -// # data: ContextData -// # ): ReturnedInfo { -// # const [nextPath, fragPath] = parseRefPath(ref, path); -// # const reader = new ArrayReader(fragPath); -// # const node = this.docs.get(nextPath) as NBTNode; -// # // tslint:disable-next-line:helper-return -// # return this.walkNextNode({ -// # useReferences: true, -// # finalValidation: false, -// # node, -// # path: nextPath, -// # reader, -// # tag: data.tag -// # }); -// # } -// # -// # private walkRefNode(data: ContextData): ReturnedInfo { -// # const { node, path } = data; -// # const helper = new ReturnHelper(); -// # const [nextPath] = parseRefPath(node.ref, path); -// # const nnode = this.walkRef(node.ref, path, data); -// # if (helper.merge(nnode)) { -// # const out = this.walkNextNode({ -// # ...data, -// # node: nnode.data, -// # path: nextPath -// # }); -// # if (helper.merge(out)) { -// # return helper.succeed(out.data); -// # } else { -// # return helper.fail(); -// # } -// # } else { -// # return helper.fail(); -// # } -// # } -// # -// # private walkRootNode(data: ContextData): ReturnedInfo { -// # const { node, reader, path } = data; -// # const next = reader.read(); -// # const helper = new ReturnHelper(); -// # if (next in node.children) { -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: node.children[next] -// # }) -// # ); -// # } else { -// # for (const key of Object.keys(node.children)) { -// # if (key.startsWith("$")) { -// # const ref = key.substring(1); -// # const [nextPath] = parseRefPath(ref, path); -// # const list = (this.docs.get(nextPath) as any) as ValueList; -// # if ( -// # list.find( -// # v => (isString(v) ? v === next : v.value === next) -// # ) -// # ) { -// # return helper.return( -// # this.walkNextNode({ -// # ...data, -// # node: node.children[key] -// # }) -// # ); -// # } -// # } -// # } -// # } -// # return helper.fail(); -// # } -// #} -// # diff --git a/src/parsers/minecraft/resources.ts b/src/parsers/minecraft/resources.ts index e939ee5..5ff4343 100644 --- a/src/parsers/minecraft/resources.ts +++ b/src/parsers/minecraft/resources.ts @@ -14,8 +14,8 @@ import { processParsedNamespaceOption, ReturnHelper, startPaths, - stringArrayToNamespaces, - stringifyNamespace + stringArrayToIDs, + stringifyID } from "../../misc-functions"; import { Parser } from "../../types"; @@ -79,7 +79,7 @@ export const functionParser: Parser = { exceptions.unknown_function.create( start, reader.cursor, - stringifyNamespace(data.parsed) + stringifyID(data.parsed) ) ); } @@ -111,7 +111,7 @@ export const functionParser: Parser = { exceptions.unknown_tag.create( start, reader.cursor, - stringifyNamespace(parsed.data) + stringifyID(parsed.data) ) ) .succeed(); @@ -130,7 +130,7 @@ const bossbarParser: Parser = { if (info.data.localData && info.data.localData.nbt.level) { const start = reader.cursor; const bars = info.data.localData.nbt.level.Data.CustomBossEvents; - const options = stringArrayToNamespaces(Object.keys(bars)); + const options = stringArrayToIDs(Object.keys(bars)); const result = parseNamespaceOption(reader, options); if (helper.merge(result)) { return helper.succeed(); @@ -141,7 +141,7 @@ const bossbarParser: Parser = { exceptions.nobossbar.create( start, reader.cursor, - stringifyNamespace(result.data) + stringifyID(result.data) ) ) .succeed(); @@ -220,7 +220,7 @@ export const resourceParser: Parser = { (kind.issue as CommandErrorBuilder).create( start, reader.cursor, - stringifyNamespace(result.data) + stringifyID(result.data) ) ) .succeed(); diff --git a/src/parsers/minecraft/scoreboard.ts b/src/parsers/minecraft/scoreboard.ts index 9d61346..308b820 100644 --- a/src/parsers/minecraft/scoreboard.ts +++ b/src/parsers/minecraft/scoreboard.ts @@ -13,11 +13,11 @@ import { } from "../../data/lists/criteria"; import { DisplaySlots } from "../../data/nbt/nbt-types"; import { - convertToNamespace, - namespacesEqual, + convertToID, + idsEqual, parseNamespaceOption, ReturnHelper, - stringArrayToNamespaces + stringArrayToIDs } from "../../misc-functions"; import { typed_keys } from "../../misc-functions/third_party/typed-keys"; import { Parser } from "../../types"; @@ -174,7 +174,7 @@ const UNKNOWN_CRITERIA = new CommandErrorBuilder( "Unknown criteria '%s'" ); -const customPrefix = convertToNamespace("minecraft:custom"); +const customPrefix = convertToID("minecraft:custom"); export const criteriaParser: Parser = { parse: (reader, info) => { // tslint:disable:no-shadowed-variable @@ -247,10 +247,10 @@ export const criteriaParser: Parser = { const postStart = reader.cursor; for (const choice of entityCriteria) { reader.cursor = postStart; - if (namespacesEqual(data.literal, choice)) { + if (idsEqual(data.literal, choice)) { const result = parseNamespaceOption( reader, - stringArrayToNamespaces([ + stringArrayToIDs([ ...info.data.globalData.registries[ "minecraft:entity_type" ] @@ -265,10 +265,10 @@ export const criteriaParser: Parser = { } for (const choice of blockCriteria) { reader.cursor = postStart; - if (namespacesEqual(data.literal, choice)) { + if (idsEqual(data.literal, choice)) { const result = parseNamespaceOption( reader, - stringArrayToNamespaces([ + stringArrayToIDs([ ...info.data.globalData.registries["minecraft:block"] ]), CompletionItemKind.Reference, @@ -281,10 +281,10 @@ export const criteriaParser: Parser = { } for (const choice of itemCriteria) { reader.cursor = postStart; - if (namespacesEqual(data.literal, choice)) { + if (idsEqual(data.literal, choice)) { const result = parseNamespaceOption( reader, - stringArrayToNamespaces([ + stringArrayToIDs([ ...info.data.globalData.registries[ "minecraft:entity_type" ] @@ -297,10 +297,10 @@ export const criteriaParser: Parser = { } } } - if (namespacesEqual(data.literal, customPrefix)) { + if (idsEqual(data.literal, customPrefix)) { const result = parseNamespaceOption( reader, - stringArrayToNamespaces([ + stringArrayToIDs([ ...info.data.globalData.registries["minecraft:custom_stat"] ]), CompletionItemKind.Reference, diff --git a/src/refactor/command-parser/infra/errors.ts b/src/refactor/command-parser/infra/errors.ts new file mode 100644 index 0000000..0ef690e --- /dev/null +++ b/src/refactor/command-parser/infra/errors.ts @@ -0,0 +1,54 @@ +// Handles the central error store. +import { notStrictEqual, strictEqual } from "assert"; +import { DiagnosticSeverity } from "vscode-languageserver"; + +export type ErrorID = number; + +/** + * Information about a single error + */ +export interface ErrorInfo { + code: ErrorID; + defaultText: string; + severity?: DiagnosticSeverity; +} + +const errors = new Map(); + +/** + * Register an error, and get its ID. + * Breakdown of error codes: + * - 1000-1999: Generally applicable errors, e.g. Namespace parsing, etc. + * - 2000-9999: Parser specific errors. Each parser has an assigned block of 100 errors. + * - 10000 and above: Unused + * @param code The stable code of the error. + * @param defaultText The + */ +export function registerError( + code: number, + defaultText: string, + severity?: DiagnosticSeverity +): ErrorID { + strictEqual( + errors.get(code), + undefined, + "Tried to create two errors with same code" + ); + const data: ErrorInfo = { + code, + defaultText, + severity + }; + errors.set(code, data); + return code; +} + +export function getError(code: ErrorID): ErrorInfo { + const error = errors.get(code); + notStrictEqual( + error, + undefined, + "Attempted to get an error which hadn't been previously defined" + ); + return error as ErrorInfo; +} diff --git a/src/refactor/command-parser/infra/parse-results.ts b/src/refactor/command-parser/infra/parse-results.ts new file mode 100644 index 0000000..058e36a --- /dev/null +++ b/src/refactor/command-parser/infra/parse-results.ts @@ -0,0 +1,219 @@ +import { notStrictEqual } from "assert"; +import { + CompletionItem, + CompletionList, + MarkupContent, + MarkupKind +} from "vscode-languageserver"; + +import { LineRange } from "../../../types"; + +import { ErrorID } from "./errors"; + +export interface Suggestion extends Partial { + // The markdown description for this suggestion + description?: string | string[]; + label?: string; +} + +/* /** + * An acummulator for errors about files + * / +export class MiscInfoStorer {} + */ +export interface ErrorData { + code: ErrorID; + range: LineRange; + substitutions: string[]; +} + +export interface MergeOptions { + // Actions?: boolean; + errors?: boolean; + suggestions?: boolean; +} + +/** + * An accumulator for the results of parsing. Subclass of Parser, but potentially more generally applicable. + * TODO: Work out how to store file based errors + */ +export class ParseResults { + public get erroring(): boolean { + return this.errors === undefined; + } + + public get suggesting(): boolean { + return typeof this.suggestions !== "undefined"; + } + + // TODO: Use a more stable type to allow better testing. + private errors: ErrorData[] | undefined; + private suggestions: Map> | undefined; + + public addError( + id: ErrorID, + range: LineRange, + ...substitutions: string[] + ): void { + if (this.errors) { + this.errors.push({ code: id, range, substitutions }); + } + } + + public addSuggestion( + start: number, + text: string, + suggestion: Suggestion = {} + ): void { + if (this.suggestions) { + let oldForIndex = this.suggestions.get(start); + if (typeof oldForIndex === "undefined") { + oldForIndex = new Map(); + this.suggestions.set(start, oldForIndex); + } + const oldValue = oldForIndex.get(text); + if (oldValue) { + mergeSuggestions(oldValue, suggestion); + } else { + oldForIndex.set(text, suggestion); + } + } + } + + public call any>( + func: F, + settings: MergeOptions, + ...rest: F extends (self: this, ...args: infer A) => any ? A : never + ): ReturnType { + const errors = this.errors; + const suggestions = this.suggestions; + if (settings.errors === false) { + this.errors = undefined; + } + if (settings.suggestions === false) { + this.suggestions = undefined; + } + const result = func(this, ...rest); + this.errors = errors; + this.suggestions = suggestions; + return result; + } + + public callMethod any>( + func: F, + settings: MergeOptions, + ...rest: F extends (this: this, ...args: infer A) => any ? A : never + ): ReturnType { + const errors = this.errors; + const suggestions = this.suggestions; + if (settings.errors === false) { + this.errors = undefined; + } + if (settings.suggestions === false) { + this.suggestions = undefined; + } + const result = func.bind(this)(...rest); + this.errors = errors; + this.suggestions = suggestions; + return result; + } + /** + * Create a `CompletionList` from the stored suggestions. + * + * TODO: Consider moving this from the command-parser package into the server package + * @param line The line number in the document the completions should be created for + * @param end The position in the line of the users cursor + */ + public createCompletionList(line: number, end: number): CompletionList { + if (this.suggestions) { + const result: CompletionItem[] = []; + for (const [character, texts] of this.suggestions) { + for (const [newText, suggestion] of texts) { + const documentation: + | MarkupContent + | undefined = suggestion.description + ? { + kind: MarkupKind.Markdown, + value: Array.isArray(suggestion.description) + ? suggestion.description.join("\n\n---\n") + : suggestion.description + } + : undefined; + result.push({ + ...suggestion, + documentation, + label: suggestion.label || newText, + textEdit: { + newText, + range: { + end: { line, character: end }, + start: { line, character } + } + } + }); + } + } + return CompletionList.create(result, false); + } else { + throw new Error( + "Attempted to create a CompletionList when suggestions are not enabled" + ); + } + } + + public disableErrors(): ParseResults["errors"] { + const val = this.errors; + this.suggestions = undefined; + return val; + } + + public disableSuggestions(): ParseResults["suggestions"] { + const val = this.suggestions; + this.suggestions = undefined; + return val; + } + + public enableErrors(value: ParseResults["errors"] = []): void { + this.errors = value; + } + + public enableSuggestions( + value: ParseResults["suggestions"] = new Map() + ): void { + this.suggestions = value; + } + + public getErrors(): ErrorData[] { + notStrictEqual( + this.errors, + undefined, + "Attempted to get errors when errors are not enabled" + ); + return this.errors as ErrorData[]; + } +} + +function mergeSuggestions(first: Suggestion, newer: Suggestion): void { + if (first.description) { + if (typeof newer.description !== "undefined") { + if (typeof first.description === "string") { + if (typeof newer.description === "string") { + first.description = [first.description, newer.description]; + } else { + first.description = [ + first.description, + ...newer.description + ]; + } + } else { + if (typeof newer.description === "string") { + first.description.push(newer.description); + } else { + first.description.push(...newer.description); + } + } + } + } else { + first.description = newer.description; + } +} diff --git a/src/refactor/command-parser/infra/parser-ctx.ts b/src/refactor/command-parser/infra/parser-ctx.ts new file mode 100644 index 0000000..9ef9fbb --- /dev/null +++ b/src/refactor/command-parser/infra/parser-ctx.ts @@ -0,0 +1,14 @@ +import { Parser } from "./parser"; + +export class ParserCtx extends Parser { + public context: T; + public settings: McFunctionSettings; + public constructor( + context: T, + ...args: ConstructorParameters + ) { + super(...args); + this.context = context; + this.settings = mcLangSettings; + } +} diff --git a/src/refactor/command-parser/infra/parser.ts b/src/refactor/command-parser/infra/parser.ts new file mode 100644 index 0000000..e6eb1b2 --- /dev/null +++ b/src/refactor/command-parser/infra/parser.ts @@ -0,0 +1,463 @@ +import { typed_keys } from "../../../misc-functions/third_party/typed-keys"; +import { LineRange } from "../types"; + +import { ErrorID, registerError } from "./errors"; +import { ParseResults, Suggestion } from "./parse-results"; +import { Err, isErr, Ok, Result } from "./result"; + +/** + * String reader exceptions. + * Codes 1000-1100 + */ +const EXCEPTIONS = { + EXPECTED_BOOL: registerError(1000, "Expected bool"), + EXPECTED_END_OF_QUOTE: registerError(1031, "Unclosed quoted string"), + EXPECTED_FLOAT: registerError(1020, "Expected float"), + EXPECTED_INT: registerError(1010, "Expected integer"), + EXPECTED_START_OF_QUOTE: registerError( + 1030, + "Expected quote to start a string" + ), + EXPECTED_STRING_FROM: registerError( + 1041, + "Expected string from [%s], got '%s'" + ), + EXPECTED_SYMBOL: registerError(1040, "Expected '%s'"), + INVALID_BOOL: registerError( + 1001, + "Invalid bool, expected true or false but found '%s'" + ), + INVALID_ESCAPE: registerError( + 1032, + "Invalid escape sequence '\\%s' in quoted string)" + ), + INVALID_FLOAT: registerError(1021, "Invalid float '%s'"), + INVALID_INT: registerError(1011, "Invalid integer '%s'") +}; + +const ESCAPE = "\\"; + +export interface QuotingInfo { + /** + * True if quoting is allowed + */ + quote: boolean; + /** + * Defined if not quoting is allowed + */ + unquoted?: RegExp; +} + +const QUOTES = ['"', "'"]; + +/** + * A parser type, which is analogous to the StringReader type from Mojang's Brigadier, + * but also supports inline storage of errors and suggestions. + */ +export class Parser extends ParseResults { + public static charAllowedInUnquotedString = /^[0-9A-Za-z_\-\.+]$/; + public static charAllowedNumber = /^[0-9\-\.]$/; + public static defaultQuotingInfo: QuotingInfo = { + quote: true, + unquoted: Parser.charAllowedInUnquotedString + }; + public static noQuoteInfo: QuotingInfo = { + quote: false, + unquoted: Parser.charAllowedInUnquotedString + }; + public static quotesOnly: QuotingInfo = { quote: true }; + + public static isQuotedStringStart(char: string): boolean { + return QUOTES.includes(char); + } + + private static readonly bools = { true: true, false: false }; + public cursor = 0; + public readonly string: string; + + public constructor( + stringToRead: string, + ...superParameters: ConstructorParameters + ) { + super(...superParameters); + this.string = stringToRead; + } + + public addError( + id: ErrorID, + range: LineRange | number, + ...substitutions: string[] + ): void { + super.addError( + id, + typeof range === "number" + ? { start: range, end: this.cursor } + : range, + ...substitutions + ); + } + + public canRead(length: number = 1): boolean { + return this.cursor + length <= this.string.length; + } + /** + * Require that a specific string follows + * @param str The string which should come next + */ + public expect(str: string): boolean { + if (this.getRemainingLength() <= str.length) { + this.addSuggestion(this.cursor, str); + } + const sub = this.string.substr(this.cursor, str.length); + if (sub !== str) { + this.addError( + EXCEPTIONS.EXPECTED_SYMBOL, + Math.min(this.string.length, this.cursor + str.length), + str + ); + return false; + } + this.cursor += str.length; + return true; + } + + public expectOption(...options: T[]): T | undefined { + const start = this.cursor; + let out: string | undefined; + for (const s of options) { + if ( + this.callMethod( + this.expect.bind(this), + { + errors: false + }, + s + ) + ) { + if (!out || s.length > out.length) { + out = s; + } + this.cursor = start; + } + } + if (!out) { + const end = Math.min( + this.getTotalLength(), + start + Math.max(...options.map(v => v.length)) + ); + this.addError( + EXCEPTIONS.EXPECTED_STRING_FROM, + { start, end }, + options.join(), + this.getRemaining() + ); + return undefined; + } + this.cursor += out.length; + return out as T; + } + + public getRead(): string { + return this.string.substring(0, this.cursor); + } + public getRemaining(): string { + return this.string.substring(this.cursor); + } + public getRemainingLength(): number { + return this.string.length - this.cursor; + } + public getTotalLength(): number { + return this.string.length; + } + public peek(offset: number = 0): string { + return this.string.charAt(this.cursor + offset); + } + public read(): string { + return this.string.charAt(this.cursor++); + } + /** + * Read a boolean value from the string + */ + public readBoolean(quoting?: QuotingInfo): boolean | undefined { + const start = this.cursor; + const value = this.readOption( + typed_keys(Parser.bools), + quoting + ); + if (isErr(value)) { + if (value.err) { + this.addError(EXCEPTIONS.INVALID_BOOL, start, value.err); + } + // Invalid quoted string otherwise, so no new error. + return undefined; + } + return Parser.bools[value.ok]; + } + /** + * Read a float from the string + */ + public readFloat(): number | undefined { + const start = this.cursor; + const readToTest: string = this.readWhileRegexp( + Parser.charAllowedNumber + ); + if (readToTest.length === 0) { + this.addError(EXCEPTIONS.EXPECTED_FLOAT, { + end: this.string.length, + start + }); + return undefined; + } + + // The Java readInt throws upon multiple `.`s, but Javascript's doesn't + if ((readToTest.match(/\./g) || []).length > 1) { + this.addError( + EXCEPTIONS.INVALID_FLOAT, + start, + this.string.substring(start, this.cursor) + ); + return undefined; + } + try { + return parseFloat(readToTest); + } catch (error) { + this.addError(EXCEPTIONS.INVALID_FLOAT, start, readToTest); + return undefined; + } + } + /** + * Read an integer from the string + */ + public readInt(): number | undefined { + const start = this.cursor; + const readToTest: string = this.readWhileRegexp( + Parser.charAllowedNumber + ); + if (readToTest.length === 0) { + this.addError(EXCEPTIONS.EXPECTED_INT, { + end: this.string.length, + start + }); + return undefined; + } + // The Java readInt throws upon a `.`, but the regex includes one in brigadier + // This handles this case + if (readToTest.indexOf(".") !== -1) { + this.addError( + EXCEPTIONS.INVALID_INT, + start, + this.string.substring(start, this.cursor) + ); + return undefined; + } + try { + return Number.parseInt(readToTest, 10); + } catch (error) { + this.addError(EXCEPTIONS.INVALID_INT, start, readToTest); + return undefined; + } + } + /** + * Expect a string from a selection + */ + public readOption( + options: T[], + quoting: QuotingInfo = Parser.defaultQuotingInfo, + suggestion?: Suggestion + ): Result { + const start = this.cursor; + const result = this.callMethod( + this.readOptionInner.bind(this), + { suggestions: false }, + quoting + ); + if (this.shouldSugggest()) { + for (const option of options) { + this.addSuggestion( + start, + quoteIfNeeded(option, quoting), + suggestion + ); + } + } + if (isErr(result)) { + return result; + } + const { ok } = result; + const valid = options.some(opt => opt === ok); + if (valid) { + return Ok(ok as T); + } + return Err(ok); + } + /** + * Read from the string, returning a string, which, in the original had been surrounded by quotes + */ + public readQuotedString(): Result { + const start = this.cursor; + if (!this.canRead()) { + // Following the behaviour of Brigadier: + // - https://github.com/Mojang/brigadier/blob/master/src/main/java/com/mojang/brigadier/StringReader.java#L185 + return Ok(""); + } + const terminator = this.peek(); + if (!Parser.isQuotedStringStart(terminator)) { + this.addError(EXCEPTIONS.EXPECTED_START_OF_QUOTE, { + end: this.string.length, + start: this.cursor + }); + return Err(undefined); + } + let result = ""; + let escaped = false; + while (this.canRead()) { + this.skip(); + const char: string = this.peek(); + if (escaped) { + if (char === terminator || char === ESCAPE) { + result += char; + escaped = false; + } else { + this.skip(); + this.addError( + EXCEPTIONS.INVALID_ESCAPE, + // Includes backslash + this.cursor - 2, + char + ); + } + } else if (char === ESCAPE) { + escaped = true; + } else if (char === terminator) { + this.skip(); + return Ok(result); + // This is https://github.com/palantir/tslint/issues/4600. (Fixed in next version) + // tslint:disable-next-line: unnecessary-else + } else { + result += char; + } + } + this.addSuggestion(this.cursor, terminator); + this.addError(EXCEPTIONS.EXPECTED_END_OF_QUOTE, start); + return Err(result); + } + /** + * Read a (quoted or unquoted) string literal from the string + */ + public readString( + unquotedRegex: RegExp = Parser.charAllowedInUnquotedString + ): Result { + if (this.canRead() && Parser.isQuotedStringStart(this.peek())) { + return this.readQuotedString(); + } + if (this.shouldSugggest()) { + this.addSuggestion(this.cursor, QUOTES[0]); + } + return Ok(this.readWhileRegexp(unquotedRegex)); + } + + /** + * Read a string which is not surrounded by quotes. + * Can only contain alphanumerical characters, _,+,. and - + */ + public readUnquotedString(): string { + return this.readWhileRegexp(Parser.charAllowedInUnquotedString); + } + + /** + * Read the string until a certain regular expression matches the + * character under the cursor. + * @param exp The Regular expression to test against. + */ + public readUntilRegexp(exp: RegExp): string { + return this.readWhileFunction(s => !exp.test(s)); + } + /** + * Read while a certain function returns true on each consecutive character starting with the one under the cursor. + * In most cases, it is better to use readWhileRegexp. + * @param callback The function to use. + */ + public readWhileFunction(callback: (char: string) => boolean): string { + const begin = this.cursor; + while (callback(this.peek())) { + if (this.canRead()) { + this.skip(); + } else { + return this.string.substring(begin); + } + } + return this.string.substring(begin, this.cursor); + } + /** + * Read the string while a certain regular expression matches the character under the cursor. + * The cursor ends on the first character which doesn't match + * @param exp The Regular Expression to test against + */ + public readWhileRegexp(exp: RegExp): string { + return this.readWhileFunction(s => exp.test(s)); + } + + /** + * The most common check is !this.canRead() to block suggestions + */ + public shouldSugggest(): boolean { + return this.suggesting && !this.canRead(); + } + + public skip(): void { + this.cursor++; + } + public skipWhitespace(): void { + this.readWhileRegexp(/\s/); // Whitespace + } + private readOptionInner( + info: QuotingInfo + ): Result { + if (info.quote) { + return this.readString(info.unquoted); + } + if (info.unquoted) { + return Ok(this.readWhileRegexp(info.unquoted)); + } + throw new Error( + "Quoting kind which doesn't support quoting or any characters" + ); + } +} + +export function quoteIfNeeded( + value: string, + quoting: QuotingInfo = Parser.defaultQuotingInfo +): string { + if (!quoting.quote) { + return value; + } + let needsQuotes = false; + const quoteCounts: Record = { '"': 0, "'": 0 }; + if (quoting.unquoted) { + for (const char of value) { + if (!char.match(quoting.unquoted)) { + needsQuotes = true; + } + if (quoteCounts.hasOwnProperty(char)) { + quoteCounts[char]++; + } + } + return value; + } + if (needsQuotes) { + const [[quote]] = Object.entries(quoteCounts).sort( + (prev, next) => prev[1] - next[1] + ); + return quote + escapeQuotes(value, quote) + quote; + } + return value; +} + +function escapeQuotes(value: string, quote: string = '"'): string { + return value + .replace(/\\/g, "\\\\") + .replace(new RegExp(quote, "g"), `\\${quote}`); +} + +export const READER_EXCEPTIONS = EXCEPTIONS; diff --git a/src/refactor/command-parser/infra/result.ts b/src/refactor/command-parser/infra/result.ts new file mode 100644 index 0000000..8403c0a --- /dev/null +++ b/src/refactor/command-parser/infra/result.ts @@ -0,0 +1,27 @@ +// A simple result type, to encode a success and failure + +export interface Ok { + ok: R; +} + +export interface Err { + err: E; +} + +export type Result = Ok | Err; + +export function isOk(result: Result): result is Ok { + return result.hasOwnProperty("ok"); +} + +export function isErr(result: Result): result is Err { + return !isOk(result); +} + +export function Ok(ok: R): Ok { + return { ok }; +} + +export function Err(err: E): Err { + return { err }; +} diff --git a/src/refactor/command-parser/nbt-types.ts b/src/refactor/command-parser/nbt-types.ts new file mode 100644 index 0000000..376dc7e --- /dev/null +++ b/src/refactor/command-parser/nbt-types.ts @@ -0,0 +1,222 @@ +import * as Long from "long"; + +export type NBTBoolean = 0 | 1; + +//#region Scoreboard +export interface Scoreboard { + data: ScoreboardData; + DataVersion: number; +} + +export interface ScoreboardData { + DisplaySlots: DisplaySlots; + Objectives: Objective[]; + PlayerScores: PlayerScore[]; + Teams: Team[]; +} + +export interface DisplaySlots { + /** list */ + slot_0: string; + /** sidebar */ + slot_1: string; + /** belowName */ + slot_10: string; + + slot_11: string; + slot_12: string; + slot_13: string; + slot_14: string; + slot_15: string; + slot_16: string; + slot_17: string; + slot_18: string; + slot_2: string; + slot_3: string; + slot_4: string; + slot_5: string; + slot_6: string; + slot_7: string; + slot_8: string; + slot_9: string; +} + +export interface Objective { + CriteriaName: string; + DisplayName: string; + Name: string; + RenderType: "hearts" | "integer"; +} + +export interface PlayerScore { + Locked: NBTBoolean; + Name: string; + Objective: string; + Score: number; +} + +export interface TeamOptions { + AllowFriendlyFire: NBTBoolean; + CollisionRule: "always" | "never" | "pushOtherTeams" | "pushOwnTeam"; + DeathMessageVisibility: + | "always" + | "never" + | "hideForOtherTeams" + | "hideForOwnTeam"; + DisplayName: string; + MemberNamePreifx: string; + MemberNameSuffix: string; + Name: string; + NameTagVisibility: + | "always" + | "never" + | "hideForOtherTeams" + | "hideForOwnTeam"; + SeeFriendlyInvisibles: NBTBoolean; +} + +export interface Team extends TeamOptions { + Players: string[]; +} + +//#endregion +//#region level.dat + +export interface Level { + Data: LevelData; +} + +export interface LevelData { + allowCommands: NBTBoolean; + BorderCenterX: number; + BorderCenterZ: number; + BorderDamagePerBlock: number; + BorderSafeZone: number; + BorderSize: number; + BorderSizeLerpTarget: number; + BorderSizeLerpTime: Long; + BorderWarningBlocks: number; + BorderWarningTime: number; + clearWeatherTime: number; + CustomBossEvents: { + [id: string]: CustomBossEvent; + }; + Datapacks: { + Disabled: string[]; + Enabled: string[]; + }; + DataVersion: number; + DayTime: Long; + Difficulty: 0 | 1 | 2 | 3; + DifficultyLocked: NBTBoolean; + DimensionData: { + 1: EndData; + }; + GameRules: GameRules; + gameType: 0 | 1 | 2 | 3; + generatorName: + | "default" + | "flat" + | "largeBiomes" + | "amplified" + | "buffet" + | "debug_all_block_states"; + generatorOptions: SuperflatGen | BuffetGen; + generatorVersion: number; + hardcore: NBTBoolean; + initialized: NBTBoolean; + LastPlayed: Long; + LevelName: string; + MapFeatures: NBTBoolean; + Player?: any; + raining: NBTBoolean; + rainTime: number; + RandomSeed: Long; + SizeOnDisk: Long; + SpawnX: number; + SpawnY: number; + SpawnZ: number; + thundering: NBTBoolean; + thunderTime: number; + Time: Long; + Version: { + Id: number; + Name: string; + Snapshot: NBTBoolean; + }; + version: number; +} + +export interface CustomBossEvent { + Color: string; + CreateWorldFog: NBTBoolean; + DarkenScreen: NBTBoolean; + Max: number; + Name: string; + Overlay: + | "progress" + | "notched_6" + | "notched_10" + | "notched_12" + | "notched_20"; + PlayBossMusic: NBTBoolean; + Players: Array<{ L: Long; M: Long }>; + Value: number; + Visible: NBTBoolean; +} + +export interface EndData { + DragonFight: DragonFight; +} + +export interface DragonFight { + DragonKilled: NBTBoolean; + DragonUUIDLeast: Long; + DragonUUIDMost: Long; + ExitPortalLocation: { + X: number; + Y: number; + Z: number; + }; + Gateways: number[]; + PreviouslyKilled: NBTBoolean; +} + +export interface GameRules { + [id: string]: string; // Maybe make concrete ones in the future +} + +export interface SuperflatGen { + biome: string; + layers: SuperflatLayer[]; + structures: { + [id: string]: {}; + }; +} + +export interface SuperflatLayer { + block: string; + height: number; +} + +export interface BuffetGen { + biome_source: BuffetBiomeSource; + chunk_generator: BuffetChunkGen; +} + +export interface BuffetBiomeSource { + options: { + biomes: string[]; + }; + type: "minecraft:fixed" | "minecraft:checkerboard"; +} + +export interface BuffetChunkGen { + options: { + default_block: string; + default_fluid: string; + }; + type: "minecraft:surface" | "minecraft:cave" | "minecraft:floating_islands"; +} + +//#endregion diff --git a/src/refactor/command-parser/parsers/shared/id-map.ts b/src/refactor/command-parser/parsers/shared/id-map.ts new file mode 100644 index 0000000..3cabf03 --- /dev/null +++ b/src/refactor/command-parser/parsers/shared/id-map.ts @@ -0,0 +1,304 @@ +export type SerializedIDMap = Array<[string, Array<[string, T]>]>; + +export interface MaybeResolved { + raw: T; + resolved?: R; +} + +export interface Resolved extends MaybeResolved { + resolved: R; +} + +export interface NamespaceMapParseResult extends Resolved { + id: ID; +} + +export type IdMapGetType = R extends undefined ? T : Resolved; + +export class IDMap { + /** + * The function which should be used when creating a description for a suggestion + */ + public set suggestionDescriptionFunction( + v: undefined | ((value: T, id: ID, resolved: R, context: C) => string) + ) { + this._descriptionFunction = v; + } + + public static fromIDs(...ids: ID[]): IdSet { + const result = new IDMap(); + for (const id of ids) { + result.add(id); + } + return result; + } + + public static fromStringArray(strings: string[]): IdSet { + return IDMap.fromIDs(...stringArrayToIDs(strings)); + } + + private _descriptionFunction: + | undefined + | ((value: T, id: ID, resolved: R, context: C) => string); + + private readonly map: Map< + string, + Map> + > = new Map(); + + private readonly resolver: R extends undefined + ? undefined + : (input: T, map: IDMap) => R; + + public constructor( + ...resolver: R extends undefined ? [] : [IDMap["resolver"]] + ) { + this.resolver = resolver[0] as any; + } + + public *[Symbol.iterator](): IterableIterator<[ID, T]> { + for (const [namespace, map] of this.map) { + for (const [path, maybeResolved] of map) { + yield [{ namespace, path }, maybeResolved.raw]; + } + } + } + + public add( + id: ID, + // Hack to improve the ergonomics of IdSet + ...value: T extends undefined ? [] : [T] + ): void { + const innerMap = this.namespaceMapOrDefault(id.namespace); + innerMap.set(id.path, { raw: value[0] as T }); + } + + public addFrom(other: IDMap): void { + other.map.forEach((v, k) => { + const map = this.namespaceMapOrDefault(k); + v.forEach((value, key) => { + map.set(key, value); + }); + }); + } + + public addSerialized(serialised: SerializedIDMap): void; + public addSerialized( + serialised: SerializedIDMap, + mapfunction?: (val: A) => T + ): void; + public addSerialized( + serialised: SerializedIDMap, + mapfunction?: (val: A) => T + ): void { + for (const [namespace, map] of serialised) { + const namespaceMap = this.namespaceMapOrDefault(namespace); + for (const [path, raw] of map) { + namespaceMap.set(path, { + raw: mapfunction + ? mapfunction(raw) + : ((raw as unknown) as T) + }); + } + } + } + + public edit(id: ID): T | undefined { + const val = this.getRaw(id); + if (val) { + val.resolved = undefined; + return val.raw; + } + return undefined; + } + + public get(id: ID): IdMapGetType | undefined { + const val = this.getResolved(id); + if (val) { + if (typeof val.resolved === "undefined") { + return val as IdMapGetType; + } else { + return val.raw as IdMapGetType; + } + } + return undefined; + } + + public getUnresolved(id: ID): T | undefined { + const value = this.getRaw(id); + return value && value.raw; + } + + public has(id: ID): boolean { + const innerMap = this.namespaceMap(id.namespace); + return !!(innerMap && innerMap.has(id.path)); + } + + /** + * Parse an ID from this map. This will not add any errors, it is left to the caller + * to check that there is nothing following. + * + * @param namespaceCharacter The character to use as the namespace character + * + * @returns ReturnSuccess containing the wrapped T and the ID or ReturnFailure with the best effort parsed ID + */ + public parse( + reader: StringReader, + context: C, + namespaceCharacter: string = NAMESPACE + ): ReturnedInfo, CE, ID> { + const helper = new ReturnHelper(); + const start = reader.cursor; + const firstSegment = readNamespaceSegment(reader, namespaceCharacter); + if (reader.canRead() && reader.peek() === namespaceCharacter) { + reader.skip(); + const pathStart = reader.cursor; + const path = readNamespaceSegment(reader, namespaceCharacter); + const id: ID = { namespace: firstSegment, path }; + if (!reader.canRead()) { + const map = this.namespaceMap(firstSegment); + if (map) { + map.forEach((maybeResolved, key) => { + helper.addSuggestion( + pathStart, + key, + CompletionItemKind.EnumMember, + this._descriptionFunction + ? this._descriptionFunction( + maybeResolved.raw, + { + namespace: firstSegment, + path + }, + maybeResolved.resolved || + (this.resolver && + this.resolver( + maybeResolved.raw, + this + )), + context + ) + : undefined + ); + }); + } + } + const value = this.getResolved(id); + if (typeof value !== "undefined") { + return helper.succeed>({ + ...value, + id + }); + } + return helper.failWithData(id); + } else { + if (!reader.canRead()) { + helper.addSuggestion(reader.cursor, namespaceCharacter); + this.map.forEach((_, key) => { + helper.addSuggestion( + start, + key + namespaceCharacter, + CompletionItemKind.EnumMember + ); + }); + const defaultMap = this.namespaceMap(); + if (defaultMap) { + defaultMap.forEach((_, key) => { + const v = this.getResolved({ path: key }); + if (v) { + helper.addSuggestion( + start, + stringifyID({ path: key }, namespaceCharacter), + CompletionItemKind.Constant, + this._descriptionFunction && + this._descriptionFunction( + v.raw, + { + path: firstSegment + }, + v.resolved, + context + ) + ); + } + }); + } + } + const id: ID = { path: firstSegment }; + const value = this.getResolved(id); + if (typeof value !== "undefined") { + return helper.succeed>({ + ...value, + id + }); + } + return helper.failWithData(id); + } + } + + public remove(id: ID): boolean { + const map = this.namespaceMap(id.namespace); + if (map) { + const result = map.delete(id.path); + if (map.size === 0) { + this.map.delete(id.namespace || DEFAULT_NAMESPACE); + } + return result; + } + return false; + } + + public serialize(): SerializedIDMap; + public serialize( + mapfunction?: (val: T) => A | false + ): SerializedIDMap; + public serialize( + mapfunction?: (val: T) => A | false + ): SerializedIDMap { + return [...this.map].map(([namespace, inner]) => [ + namespace, + [...inner] + .map(v => [ + v[0], + mapfunction ? mapfunction(v[1].raw) : v[1].raw + ]) + .filter(v => v[1] !== false) + ]) as SerializedIDMap; + } + + private getRaw(id: ID): MaybeResolved | undefined { + const innerMap = this.namespaceMap(id.namespace); + return innerMap && innerMap.get(id.path); + } + + private getResolved(id: ID): Resolved | undefined { + const val = this.getRaw(id); + if (val) { + val.resolved = + val.resolved || (this.resolver && this.resolver(val.raw, this)); + return val as Resolved; + } + return undefined; + } + + private namespaceMap( + namespace: string = DEFAULT_NAMESPACE + ): Map> | undefined { + return this.map.get(namespace); + } + + private namespaceMapOrDefault( + namespace: string = DEFAULT_NAMESPACE + ): Map> { + const result = this.map.get(namespace); + if (typeof result !== "undefined") { + return result; + } + const newMap = new Map(); + this.map.set(namespace, newMap); + return newMap; + } +} + +// TODO: Make this its own class +export type IdSet = IDMap; diff --git a/src/refactor/command-parser/types.ts b/src/refactor/command-parser/types.ts new file mode 100644 index 0000000..b4e4134 --- /dev/null +++ b/src/refactor/command-parser/types.ts @@ -0,0 +1,196 @@ +import { LanguageService } from "vscode-json-languageservice"; + +import { Level, Scoreboard } from "./nbt-types"; +import { IDMap, IdSet } from "./parsers/shared/id-map"; + +// tslint:disable-next-line: interface-name +export interface ID { + namespace?: string; + path: string; +} + +export type DataPackID = number; +// Undefined represents Vanilla's data +export type DataPackReference = DataPackID | undefined; + +export interface McmetaFile { + pack?: { description?: string; pack_format?: number }; +} + +export interface Tag { + replace?: boolean; + values: string[]; +} + +export interface Advancement { + criteria: Set; +} + +export type RegistryNames = + | "minecraft:sound_event" + | "minecraft:fluid" + | "minecraft:mob_effect" + | "minecraft:block" + | "minecraft:enchantment" + | "minecraft:entity_type" + | "minecraft:item" + | "minecraft:potion" + | "minecraft:carver" + | "minecraft:surface_builder" + | "minecraft:feature" + | "minecraft:decorator" + | "minecraft:biome" + | "minecraft:particle_type" + | "minecraft:biome_source_type" + | "minecraft:block_entity_type" + | "minecraft:chunk_generator_type" + | "minecraft:dimension_type" + | "minecraft:motive" + | "minecraft:custom_stat" + | "minecraft:chunk_status" + | "minecraft:structure_feature" + | "minecraft:structure_piece" + | "minecraft:rule_test" + | "minecraft:structure_processor" + | "minecraft:structure_pool_element" + | "minecraft:menu" + | "minecraft:recipe_type" + | "minecraft:recipe_serializer" + | "minecraft:stat_type" + | "minecraft:villager_type" + | "minecraft:villager_profession"; + +export interface Datapack { + id: DataPackID; + mcmeta?: McmetaFile; + name: string; + world: string; +} + +export type RegistryData = Record; + +export interface WorldNBT { + level?: Level; + scoreboard?: Scoreboard; +} + +export interface CommandTree { + children: Record; + executable?: boolean; + /** + * The parser for this node. Only Applicable if type of argument + */ + parser?: string; + properties?: Dictionary; + redirect?: string[]; + type: "literal" | "argument" | "root"; +} + +export type Blocks = IDMap>>; + +export interface WorldInfo { + datapacksFolder: string; + nbt: WorldNBT; + packnamesmap: Map; +} + +export interface CommandData { + blocks: Blocks; + commands: CommandTree; + data_info: { version: string }; + jsonService: LanguageService; + nbt_docs: NBTDocs; + packs: Map; + registries: RegistryData; + resources: Resources; + worlds: Map; +} + +export interface LineRange { + end: number; + start: number; +} + +export type PackMap = Map; + +export type ResourcesMap = IDMap< + PackMap, + R, + CommandData +>; +// Used for tags + +export interface ResolvedTag { + /** + * Directly referenced types + */ + finals: IdSet; + /** + * Resolved types + */ + resolved: IdSet; + /** + * Tags referenced + */ + tags: IdSet; +} + +export type TagMap = ResourcesMap; + +export interface Resources { + advancements: ResourcesMap; + block_tags: TagMap; + entity_tags: TagMap; + fluid_tags: TagMap; + function_tags: TagMap; + functions: ResourcesMap; + item_tags: TagMap; + loot_tables: ResourcesMap; + recipes: ResourcesMap; + structures: ResourcesMap; +} + +export interface ParserInfo { + /** + * The immutable context + */ + context: CommandContext; + data: CommandData; + node_properties: Dictionary; + path: CommandNodePath; // Will be > 0 + /** + * When suggesting, the end of the reader's string will be the cursor position + */ + suggesting: boolean; +} + +/** + * A change to the shared context + */ +export type ContextChange = Partial | undefined; + +export interface CommandContext { + // [key: string]: any; + /** + * What we know about the executor of the commmand. + * + * TODO: Possibly allow specifying this within a function as a sort of shebang, + * then validate all function calls with this requirement + */ + executor?: EntityInfo; + /** + * The result from an nbt path + */ + nbt_path?: TypedNode["type"]; + /** + * A different entity which is important during parsing + */ + otherEntity?: EntityInfo; +} + +export interface EntityInfo { + /** + * The possible entity types of this entity + */ + ids?: ID[]; +} diff --git a/src/test/assertions.ts b/src/test/assertions.ts index 178a021..c3a49c2 100644 --- a/src/test/assertions.ts +++ b/src/test/assertions.ts @@ -1,244 +1,69 @@ -import { AssertionError, notStrictEqual, strictEqual } from "assert"; +import { + AssertionError, + deepStrictEqual, + notStrictEqual, + strictEqual +} from "assert"; +import * as snapshot from "snap-shot-it"; -import { CommandError } from "../brigadier/errors"; import { StringReader } from "../brigadier/string-reader"; import { NAMESPACE } from "../consts"; -import { DataResource, MinecraftResource, NamespacedName } from "../data/types"; -import { - convertToNamespace, - isSuccessful, - namespacesEqual -} from "../misc-functions"; -import { typed_keys } from "../misc-functions/third_party/typed-keys"; -import { - CE, - ContextChange, - LineRange, - Parser, - ParserInfo, - ReturnedInfo, - ReturnSuccess, - SubAction, - SuggestResult -} from "../types"; +import { DataID, ResourceID } from "../data/types"; +import { convertToID } from "../misc-functions"; +import { ContextChange, Parser, ParserInfo, ReturnedInfo } from "../types"; import { blankproperties } from "./blanks"; +// To allow for auto-import +export { snapshot }; + export type TestParserInfo = Pick< ParserInfo, "context" | "data" | "node_properties" | "path" >; +export interface ParserTestResult { + cursor: number; + parsing: ReturnedInfo; + suggesting: ReturnedInfo; +} + export const testParser = (parser: Parser) => ( data: Partial = {} -) => ( - text: string, - expected: ReturnAssertionInfo -): [ReturnedInfo, StringReader] => { - let cursorPos: number; - { +) => { + function runParsertest( + text: string, + startingPos: number = 0 + ): ParserTestResult { const reader = new StringReader(text); - const result = parser.parse(reader, { + reader.cursor = startingPos; + const suggesting = parser.parse(reader, { ...blankproperties, ...data, suggesting: true }); - returnAssert(result, { - start: expected.start, - succeeds: expected.succeeds, - suggestions: expected.suggestions - }); // There should be no non-suggestions when suggesting - cursorPos = reader.cursor; - } - { - const reader = new StringReader(text); - const result = parser.parse(reader, { + const cursor = reader.cursor; + reader.cursor = startingPos; + const parsing = parser.parse(reader, { ...blankproperties, ...data, suggesting: false }); - returnAssert(result, { ...expected, suggestions: [] }); // There should be no suggestions when not suggesting - strictEqual(reader.cursor, cursorPos); - return [result, reader]; + strictEqual(reader.cursor, cursor); + deepStrictEqual(parsing.suggestions, []); + return { cursor, parsing, suggesting }; } + return runParsertest; }; -export interface ReturnAssertionInfo { - actions?: SubAction[]; - errors?: ErrorInfo[]; - numActions?: number; - numMisc?: number; - start?: number; - succeeds: boolean; - suggestions?: SuggestedOption[]; -} - -export function returnAssert( - actual: ReturnedInfo, - { - errors = [], - numActions = 0, - numMisc = 0, - start = 0, - succeeds, - suggestions = [], - actions - }: ReturnAssertionInfo -): actual is ReturnSuccess { - if (isSuccessful(actual) === succeeds) { - assertErrors(errors, actual.errors); - assertSuggestions(suggestions, actual.suggestions, start); - if (actions) { - assertMembers(actual.actions, actions, (expected, real) => - typed_keys(expected).every(k => real[k] === expected[k]) - ); - } else { - strictEqual(actual.actions.length, numActions); - } - strictEqual(actual.misc.length, numMisc); - } else { - throw new AssertionError({ - message: `Expected value given to ${ - succeeds ? "succeed" : "fail" - }, but it didn't. Value is: '${JSON.stringify(actual)}'` - }); - } - return true; -} - -/** - * Information about a single expected error - */ -export interface ErrorInfo { - code: string; - range: LineRange; -} - -/** - * Ensures that the right errors from `expected` are in `realErrors`s - * @param expected The errors which are expected - * @param actual The actual errors - */ -export function assertErrors( - expected: ErrorInfo[], - actual: CommandError[] | undefined -): void { - assertMembers( - actual, - expected, - errorMatches, - info => - `Expected errors to contain an error with code '${ - info.code - }' and range ${info.range.start}...${ - info.range.end - }, but none met this. The errors were ${JSON.stringify(actual)}` - ); -} - -function errorMatches(error: CommandError, expected: ErrorInfo): boolean { - return ( - expected.code === error.code && - expected.range.start === error.range.start && - expected.range.end === error.range.end - ); -} - -/** - * Information about a single expected suggestion - */ -export interface SuggestionInfo { - start: number; - text: string; -} - -/** - * Information about a single expected suggestion. - * If a string is given, the start is taken to be `start` - */ -export type SuggestedOption = SuggestionInfo | string; - -/** - * Ensure that the suggestions match the assertion about them - */ -export function assertSuggestions( - expected: SuggestedOption[], - actual: SuggestResult[] | undefined, - start: number = 0 -): void { - assertMembers( - actual, - expected, - (suggestion, expectation) => { - const [startText, position]: [string, number] = - typeof expectation === "string" - ? [expectation, start] - : [expectation.text, expectation.start]; - if (typeof suggestion === "string") { - return position === 0 && suggestion === startText; - } else { - return ( - suggestion.text === startText && - suggestion.start === position - ); - } - }, - expectation => { - const [startText, position]: [string, number] = - typeof expectation === "string" - ? [expectation, start] - : [expectation.text, expectation.start]; - return `Expected suggestions to contain a suggestion starting with text '${startText}' at position ${position}, but this was not found: got ${JSON.stringify( - actual - )} instead`; - } - ); -} - -export function assertNamespaces( - expected: NamespacedName[], - actual: NamespacedName[] | undefined -): void { - assertMembers( - actual, - expected, - namespacesEqual, - expectation => - `Expected to find a path with namespace '${ - expectation.namespace - }' and path ${expectation.path}, but none matched` - ); -} - -/* -export function assertReturn( - val: ReturnedInfo, - shouldSucceed: boolean, - errors: ErrorInfo[] = [], - suggestions: Array = [], - numActions: number = 0, - suggestStart: number = 0 -): val is ReturnSuccess { - if (isSuccessful(val) === shouldSucceed) { - assertErrors(errors, val.errors); - assertSuggestions(suggestions, val.suggestions, suggestStart); - strictEqual( - val.actions.length, - numActions, - `incorrect Number of expected actions: '${JSON.stringify( - val.actions - )}'` - ); - return shouldSucceed; - } else { - throw new AssertionError({ - message: `Expected value given to ${ - shouldSucceed ? "succeed" : "fail" - }, but it didn't. Value is: '${JSON.stringify(val)}'` - }); - } -} - */ +export const testFunction = ( + func: (reader: StringReader, ...args: A) => T, + ...args: A +) => (text: string, startingPos = 0) => { + const reader = new StringReader(text); + reader.cursor = startingPos; + return func(reader, ...args); +}; export function unwrap(val: T | undefined): T { if (val === undefined) { throw new AssertionError({ @@ -289,11 +114,11 @@ export function convertToResource( input: string, data?: T, splitChar: string = NAMESPACE -): DataResource { - const result = convertToNamespace(input, splitChar); +): DataID { + const result = convertToID(input, splitChar); notStrictEqual(result.namespace, undefined); if (data) { - return { ...(result as MinecraftResource), data }; + return { ...(result as ResourceID), data }; } - return result as MinecraftResource; + return result as ResourceID; } diff --git a/src/test/blanks.ts b/src/test/blanks.ts index 2dca81b..753900f 100644 --- a/src/test/blanks.ts +++ b/src/test/blanks.ts @@ -4,7 +4,7 @@ import { GlobalData } from "../data/types"; import { PackLocationSegments } from "../misc-functions"; import { CommandData, LineRange } from "../types"; -import { ReturnAssertionInfo, TestParserInfo } from "./assertions"; +import { TestParserInfo } from "./assertions"; /** * Blank items for testing @@ -17,8 +17,6 @@ export const pack_segments: PackLocationSegments = { rest: "" }; -export const succeeds: ReturnAssertionInfo = { succeeds: true }; - export const emptyRange = (): LineRange => ({ start: 0, end: 0 }); export const blankproperties: TestParserInfo = { context: {}, diff --git a/src/test/brigadier/string-reader.test.ts b/src/test/brigadier/string-reader.test.ts index bbfc755..da5c0f7 100644 --- a/src/test/brigadier/string-reader.test.ts +++ b/src/test/brigadier/string-reader.test.ts @@ -1,8 +1,7 @@ import * as assert from "assert"; import { StringReader } from "../../brigadier/string-reader"; -import { returnAssert } from "../assertions"; -import { succeeds } from "../blanks"; +import { snapshot } from "../assertions"; describe("string-reader", () => { describe("constructor()", () => { @@ -174,155 +173,50 @@ describe("string-reader", () => { }); }); describe("readInt()", () => { - [1, 2, 3, 4, 5].forEach((val: number) => { - it(`should read a ${val} character long integer`, () => { - const numbers = Array(val) - .fill(1) - .map((_, i) => i); - const reader = new StringReader(numbers.join("")); - const result = reader.readInt(); - if (returnAssert(result, succeeds)) { - assert.strictEqual( - result.data, - Number.parseInt(numbers.join(""), 10) - ); - } - }); - }); - it("should read a negative integer", () => { - const reader = new StringReader("-1000"); - const result = reader.readInt(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, -1000); - } - }); - it("should fail when there is a decimal place", () => { - const reader = new StringReader("1000."); - const result = reader.readInt(); - returnAssert(result, { - errors: [ - { code: "parsing.int.invalid", range: { start: 0, end: 5 } } - ], - succeeds: false - }); - }); - it("should read an integer until the first non-integer value", () => { - const reader = new StringReader("1000test"); - const result = reader.readInt(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, 1000); - } - }); - it("should throw an error when there is no integer under the cursor", () => { - const reader = new StringReader("noint"); - const result = reader.readInt(); - returnAssert(result, { - errors: [ - { - code: "parsing.int.expected", - range: { start: 0, end: 5 } - } - ], - succeeds: false - }); - }); - it("should fail when there is an invalid int under the cursor", () => { - const reader = new StringReader("1."); - const result = reader.readInt(); - returnAssert(result, { - errors: [ - { - code: "parsing.int.invalid", - range: { - end: 2, - start: 0 - } - } - ], - succeeds: false - }); + it("should have the correct behaviour for various inputs", () => { + const intReader = (text: string) => + new StringReader(text).readInt(); + snapshot( + intReader, + "1000.", + "-1000", + "1000test", + "noint", + "1.", + // Test integers of lengths up to 5 + ...[1, 2, 3, 4, 5].map(val => + Array(val) + .fill(1) + .map((_, i) => i) + .join("") + ) + ); }); }); describe("readFloat()", () => { - [1, 2, 3, 4, 5].forEach((val: number) => { - it(`should read a ${val} character long integer`, () => { - const numbers = Array(val) - .fill(1) - .map(v => v + 1); - const reader = new StringReader(numbers.join("")); - const result = reader.readFloat(); - if (returnAssert(result, succeeds)) { - assert.strictEqual( - result.data, - Number.parseInt(numbers.join(""), 10) - ); - } - }); - }); - it("should read a negative integer", () => { - const reader = new StringReader("-1000"); - const result = reader.readFloat(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, -1000); - } - }); - it("should return an integer even when there is a trailing decimal place", () => { - const reader = new StringReader("1000."); - const result = reader.readFloat(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, 1000); - } - }); - it("should read a float with a decimal place", () => { - const reader = new StringReader("1000.123"); - const result = reader.readFloat(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, 1000.123); - } - }); - it("should read a negative float", () => { - const reader = new StringReader("-1000.123"); - const result = reader.readFloat(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, -1000.123); - } - }); - it("should read a float until the first non-float value", () => { - const reader = new StringReader("1000.123test"); - const result = reader.readFloat(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, 1000.123); - } - assert.strictEqual(reader.cursor, 8); - }); - it("should fail when there is not a float under the cursor", () => { - const reader = new StringReader("nofloat"); - const result = reader.readFloat(); - returnAssert(result, { - errors: [ - { - code: "parsing.float.expected", - range: { start: 0, end: 7 } - } - ], - succeeds: false - }); - }); - it("should fail when there is an invalid float under the cursor", () => { - const reader = new StringReader("1.1.1.1.1"); - const result = reader.readFloat(); - returnAssert(result, { - errors: [ - { - code: "parsing.float.invalid", - range: { - end: 9, - start: 0 - } - } - ], - succeeds: false - }); + it("should have the correct behaviour for various inputs", () => { + const floatReader = (text: string) => + new StringReader(text).readInt(); + snapshot( + floatReader, + "-1000", + "1000.", + "1000test", + "noint", + "1.", + "1000.123", + "-1000.123", + "1000.123test", + "nofloat", + "1.1.1.1.1", + // Test integers of lengths up to 5 + ...[1, 2, 3, 4, 5].map(val => + Array(val) + .fill(1) + .map((_, i) => i) + .join("") + ) + ); }); }); describe("readUnquotedString()", () => { @@ -343,94 +237,32 @@ describe("string-reader", () => { }); }); describe("readQuotedString()", () => { + it("should have the correct behaviour on various inputs", () => { + const quotedStringReader = (text: string) => + new StringReader(text).readQuotedString(); + snapshot( + quotedStringReader, + "test", + ...[ + '"hello"', + '""', + '"quote\\"here"', + '"backslash\\\\here"', + '"oop\\s"', + '"trailing' + ] + // Same tests with single quotes and with double quotes + .map(v => [v, v.replace('"', "'")]) + .reduce((acc, val) => acc.concat(val), []), + // Mixed quotes + "'quote\" in the middle'", + '"quote\' in the middle"' + ); + }); it("should return an empty string if it reading from the end", () => { const reader = new StringReader("test"); reader.cursor = 4; - const result = reader.readQuotedString(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, ""); - assert.strictEqual(reader.cursor, 4); - } - }); - it("should throw an error if there is no opening quote", () => { - const reader = new StringReader("test"); - const result = reader.readQuotedString(); - returnAssert(result, { - errors: [ - { - code: "parsing.quote.expected.start", - range: { start: 0, end: 4 } - } - ], - succeeds: false - }); - }); - it("should read a full quoted string, giving a result without the quotes", () => { - const reader = new StringReader('"hello"'); - const result = reader.readQuotedString(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, "hello"); - assert.strictEqual(reader.cursor, 7); - } - }); - it("should return an empty string when there is an empty quoted string", () => { - const reader = new StringReader('""'); - const result = reader.readQuotedString(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, ""); - assert.strictEqual(reader.cursor, 2); - } - }); - it("should allow escaped quotes", () => { - const reader = new StringReader('"quote\\"here"'); - const result = reader.readQuotedString(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, 'quote"here'); - assert.strictEqual(reader.cursor, 13); - } - }); - it("should allow escaped backslashes", () => { - const reader = new StringReader('"backslash\\\\here"'); - const result = reader.readQuotedString(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, "backslash\\here"); - assert.strictEqual(reader.cursor, 17); - } - }); - it("should not allow surplus escapes", () => { - const reader = new StringReader('"oop\\s"'); - const result = reader.readQuotedString(); - returnAssert(result, { - errors: [ - { - code: "parsing.quote.escape", // Repeat of what Brigadier does? - range: { start: 4, end: 6 } - } - ], - succeeds: false - }); - }); - it("should fail when there is no closing quote", () => { - const reader = new StringReader('"trailing'); - const result = reader.readQuotedString(); - returnAssert(result, { - errors: [ - { - code: "parsing.quote.expected.end", - range: { start: 0, end: 9 } - } - ], - succeeds: false, - suggestions: [{ start: 9, text: '"' }] - }); - }); - it("should allow a single quoted string", () => { - const reader = new StringReader("'single quoted'"); - const result = reader.readQuotedString(); - if (returnAssert(result, succeeds)) { - assert.strictEqual(result.data, "single quoted"); - assert.strictEqual(reader.getRemainingLength(), 0); - } + snapshot(reader.readQuotedString()); }); }); describe("readString()", () => { @@ -444,112 +276,35 @@ describe("string-reader", () => { }); }); describe("readBoolean()", () => { - it("should return true if the string is true", () => { - const reader = new StringReader("true"); - const result = reader.readBoolean(); - if ( - returnAssert(result, { succeeds: true, suggestions: ["true"] }) - ) { - assert.strictEqual(result.data, true); - } - }); - it("should return false if the string is false", () => { - const reader = new StringReader("false"); - const result = reader.readBoolean(); - if ( - returnAssert(result, { - succeeds: true, - suggestions: ["false"] - }) - ) { - assert.strictEqual(result.data, false); - } - }); - it("should throw an error if not a boolean", () => { - const reader = new StringReader("nonBoolean"); - const result = reader.readBoolean(); - returnAssert(result, { - errors: [ - { - code: "parsing.bool.invalid", - range: { start: 0, end: 10 } - } - ], - succeeds: false - }); + it("should work correctly for various inputs", () => { + const boolRead = (text: string) => + new StringReader(text).readBoolean(); + snapshot(boolRead, "true", "false", "nonBoolean"); }); }); describe("expect()", () => { - it("should check the character under the cursor", () => { - const reader = new StringReader("test"); - const result = reader.expect("t"); - returnAssert(result, succeeds); - assert.strictEqual(reader.cursor, 1); - }); - it("should not allow any other character", () => { - const reader = new StringReader("test"); - const result = reader.expect("n"); - returnAssert(result, { - errors: [ - { code: "parsing.expected", range: { start: 0, end: 1 } } - ], - succeeds: false - }); - assert.strictEqual(reader.cursor, 0); - }); - it("should allow a multi character string", () => { - const reader = new StringReader("test"); - const result = reader.expect("tes"); - returnAssert(result, succeeds); - assert.strictEqual(reader.cursor, 3); - }); - it("should not allow an incorrect multi-character string", () => { - const reader = new StringReader("test"); - const result = reader.expect("not"); - returnAssert(result, { - errors: [ - { code: "parsing.expected", range: { start: 0, end: 3 } } - ], - succeeds: false - }); - assert.strictEqual(reader.cursor, 0); - }); - it("should give a suggestion of the string", () => { - const reader = new StringReader("te"); - const result = reader.expect("test"); - returnAssert(result, { - errors: [ - { code: "parsing.expected", range: { start: 0, end: 2 } } - ], - succeeds: false, - suggestions: [{ start: 0, text: "test" }] - }); - assert.strictEqual(reader.cursor, 0); + it("should work correctly for various inputs", () => { + const expectRead = (text: string, expected: string) => + new StringReader(text).expect(expected); + snapshot( + expectRead, + ["test", "t"], + ["test", "n"], + ["test", "tes"], + ["test", "not"], + ["te", "test"] + ); }); }); describe("readOption", () => { - it("should succeed for one of the options", () => { - const reader = new StringReader("test"); - const result = reader.readOption(["test", "other"]); - if ( - returnAssert(result, { - succeeds: true, - suggestions: ["test"] - }) - ) { - assert.strictEqual(result.data, "test"); - } - }); - it("should fail with an unknown value", () => { - const reader = new StringReader("test"); - const result = reader.readOption(["nottest", "other"]); - if ( - returnAssert(result, { - succeeds: false - }) - ) { - assert.strictEqual(result.data, "test"); - } + it("should work correctly for various inputs", () => { + const optionRead = (text: string, options: string[]) => + new StringReader(text).expectOption(...options); + snapshot( + optionRead, + ["test", ["test", "other"]], + ["test", ["nottest", "other"]] + ); }); }); describe("readWhileFunction()", () => { diff --git a/src/test/completions.test.ts b/src/test/completions.test.ts index 0919c1b..b4c8798 100644 --- a/src/test/completions.test.ts +++ b/src/test/completions.test.ts @@ -78,6 +78,7 @@ describe("ComputeCompletions()", () => { isIncomplete: true, items: [ { + insertTextFormat: InsertTextFormat.PlainText, kind: CompletionItemKind.Method, label: "nochildren", textEdit: { @@ -112,7 +113,7 @@ describe("ComputeCompletions()", () => { } } ] - }; // Default kind // Default kind + }; // Default kind result.items.sort((a, b) => b.label.length - a.label.length); assert.deepStrictEqual(result, expected); }); @@ -170,6 +171,7 @@ describe("ComputeCompletions()", () => { } }, { + insertTextFormat: InsertTextFormat.PlainText, kind: CompletionItemKind.Method, label: "chil", textEdit: { diff --git a/src/test/data/NOTICE.txt b/src/test/data/NOTICE.txt deleted file mode 100644 index ae33e89..0000000 --- a/src/test/data/NOTICE.txt +++ /dev/null @@ -1,2 +0,0 @@ -The Data Folder is the only folder which should contains references to accessing the file system. -Because of this, the `resources` folder contains example data for use with testing \ No newline at end of file diff --git a/src/test/data/cache.test.ts b/src/test/data/cache.test.ts deleted file mode 100644 index feee6ca..0000000 --- a/src/test/data/cache.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -import * as assert from "assert"; -import * as fs from "fs"; -import * as path from "path"; -// tslint:disable-next-line:no-implicit-dependencies This is only for testing -import * as rimraf from "rimraf"; -import { promisify } from "util"; - -import * as cache from "../../data/cache"; -import { Cacheable } from "../../data/types"; - -const rimrafAsync = promisify(rimraf); - -const cacheFolder = path.join(process.cwd(), "cache"); -const testData: Cacheable = { - blocks: { - "minecraft:chocolate": {} - }, - commands: { - children: { - hello: { - type: "literal" - } - }, - type: "root" - }, - meta_info: { version: "1.13" }, - registries: { "minecraft:items": new Set(["test", "test2"]) }, - resources: {} -} as any; -describe("Cache Management", () => { - after(async () => rimrafAsync(cacheFolder)); - describe("cacheData", () => { - it("should cache the data in the folder", async () => { - await cache.cacheData(testData); - const files = await promisify(fs.readdir)(cacheFolder); - assert.deepStrictEqual( - files.sort(), - [ - "commands.json", - "blocks.json", - "meta_info.json", - "registries.json", - "resources.json" - ].sort() - ); - }); - }); - describe("readCache", () => { - it("should be consistent with cacheData", async () => { - await cache.cacheData(testData); - const data = await cache.readCache(); - assert.deepStrictEqual(data, testData); - }); - }); -}); diff --git a/src/test/data/datapack-resource.test.ts b/src/test/data/datapack-resource.test.ts index 81a3a70..e9e6692 100644 --- a/src/test/data/datapack-resource.test.ts +++ b/src/test/data/datapack-resource.test.ts @@ -2,14 +2,9 @@ import * as assert from "assert"; import * as path from "path"; import { getPacksInfo } from "../../data/datapack-resources"; -import { Datapack, MinecraftResource, WorldInfo } from "../../data/types"; +import { Datapack, ResourceID, WorldInfo } from "../../data/types"; import { typed_keys } from "../../misc-functions/third_party/typed-keys"; -import { - assertMembers, - assertNamespaces, - convertToResource, - returnAssert -} from "../assertions"; +import { assertMembers, convertToResource, snapshot } from "../assertions"; import { emptyGlobal } from "../blanks"; // Tests are run from within the lib folder, but data is in the root @@ -44,7 +39,7 @@ describe("Datapack Resource Testing", () => { path.join(rootFolder, "test_world", "datapacks"), emptyGlobal ); - returnAssert(result, { succeeds: true, numMisc: 5 }); + snapshot(result); assert(result.misc.every(v => v.kind === "ClearError")); assertPacksInfo(result.data, datapacks, expected); }); @@ -70,10 +65,10 @@ function assertPacksInfo( assertMembers(keys, Object.keys(actual.data), (a, b) => a === b); for (const kind of keys) { - const resources = pack.data[kind] as MinecraftResource[]; - const actualResources = actual.data[kind] as MinecraftResource[]; + const resources = pack.data[kind] as ResourceID[]; + const actualResources = actual.data[kind] as ResourceID[]; assert(actualResources.every(v => v.pack === id)); - assertNamespaces(resources, actualResources); + snapshot(resources); } } } diff --git a/src/test/data/nbt.test.ts b/src/test/data/nbt.test.ts index 0b22113..355dc58 100644 --- a/src/test/data/nbt.test.ts +++ b/src/test/data/nbt.test.ts @@ -1,13 +1,13 @@ import * as assert from "assert"; import * as Long from "long"; -import { loadNBT } from "../../data/nbt/nbt-cache"; +import { loadWorldNBT } from "../../data/nbt/nbt-cache"; import { LevelData } from "../../data/nbt/nbt-types"; import { parse } from "../../data/nbt/parser"; import { readFileAsync } from "../../misc-functions"; +// This is a description of the nbt specification test file interface BigTest { - // This name is from the test file provided by Notch "byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))": number[]; byteTest: number; doubleTest: number; @@ -34,7 +34,6 @@ interface BigTest { } const bigtest: BigTest = { - // *thanks notch* "byteArrayTest (the first 1000 values of (n*n*255+n*7)%100, starting with n=0 (0, 62, 34, 16, 8, ...))": new Array< number >(1000) @@ -91,7 +90,7 @@ describe("(binary) nbt parser tests", () => { }); it("should parse level.dat", async () => { - const nbt = await loadNBT("test_data/test_world"); + const nbt = await loadWorldNBT("test_data/test_world"); if (!!nbt.level) { const data = nbt.level.Data; assert.strictEqual(data.version, 19133); diff --git a/src/test/misc-functions/group-resources.test.ts b/src/test/misc-functions/group-resources.test.ts index a982717..f5e8489 100644 --- a/src/test/misc-functions/group-resources.test.ts +++ b/src/test/misc-functions/group-resources.test.ts @@ -1,6 +1,8 @@ +/* +// TODO: This will be rewritten to use IdMap/IdSet import { getResourcesofType } from "../../misc-functions/group-resources"; import { CommandData } from "../../types"; -import { assertNamespaces, convertToResource } from "../assertions"; +import { convertToResource, snapshot } from "../assertions"; const dummyData: CommandData = { globalData: { @@ -71,3 +73,4 @@ describe("Group Resources (Misc)", () => { ); }); }); + */ diff --git a/src/test/misc-functions/namespace.test.ts b/src/test/misc-functions/namespace.test.ts index acf99ae..38ade7e 100644 --- a/src/test/misc-functions/namespace.test.ts +++ b/src/test/misc-functions/namespace.test.ts @@ -1,45 +1,41 @@ import * as assert from "assert"; -import { NamespacedName } from "../../data/types"; -import { - convertToNamespace, - namespacesEqual, - stringifyNamespace -} from "../../misc-functions/namespace"; +import { ID } from "../../data/types"; +import { convertToID, idsEqual, stringifyID } from "../../misc-functions/id"; describe("Namespace Functions (Misc)", () => { describe("namespacesEqual()", () => { it("should return true if the names are equal", () => { - const name: NamespacedName = { + const name: ID = { namespace: "namespace", path: "testpath" }; assert( - namespacesEqual(name, { + idsEqual(name, { namespace: "namespace", path: "testpath" }) ); }); it("should return false if the namespaces aren't equal", () => { - const name: NamespacedName = { + const name: ID = { namespace: "namespace1", path: "testpath" }; assert( - !namespacesEqual(name, { + !idsEqual(name, { namespace: "namespace2", path: "testpath" }) ); }); it("should return false if the paths aren't equal", () => { - const name: NamespacedName = { + const name: ID = { namespace: "namespace", path: "testpath1" }; assert( - !namespacesEqual(name, { + !idsEqual(name, { namespace: "namespace", path: "testpath2" }) @@ -48,101 +44,89 @@ describe("Namespace Functions (Misc)", () => { }); describe("convertToNamespace()", () => { it("should convert a regular namespaced name", () => { - const expected: NamespacedName = { + const expected: ID = { namespace: "namespace", path: "path" }; - assert.deepStrictEqual( - convertToNamespace("namespace:path"), - expected - ); + assert.deepStrictEqual(convertToID("namespace:path"), expected); }); it("should have no namespace if there is no namespace", () => { - const expected: NamespacedName = { path: "pathonly" }; - assert.deepStrictEqual(convertToNamespace("pathonly"), expected); + const expected: ID = { path: "pathonly" }; + assert.deepStrictEqual(convertToID("pathonly"), expected); }); it("should have no namespace if the namespace is empty", () => { // This bug/feature(?): https://bugs.mojang.com/browse/MC-125100 - const expected: NamespacedName = { path: "pathonly" }; - assert.deepStrictEqual(convertToNamespace(":pathonly"), expected); + const expected: ID = { path: "pathonly" }; + assert.deepStrictEqual(convertToID(":pathonly"), expected); }); }); describe("stringifyNamespace()", () => { it("should convert a normal namespace", () => { assert.strictEqual( - stringifyNamespace({ path: "path", namespace: "namespace" }), + stringifyID({ path: "path", namespace: "namespace" }), "namespace:path" ); }); it("should use the default namespace with a non-specified namespace", () => { - assert.strictEqual( - stringifyNamespace({ path: "path" }), - "minecraft:path" - ); + assert.strictEqual(stringifyID({ path: "path" }), "minecraft:path"); }); }); }); describe("convertToNamespace()", () => { it("should convert a regular namespaced name", () => { - const expected: NamespacedName = { + const expected: ID = { namespace: "namespace", path: "path" }; - assert.deepStrictEqual(convertToNamespace("namespace:path"), expected); + assert.deepStrictEqual(convertToID("namespace:path"), expected); }); it("should have no namespace if there is no namespace", () => { - const expected: NamespacedName = { path: "pathonly" }; - assert.deepStrictEqual(convertToNamespace("pathonly"), expected); + const expected: ID = { path: "pathonly" }; + assert.deepStrictEqual(convertToID("pathonly"), expected); }); it("should have no namespace if the namespace is empty", () => { // This bug/feature(?): https://bugs.mojang.com/browse/MC-125100 - const expected: NamespacedName = { path: "pathonly" }; - assert.deepStrictEqual(convertToNamespace(":pathonly"), expected); + const expected: ID = { path: "pathonly" }; + assert.deepStrictEqual(convertToID(":pathonly"), expected); }); }); describe("stringifyNamespace()", () => { it("should convert a normal namespace", () => { assert.strictEqual( - stringifyNamespace({ path: "path", namespace: "namespace" }), + stringifyID({ path: "path", namespace: "namespace" }), "namespace:path" ); }); it("should use the default namespace with a non-specified namespace", () => { - assert.strictEqual( - stringifyNamespace({ path: "path" }), - "minecraft:path" - ); + assert.strictEqual(stringifyID({ path: "path" }), "minecraft:path"); }); }); describe("convertToNamespace()", () => { it("should convert a regular namespaced name", () => { - const expected: NamespacedName = { + const expected: ID = { namespace: "namespace", path: "path" }; - assert.deepStrictEqual(convertToNamespace("namespace:path"), expected); + assert.deepStrictEqual(convertToID("namespace:path"), expected); }); it("should have no namespace if there is no namespace", () => { - const expected: NamespacedName = { path: "pathonly" }; - assert.deepStrictEqual(convertToNamespace("pathonly"), expected); + const expected: ID = { path: "pathonly" }; + assert.deepStrictEqual(convertToID("pathonly"), expected); }); it("should have no namespace if the namespace is empty", () => { // This bug/feature(?): https://bugs.mojang.com/browse/MC-125100 - const expected: NamespacedName = { path: "pathonly" }; - assert.deepStrictEqual(convertToNamespace(":pathonly"), expected); + const expected: ID = { path: "pathonly" }; + assert.deepStrictEqual(convertToID(":pathonly"), expected); }); }); describe("stringifyNamespace()", () => { it("should convert a normal namespace", () => { assert.strictEqual( - stringifyNamespace({ path: "path", namespace: "namespace" }), + stringifyID({ path: "path", namespace: "namespace" }), "namespace:path" ); }); it("should use the default namespace with a non-specified namespace", () => { - assert.strictEqual( - stringifyNamespace({ path: "path" }), - "minecraft:path" - ); + assert.strictEqual(stringifyID({ path: "path" }), "minecraft:path"); }); }); diff --git a/src/test/misc-functions/parsing/namespace.test.ts b/src/test/misc-functions/parsing/namespace.test.ts index 8ab3236..d150703 100644 --- a/src/test/misc-functions/parsing/namespace.test.ts +++ b/src/test/misc-functions/parsing/namespace.test.ts @@ -1,80 +1,16 @@ +/* +// TODO: This will be rewritten to use IdMap/IdSet import * as assert from "assert"; import { StringReader } from "../../../brigadier/string-reader"; -import { convertToNamespace } from "../../../misc-functions"; +import { convertToID } from "../../../misc-functions"; import { - namespaceStart, parseNamespace, parseNamespaceOption, readNamespaceText } from "../../../misc-functions/parsing/namespace"; -import { returnAssert } from "../../assertions"; -import { succeeds } from "../../blanks"; describe("Namespace Parsing Functions", () => { - describe("namespaceStarts()", () => { - describe("test namespace defined", () => { - it("should allow same namespace, starting path", () => { - assert( - namespaceStart( - convertToNamespace("test:testing"), - convertToNamespace("test:test") - ) - ); - }); - it("should disallow different namespace, starting path", () => { - assert( - !namespaceStart( - convertToNamespace("test:testing"), - convertToNamespace("other:test") - ) - ); - }); - }); - describe("test namespace undefined", () => { - it("should allow a starting path with the default namespace", () => { - assert( - namespaceStart( - convertToNamespace("minecraft:testing"), - convertToNamespace("test") - ) - ); - }); - it("should disallow a starting path with a non-default namespace", () => { - assert( - !namespaceStart( - convertToNamespace("other:testing"), - convertToNamespace("test") - ) - ); - }); - it("should allow a namespace starting with the path", () => { - assert( - namespaceStart( - convertToNamespace("testing:path"), - convertToNamespace("test") - ) - ); - }); - }); - - describe("base namespace undefined", () => { - it("should act as if there was a minecraft namespace", () => { - assert( - namespaceStart( - convertToNamespace("hello"), - convertToNamespace("minecraft:he") - ) - ); - assert( - namespaceStart( - convertToNamespace("hello"), - convertToNamespace("he") - ) - ); - }); - }); - }); describe("readNamespaceText()", () => { it("should read the namespace text", () => { const reader = new StringReader("mc:test[]"); @@ -86,7 +22,7 @@ describe("Namespace Parsing Functions", () => { it("should allow a parsed option, suggesting that option", () => { const reader = new StringReader("mc:succeeds"); const result = parseNamespaceOption(reader, [ - convertToNamespace("mc:succeeds") + convertToID("mc:succeeds") ]); if ( returnAssert(result, { @@ -111,7 +47,7 @@ describe("Namespace Parsing Functions", () => { it("should reject it if it's an invalid option", () => { const reader = new StringReader("mc:fails"); const result = parseNamespaceOption(reader, [ - convertToNamespace("mc:succeeds") + convertToID("mc:succeeds") ]); if (!returnAssert(result, { succeeds: false })) { assert.deepStrictEqual(result.data, { @@ -191,3 +127,4 @@ describe("Namespace Parsing Functions", () => { }); }); }); + */ diff --git a/src/test/misc-functions/parsing/nmsp-tag.test.ts b/src/test/misc-functions/parsing/nmsp-tag.test.ts index c84e891..f1db9e3 100644 --- a/src/test/misc-functions/parsing/nmsp-tag.test.ts +++ b/src/test/misc-functions/parsing/nmsp-tag.test.ts @@ -1,16 +1,14 @@ +/* +// TODO: This will be rewritten to use IdMap/IdSet import * as assert from "assert"; import { CommandErrorBuilder } from "../../../brigadier/errors"; import { StringReader } from "../../../brigadier/string-reader"; -import { convertToNamespace, namespacesEqual } from "../../../misc-functions"; +import { convertToID, idsEqual } from "../../../misc-functions"; import { parseNamespaceOrTag } from "../../../misc-functions/parsing/nmsp-tag"; import { ParserInfo } from "../../../types"; -import { - assertNamespaces, - convertToResource, - returnAssert -} from "../../assertions"; -import { blankproperties, succeeds } from "../../blanks"; +import { convertToResource } from "../../assertions"; +import { blankproperties } from "../../blanks"; const data: ParserInfo = { ...blankproperties, @@ -58,10 +56,7 @@ describe("parseNamespaceOrTag", () => { ); if (returnAssert(result, succeeds)) { assert( - namespacesEqual( - result.data.parsed, - convertToNamespace("minecraft:stone") - ) + idsEqual(result.data.parsed, convertToID("minecraft:stone")) ); } }); @@ -143,3 +138,4 @@ describe("parseNamespaceOrTag", () => { } }); }); + */ diff --git a/src/test/parse.test.ts b/src/test/parse.test.ts index 6320435..05dfd56 100644 --- a/src/test/parse.test.ts +++ b/src/test/parse.test.ts @@ -4,19 +4,17 @@ import { GlobalData } from "../data/types"; import { parseCommand } from "../parse"; import { ParseNode, StoredParseResult } from "../types"; -import { assertErrors, ErrorInfo } from "./assertions"; +import { snapshot } from "./assertions"; import { dummyParser } from "./parsers/tests/dummy1"; const fakeGlobal: GlobalData = {} as any; -function assertParse( - res: StoredParseResult | void, - errors: ErrorInfo[], - nodes: ParseNode[] -): void { +function assertParse(res: StoredParseResult | void, nodes: ParseNode[]): void { assert.notStrictEqual(res, undefined); const result = (res as any) as StoredParseResult; - assertErrors(errors, result.errors); + if (res) { + snapshot(res); + } const newNodes = result.nodes.slice(); for (const node of nodes) { assert( @@ -92,47 +90,27 @@ describe("parseCommand()", () => { it("should parse an executable, valid, command as such", () => { const result = parseCommand("hel", singleArgData, undefined); - assertParse( - result, - [], - [{ context: {}, final: {}, low: 0, high: 3, path: ["test1"] }] - ); + assertParse(result, [ + { context: {}, final: {}, low: 0, high: 3, path: ["test1"] } + ]); }); it("should parse an executable, valid, command as such prefixed by whitespace", () => { const result = parseCommand(" hel", singleArgData, undefined); - assertParse( - result, - [], - [{ context: {}, final: {}, low: 3, high: 6, path: ["test1"] }] - ); + assertParse(result, [ + { context: {}, final: {}, low: 3, high: 6, path: ["test1"] } + ]); }); it("should return a warning from a command which cannot be executed", () => { const result = parseCommand("hello", singleArgData, undefined); - assertParse( - result, - [ - { - code: "parsing.command.executable", - range: { start: 0, end: 5 } - } - ], - [{ low: 0, high: 5, path: ["test2"], context: {}, final: {} }] - ); + assertParse(result, [ + { low: 0, high: 5, path: ["test2"], context: {}, final: {} } + ]); }); it("should add an error if there text at the start not matched by a node", () => { const result = parseCommand("hi", singleArgData, undefined); - assertParse( - result, - [ - { - code: "command.parsing.matchless", - range: { start: 0, end: 2 } - } - ], - [] - ); + assertParse(result, []); }); }); @@ -169,53 +147,35 @@ describe("parseCommand()", () => { it("should only add a space between nodes", () => { const result = parseCommand("hel hel", multiArgData, undefined); - assertParse( - result, - [ - { - code: "parsing.command.executable", - range: { start: 0, end: 7 } - } - ], - [ - { - context: {}, - final: undefined, // Limitation of assert api - high: 3, - low: 0, - path: ["test1"] - }, - { - context: {}, - final: {}, - high: 7, - low: 4, - path: ["test1", "testchild1"] - } - ] - ); + assertParse(result, [ + { + context: {}, + final: undefined, // Limitation of assert api + high: 3, + low: 0, + path: ["test1"] + }, + { + context: {}, + final: {}, + high: 7, + low: 4, + path: ["test1", "testchild1"] + } + ]); }); it("should not add a node when a node which follows fails", () => { const result = parseCommand("hel hel1", multiArgData, undefined); - assertParse( - result, - [ - { - code: "command.parsing.matchless", - range: { start: 4, end: 8 } - } - ], - [ - { - context: {}, - final: {}, - high: 3, - low: 0, - path: ["test1"] - } - ] - ); + assertParse(result, [ + { + context: {}, + final: {}, + high: 3, + low: 0, + path: ["test1"] + } + ]); }); }); }); diff --git a/src/test/parsers/brigadier/bool.test.ts b/src/test/parsers/brigadier/bool.test.ts index dba31e9..5a63c3e 100644 --- a/src/test/parsers/brigadier/bool.test.ts +++ b/src/test/parsers/brigadier/bool.test.ts @@ -1,69 +1,10 @@ import { boolParser } from "../../../parsers/brigadier"; -import { testParser } from "../../assertions"; +import { snapshot, testParser } from "../../assertions"; -const boolTester = testParser(boolParser); -const testWithBasicProps = boolTester(); +const boolTester = testParser(boolParser)(); describe("Boolean Argument Parser", () => { - describe("parse()", () => { - it("should not error when it is reading true", () => { - testWithBasicProps("true", { - succeeds: true, - suggestions: ["true"] - }); - }); - it("should not error when it is reading false", () => { - testWithBasicProps("false", { - succeeds: true, - suggestions: ["false"] - }); - }); - it("should error if it is not reading true or false", () => { - testWithBasicProps("notbool", { - errors: [ - { - code: "parsing.bool.invalid", - range: { start: 0, end: 7 } - } - ], - succeeds: false - }); - }); - }); - it("should suggest false for a string beginning with it", () => { - testWithBasicProps("fal", { - errors: [ - { - code: "parsing.bool.invalid", - range: { start: 0, end: 3 } - } - ], - succeeds: false, - suggestions: ["false"] - }); - }); - it("should suggest true for a string beginning with it", () => { - testWithBasicProps("tru", { - errors: [ - { - code: "parsing.bool.invalid", - range: { start: 0, end: 3 } - } - ], - succeeds: false, - suggestions: ["true"] - }); - }); - it("should suggest both for an empty string", () => { - testWithBasicProps("", { - errors: [ - { - code: "parsing.bool.invalid", - range: { start: 0, end: 0 } - } - ], - succeeds: false, - suggestions: ["true", "false"] - }); + it("should work correctly for various inputs", () => { + snapshot(boolTester, "true", "false", "notbool", "fal", "tru", ""); }); }); diff --git a/src/test/parsers/brigadier/double.test.ts b/src/test/parsers/brigadier/double.test.ts index 91d3f6f..0fb979c 100644 --- a/src/test/parsers/brigadier/double.test.ts +++ b/src/test/parsers/brigadier/double.test.ts @@ -1,68 +1,44 @@ -import * as assert from "assert"; - import { doubleParser } from "../../../parsers/brigadier/double"; -import { testParser } from "../../assertions"; +import { snapshot, testParser } from "../../assertions"; import { blankproperties } from "../../blanks"; const doubleTester = testParser(doubleParser); describe("Double Argument Parser", () => { - function validDoubleTests( - s: string, - expectedNum: number, - numEnd: number - ): void { + function validDoubleTests(s: string, expectedNum: number): void { it("should succeed with no constraints", () => { - const result = doubleTester(blankproperties)(s, { - succeeds: true - }); - assert.strictEqual(result[1].cursor, numEnd); + snapshot(doubleTester(blankproperties), s); }); it("should reject a number less than the minimum", () => { - const result = doubleTester({ - ...blankproperties, - node_properties: { min: expectedNum + 1 } - })(s, { - errors: [ - { - code: "argument.double.low", - range: { start: 0, end: numEnd } - } - ], - succeeds: true - }); - assert.strictEqual(result[1].cursor, numEnd); + snapshot( + doubleTester({ + ...blankproperties, + node_properties: { min: expectedNum + 1 } + }), + s + ); }); it("should reject a number more than the maximum", () => { - const result = doubleTester({ - ...blankproperties, - node_properties: { - max: expectedNum - 1 - } - })(s, { - errors: [ - { - code: "argument.double.big", - range: { - end: numEnd, - start: 0 - } + snapshot( + doubleTester({ + ...blankproperties, + node_properties: { + max: expectedNum - 1 } - ], - succeeds: true - }); - assert.strictEqual(result[1].cursor, numEnd); + }), + s + ); }); } describe("valid integer", () => { - validDoubleTests("1234", 1234, 4); + validDoubleTests("1234", 1234); }); describe("valid integer with space", () => { - validDoubleTests("1234 ", 1234, 4); + validDoubleTests("1234 ", 1234); }); describe("valid float with `.`", () => { - validDoubleTests("1234.5678", 1234.5678, 9); + validDoubleTests("1234.5678", 1234.5678); }); describe("valid float with `.` and space", () => { - validDoubleTests("1234.5678 ", 1234.5678, 9); + validDoubleTests("1234.5678 ", 1234.5678); }); }); diff --git a/src/test/parsers/brigadier/float.test.ts b/src/test/parsers/brigadier/float.test.ts index b4de382..a282e40 100644 --- a/src/test/parsers/brigadier/float.test.ts +++ b/src/test/parsers/brigadier/float.test.ts @@ -1,96 +1,53 @@ -import * as assert from "assert"; - import { floatParser } from "../../../parsers/brigadier"; -import { testParser } from "../../assertions"; -import { blankproperties } from "../../blanks"; +import { snapshot, testParser } from "../../assertions"; const floatTester = testParser(floatParser); describe("Float Argument Parser", () => { - function validFloatTests( - s: string, - expectedNum: number, - numEnd: number - ): void { + function validFloatTests(s: string, expectedNum: number): void { it("should succeed with no constraints", () => { - const result = floatTester(blankproperties)(s, { - succeeds: true - }); - assert.strictEqual(result[1].cursor, numEnd); + snapshot(floatTester(), s); }); it("should reject a number less than the minimum", () => { - const result = floatTester({ - ...blankproperties, - node_properties: { min: expectedNum + 1 } - })(s, { - errors: [ - { - code: "argument.float.low", - range: { start: 0, end: numEnd } - } - ], - succeeds: true - }); - assert.strictEqual(result[1].cursor, numEnd); + snapshot( + floatTester({ + node_properties: { min: expectedNum + 1 } + }), + s + ); }); it("should reject a number more than the maximum", () => { - const result = floatTester({ - ...blankproperties, - node_properties: { - max: expectedNum - 1 - } - })(s, { - errors: [ - { - code: "argument.float.big", - range: { - end: numEnd, - start: 0 - } + snapshot( + floatTester({ + node_properties: { + max: expectedNum - 1 } - ], - succeeds: true - }); - assert.strictEqual(result[1].cursor, numEnd); + }), + s + ); }); } describe("valid integer", () => { - validFloatTests("1234", 1234, 4); + validFloatTests("1234", 1234); }); describe("valid integer with space", () => { - validFloatTests("1234 ", 1234, 4); + validFloatTests("1234 ", 1234); }); describe("valid float with `.`", () => { - validFloatTests("1234.5678", 1234.5678, 9); + validFloatTests("1234.5678", 1234.5678); }); describe("valid float with `.` and space", () => { - validFloatTests("1234.5678 ", 1234.5678, 9); + validFloatTests("1234.5678 ", 1234.5678); }); it("should fail when the number is bigger than the java maximum float", () => { - floatTester(blankproperties)( - "1000000000000000000000000000000000000000000000000000000000000", - { - errors: [ - { - code: "argument.float.big", - range: { start: 0, end: 61 } - } - ], - succeeds: true - } + snapshot( + floatTester(), + "1000000000000000000000000000000000000000000000000000000000000" ); }); it("should fail when the number is less than the java minimum float", () => { - floatTester(blankproperties)( - "-1000000000000000000000000000000000000000000000000000000000000", - { - errors: [ - { - code: "argument.float.low", - range: { start: 0, end: 62 } - } - ], - succeeds: true - } + snapshot( + floatTester(), + "-1000000000000000000000000000000000000000000000000000000000000" ); }); }); diff --git a/src/test/parsers/brigadier/integer.test.ts b/src/test/parsers/brigadier/integer.test.ts index 6a908b8..18deca3 100644 --- a/src/test/parsers/brigadier/integer.test.ts +++ b/src/test/parsers/brigadier/integer.test.ts @@ -1,83 +1,41 @@ -import * as assert from "assert"; - import { intParser } from "../../../parsers/brigadier"; -import { testParser } from "../../assertions"; +import { snapshot, testParser } from "../../assertions"; const integerTest = testParser(intParser); describe("Integer Argument Parser", () => { - function validIntTests( - s: string, - expectedNum: number, - numEnd: number - ): void { + function validIntTests(s: string, expectedNum: number): void { it("should succeed with an unconstrained value", () => { - const result = integerTest()(s, { - succeeds: true - }); - assert.strictEqual(result[1].cursor, numEnd); + snapshot(integerTest(), s); }); it("should fail with a value less than the minimum", () => { - integerTest({ - node_properties: { min: expectedNum + 1 } - })(s, { - errors: [ - { - code: "argument.integer.low", - range: { start: 0, end: numEnd } - } - ], - succeeds: true - }); + snapshot( + integerTest({ node_properties: { min: expectedNum + 1 } }), + s + ); }); it("should fail with a value more than the maximum", () => { - integerTest({ - node_properties: { max: expectedNum - 1 } - })(s, { - errors: [ - { - code: "argument.integer.big", - range: { start: 0, end: numEnd } - } - ], - succeeds: true - }); + snapshot( + integerTest({ + node_properties: { max: expectedNum - 1 } + }), + s + ); }); } describe("valid integer", () => { - validIntTests("1234", 1234, 4); + validIntTests("1234", 1234); }); describe("valid integer with space", () => { - validIntTests("1234 ", 1234, 4); + validIntTests("1234 ", 1234); }); it("should fail when the integer is bigger than the java max", () => { - integerTest()("1000000000000000", { - errors: [ - { - code: "argument.integer.big", - range: { start: 0, end: 16 } - } - ], - succeeds: true - }); + snapshot(integerTest(), "1000000000000000"); }); it("should fail when the integer is less than the java min", () => { - integerTest()("-1000000000000000", { - errors: [ - { - code: "argument.integer.low", - range: { start: 0, end: 17 } - } - ], - succeeds: true - }); + snapshot(integerTest(), "-1000000000000000"); }); it("should fail when there is an invalid integer", () => { - integerTest()("notint", { - errors: [ - { code: "parsing.int.expected", range: { start: 0, end: 6 } } - ], - succeeds: false - }); + snapshot(integerTest(), "notint"); }); }); diff --git a/src/test/parsers/brigadier/string.test.ts b/src/test/parsers/brigadier/string.test.ts index 7b72d88..f180675 100644 --- a/src/test/parsers/brigadier/string.test.ts +++ b/src/test/parsers/brigadier/string.test.ts @@ -1,40 +1,23 @@ -import * as assert from "assert"; - import { stringParser } from "../../../parsers/brigadier"; -import { testParser } from "../../assertions"; -import { succeeds } from "../../blanks"; +import { snapshot, testParser } from "../../assertions"; const stringTest = testParser(stringParser); describe("String Argument Parser", () => { - describe("parse()", () => { - it("should read to the end with a greedy string", () => { - const result = stringTest({ + it("should work with a greedy string", () => { + snapshot( + stringTest({ node_properties: { type: "greedy" } - })('test space :"-)(*', succeeds); - assert.strictEqual(result[1].cursor, 17); - }); - describe("Phrase String", () => { - const tester = stringTest({ node_properties: { type: "phrase" } }); - it("should read an unquoted string section", () => { - const result = tester('test space :"-)(*', succeeds); - assert.strictEqual(result[1].cursor, 4); - }); - it("should read a quoted string section", () => { - const result = tester('"quote test" :"-)(*', succeeds); - assert.strictEqual(result[1].cursor, 12); - }); - }); - describe("Word String", () => { - const tester = stringTest({ node_properties: { type: "word" } }); - it("should read only an unquoted string section", () => { - const result = tester('test space :"-)(*', succeeds); - assert.strictEqual(result[1].cursor, 4); - }); - it("should not read a quoted string section", () => { - const result = tester('"quote test" :"-)(*', succeeds); - assert.strictEqual(result[1].cursor, 0); - }); - }); + }), + 'test space :"-)(*' + ); + }); + it("should work for a phrase string", () => { + const tester = stringTest({ node_properties: { type: "phrase" } }); + snapshot(tester, 'test space :"-)(*', '"quote test" :"-)(*'); + }); + it("should work for a word string", () => { + const tester = stringTest({ node_properties: { type: "word" } }); + snapshot(tester, 'test space :"-)(*', '"quote test" :"-)(*'); }); }); diff --git a/src/test/parsers/literal.test.ts b/src/test/parsers/literal.test.ts index 182f12c..57332a1 100644 --- a/src/test/parsers/literal.test.ts +++ b/src/test/parsers/literal.test.ts @@ -1,42 +1,10 @@ -import * as assert from "assert"; - import { literalParser } from "../../parsers/literal"; -import { testParser } from "../assertions"; +import { snapshot, testParser } from "../assertions"; const literalTest = testParser(literalParser)({ path: ["test"] }); describe("Literal Argument Parser", () => { - describe("literal correct", () => { - it("should succeed, suggesting the string", () => { - const result = literalTest("test", { - succeeds: true, - suggestions: ["test"] - }); - assert.strictEqual(result[1].cursor, 4); - }); - it("should set the cursor to after the string when it doesn't reach the end", () => { - const result = literalTest("test ", { - succeeds: true - }); - assert.strictEqual(result[1].cursor, 4); - }); - }); - describe("literal not matching", () => { - it("should fail when the first character doesn't match", () => { - literalTest("fail ", { - succeeds: false - }); - }); - it("should fail when the last character doesn't match", () => { - literalTest("tesnot", { - succeeds: false - }); - }); - it("should suggest the string if the start is given", () => { - literalTest("tes", { - succeeds: false, - suggestions: ["test"] - }); - }); + it("should correctly handle various input", () => { + snapshot(literalTest, "test", "test ", "fail ", "tesnot", "tes"); }); }); diff --git a/src/test/parsers/minecraft/block.test.ts b/src/test/parsers/minecraft/block.test.ts index 4e82c0a..208d837 100644 --- a/src/test/parsers/minecraft/block.test.ts +++ b/src/test/parsers/minecraft/block.test.ts @@ -1,14 +1,7 @@ -import { ok } from "assert"; - -import { loadNBTDocs } from "../../../data/noncached"; import { GlobalData } from "../../../data/types"; import { parseBlockArgument } from "../../../parsers/minecraft/block"; import { CommandData, Parser } from "../../../types"; -import { - convertToResource, - SuggestedOption, - testParser -} from "../../assertions"; +import { convertToResource, snapshot, testParser } from "../../assertions"; import { blankproperties } from "../../blanks"; import { snbtTestData } from "./nbt/test-data"; @@ -83,66 +76,8 @@ describe("sharedBlockParser", () => { node_properties: { tags: true } }); plainBlockTests(test); - it("should parse a plain tag", () => { - test("#test:empty", { - start: 1, - succeeds: true, - suggestions: [ - "test:empty", - "test:empty_values", - { - start: 11, - text: "{" - }, - { - start: 11, - text: "[" - } - ] - }); - }); - it("should give an error for an unknown tag", () => { - test("#test:unknown", { - errors: [ - { - code: "arguments.block.tag.unknown", - range: { start: 0, end: 13 } - } - ], - succeeds: true, - suggestions: [ - { - start: 13, - text: "{" - }, - { - start: 13, - text: "[" - } - ] - }); - }); - it("should suggest the correct properties", () => { - const [result] = test("#test:plain[", { - errors: [ - { - code: "argument.block.property.unclosed", - range: { start: 11, end: 12 } - } - ], - numActions: 1, - start: 12, - succeeds: false, - suggestions: [ - "otherprop", - "prop1", - "prop", - "state", - { start: 11, text: "[" }, - "]" - ] - }); - ok(result.actions.every(a => a.type === "hover")); + it("should correctly parse various tag inputs", () => { + snapshot(test, "#test:empty", "#test:unknown", "#test:plain["); }); }); describe("Tags not allowed", () => { @@ -152,28 +87,12 @@ describe("sharedBlockParser", () => { node_properties: { tags: false } }); plainBlockTests(test); - it("should give an error when tags are used", () => { - test("#minecraft:anything", { - errors: [ - { - code: "argument.block.tag.disallowed", - range: { start: 0, end: 19 } - } - ], - succeeds: false - }); - }); - // N.B properties are never actually parsed in this case - it("should not give further errors when properties are used", () => { - test("#minecraft:anything[anyprop=value]", { - errors: [ - { - code: "argument.block.tag.disallowed", - range: { start: 0, end: 19 } - } - ], - succeeds: false - }); + it("should do the right thing when tags are provided", () => { + snapshot( + test, + "#minecraft:anything", + "#minecraft:anything[anyprop=value]" + ); }); }); @@ -193,377 +112,41 @@ describe("sharedBlockParser", () => { } }); - it("should parse correctly for a regular block", () => { - const tester = blockArgumentTester({ - data: { - globalData: ({ - blocks: { - "minecraft:chest": {} - }, - nbt_docs: loadNBTDocs() - } as any) as GlobalData - } - }); - const suggestions = [ - "id", - "x", - "y", - "z", - "Items: [", - "LootTable", - "LootTableSeed", - "CustomName", - "Lock" - ]; - tester("minecraft:chest{", { - errors: [ - { - code: "argument.nbt.compound.nokey", - range: { - end: 16, - start: 16 - } - } - ], - numActions: 1, - succeeds: true, - suggestions: [ - { - start: 15, - text: "{" - }, - { - start: 16, - text: "}" - }, - ...suggestions.map(v => ({ - start: 16, - text: v - })) - ] - }); - }); - it("should parse correctly with NBT", () => { - test('langserver:nbt{customTag:"Hello World"}', { - numActions: 4, // Open bracket, key, value, close bracket - succeeds: true, - suggestions: [ - { - start: 38, - text: "}" - } - ] - }); - }); - it("should parse correctly with properties and NBT", () => { - test('langserver:nbt_prop[prop=1]{customTag:"Hello World"}', { - succeeds: true, - suggestions: [ - { - start: 51, - text: "}" - } - ] - }); - }); - it("should give the correct suggestions for tag names", () => { - test("langserver:nbt{", { - errors: [ - { - code: "argument.nbt.compound.nokey", - range: { - end: 15, - start: 15 - // Could argue 14 is better, but this gets messy - // If we decide this is better we should change it. - } - } - ], - numActions: 1, - succeeds: true, - suggestions: [ - { - start: 15, - text: "customTag" - }, - { - start: 14, - text: "{" - }, - { - start: 15, - text: "}" - } - ] - }); - }); - it("should suggest a `{` if there is none at the end", () => { - test("langserver:nbt_two", { - succeeds: true, - suggestions: [ - { - start: 0, - text: "langserver:nbt_two" - }, - { - start: 18, - text: "{" - }, - { - start: 18, - text: "[" - } - ] - }); - }); - it("should give the correct tag names for merged child_ref", () => { - test("langserver:nbt_two{", { - errors: [ - { - code: "argument.nbt.compound.nokey", - range: { - end: 19, - start: 19 - } - } - ], - numActions: 1, - succeeds: true, - suggestions: [ - { - start: 18, - text: "{" - }, - { - start: 19, - text: "}" - }, - { - start: 19, - text: "key0" - }, - { - start: 19, - text: "key1" - } - ] - }); + it("should work correctly for various input with nbt", () => { + snapshot( + test, + 'langserver:nbt{customTag:"Hello World"}', + 'langserver:nbt{customTag:"Hello World"}', + 'langserver:nbt_prop[prop=1]{customTag:"Hello World"}', + "langserver:nbt{", + "langserver:nbt_two", + "langserver:nbt_two{" + ); }); }); }); function plainBlockTests(test: ReturnType): void { - it("should allow a plain block", () => { - test("langserver:noprops", { - succeeds: true, - suggestions: [ - "langserver:noprops", - { start: 18, text: "{" }, - { start: 18, text: "[" } - ] - }); - }); - it("should successfully parse an empty properties", () => { - test("langserver:noprops[]", { - succeeds: true, - suggestions: [{ start: 20, text: "{" }, { start: 19, text: "]" }] - }); - }); - it("should successfully parse an empty properties with whitespace", () => { - test("langserver:noprops[ ]", { - succeeds: true, - suggestions: [{ start: 22, text: "{" }, { start: 21, text: "]" }] - }); - }); - it("should successfully parse a single block's properties", () => { - test("langserver:props[prop=value]", { - succeeds: true, - suggestions: [{ start: 28, text: "{" }, { start: 27, text: "]" }] - }); - }); - it("should successfully parse a single block's properties with quotes", () => { - test('langserver:props["prop"="value"]', { - succeeds: true, - suggestions: [{ start: 32, text: "{" }, { start: 31, text: "]" }] - }); - }); - it("should give the correct error for an unknown property", () => { - test("langserver:props[unknown=value]", { - errors: [ - { - code: "argument.block.property.unknown", - range: { start: 17, end: 24 } - } - ], - succeeds: true, - suggestions: [{ start: 31, text: "{" }, { start: 30, text: "]" }] - }); - }); - it("should give an error for an incorrect property value", () => { - test("langserver:props[prop=unknown]", { - errors: [ - { - code: "argument.block.property.invalid", - range: { start: 22, end: 29 } - } - ], - succeeds: true, - suggestions: [{ start: 30, text: "{" }, { start: 29, text: "]" }] - }); - }); - it("should allow multiple properties", () => { - test("langserver:multi[otherprop=propvalue,prop1=other]", { - succeeds: true, - suggestions: [{ start: 49, text: "{" }, { start: 48, text: "]" }] - }); - }); - it("should give an error for duplicate properties", () => { - test("langserver:multi[otherprop=propvalue,otherprop=lang]", { - errors: [ - { - code: "argument.block.property.duplicate", - range: { start: 37, end: 46 } - } - ], - succeeds: true, - suggestions: [{ start: 52, text: "{" }, { start: 51, text: "]" }] - }); - }); - it("should give an error when the properties are not closed", () => { - test("langserver:multi[otherprop=lang", { - errors: [ - { - code: "argument.block.property.unclosed", - range: { start: 16, end: 31 } - } - ], - succeeds: false, - suggestions: [ - { start: 27, text: "lang" }, - { start: 31, text: "]" }, - { start: 31, text: "," } - ] - }); - }); - it("should give the correct block suggestions", () => { - test("langserver:", { - errors: [ - { - code: "argument.block.id.invalid", - range: { start: 0, end: 11 } - } - ], - succeeds: true, // Even though is invalid, it could be valid if there was a block "langserver:"" - suggestions: [ - "langserver:multi", - "langserver:noprops", - "langserver:props", - { - start: 11, - text: "{" - }, - { - start: 11, - text: "[" - } - ] - }); - }); - it("should suggest the blocks with both the namespace and in the minecraft namespace", () => { - test("lang", { - succeeds: true, - suggestions: [ - "langserver:multi", - "langserver:noprops", - "langserver:props", - "minecraft:lang", - { - start: 4, - text: "{" - }, - { - start: 4, - text: "[" - } - ] - }); - }); - it("should suggest the correct property names", () => { - test("langserver:multi[", { - errors: [ - { - code: "argument.block.property.unclosed", - range: { start: 16, end: 17 } - } - ], - start: 17, - succeeds: false, - suggestions: ["prop1", "otherprop", "]", { start: 16, text: "[" }] - }); - }); - it("should give an error when there is no value given", () => { - test("langserver:props[prop", { - errors: [ - { - code: "argument.block.property.novalue", - range: { start: 17, end: 21 } - } - ], - succeeds: false, - suggestions: [{ start: 17, text: "prop" }, { start: 21, text: "=" }] - }); - }); - it("should give an error when there is no value given", () => { - test('langserver:props["prop"extra', { - errors: [ - { - code: "argument.block.property.novalue", - range: { start: 17, end: 23 } - } - ], - succeeds: false - }); - }); - it("should give the correct suggestions for an empty property value", () => { - test("langserver:props[prop=", { - errors: [ - { - code: "argument.block.property.unclosed", - range: { start: 16, end: 22 } - } - ], - start: 22, - succeeds: false, - suggestions: [ - "value", - "value2", - "other", - "]", - ",", - { start: 21, text: "=" } - ] - }); - }); - it("should give the correct suggestions for a started property value", () => { - test("langserver:props[prop=val", { - errors: [ - { - code: "argument.block.property.unclosed", - range: { start: 16, end: 25 } - }, - { - code: "argument.block.property.invalid", - range: { start: 22, end: 25 } - } - ], - start: 22, - succeeds: false, - suggestions: [ - "value", - "value2", - { start: 25, text: "]" }, - { start: 25, text: "," } - ] - }); + it("should work correctly for various inputs involving plain blocks", () => { + snapshot( + test, + "langserver:noprops", + "langserver:noprops[]", + "langserver:noprops[ ]", + "langserver:props[prop=value]", + 'langserver:props["prop"="value"]', + "langserver:props[unknown=value]", + "langserver:props[prop=unknown]", + "langserver:multi[otherprop=propvalue,prop1=other]", + "langserver:multi[otherprop=propvalue,otherprop=lang]", + "langserver:multi[otherprop=lang", + "langserver:", + "lang", + "langserver:multi[", + "langserver:props[prop", + 'langserver:props["prop"extra', + "langserver:props[prop=", + "langserver:props[prop=val" + ); }); } diff --git a/src/test/parsers/minecraft/coord.test.ts b/src/test/parsers/minecraft/coord.test.ts index 7ec5660..2784e0d 100644 --- a/src/test/parsers/minecraft/coord.test.ts +++ b/src/test/parsers/minecraft/coord.test.ts @@ -1,137 +1,50 @@ import { CoordParser } from "../../../parsers/minecraft/coordinates"; -import { testParser } from "../../assertions"; -import { succeeds } from "../../blanks"; +import { snapshot, testParser } from "../../assertions"; describe("Coordinate tests", () => { - describe("parse()", () => { - describe("settings: {count: 2, float: false, local: true }", () => { - const parser = new CoordParser({ - count: 2, - float: false, - local: true - }); - const tester = testParser(parser)(); - ["~1 ~", "2 3", "5 ~", "~ ~"].forEach(v => - it(`should work for coord ${v}`, () => { - tester(v, succeeds); - }) - ); - it("should return a mix error for coord '1 ^4'", () => { - tester("1 ^4", { - errors: [ - { - code: "argument.pos.mixed", - range: { - end: 4, - start: 2 - } - } - ], - succeeds: true - }); - }); - it("should return a mix error for coord '~1 ^4'", () => { - tester("~1 ^4", { - errors: [ - { - code: "argument.pos.mixed", - range: { - end: 5, - start: 3 - } - } - ], - succeeds: true - }); - }); - it("should return an incomplete error for coord '~2 '", () => { - tester("~2 ", { - errors: [ - { - code: "argument.pos.incomplete", - range: { - end: 3, - start: 0 - } - } - ], - succeeds: false, - suggestions: [ - { - start: 3, - text: "~" - } - ] - }); - }); - it("should return an invalid int error for coord '1.3 1'", () => { - tester("1.3 1", { - errors: [ - { - code: "parsing.int.invalid", - range: { - end: 3, - start: 0 - } - } - ], - succeeds: false - }); - }); + it("should work for various inputs with settings: {count: 2, float: false, local: true }", () => { + const parser = new CoordParser({ + count: 2, + float: false, + local: true }); - describe("settings: {count: 3, float: true , local: true }", () => { - const parser = new CoordParser({ - count: 3, - float: true, - local: true - }); - const tester = testParser(parser)(); - [ - "1 2 3", - "~1 ~2 3", - "^1 ^ ^2", - "~ ~ ~", - "1.2 3 ~1", - "^.1 ^ ^3" - ].forEach(v => - it(`sould work for coord ${v}`, () => { - tester(v, succeeds); - }) - ); + const tester = testParser(parser)(); + snapshot( + tester, + "~1 ~", + "2 3", + "5 ~", + "~ ~", + "1 ^4", + "~1 ^4", + "~2 ", + "1.3 1" + ); + }); + it("should work for various inputs with settings: {count: 3, float: true , local: true }", () => { + const parser = new CoordParser({ + count: 3, + float: true, + local: true }); - describe("settings: {count: 2, float: true , local: false}", () => { - const parser = new CoordParser({ - count: 2, - float: true, - local: false - }); - const tester = testParser(parser)(); - ["~1 ~", "2 3", "5 ~20", "~ ~"].forEach(v => - it(`should work for coord ${v}`, () => { - tester(v, succeeds); - }) - ); - it("should return a no local error for coord '^ ^3'", () => { - tester("^ ^3", { - errors: [ - { - code: "argument.pos.nolocal", - range: { - end: 1, - start: 0 - } - }, - { - code: "argument.pos.nolocal", - range: { - end: 3, - start: 2 - } - } - ], - succeeds: true - }); - }); + const tester = testParser(parser)(); + snapshot( + tester, + "1 2 3", + "~1 ~2 3", + "^1 ^ ^2", + "~ ~ ~", + "1.2 3 ~1", + "^.1 ^ ^3" + ); + }); + it("should work for various inputs with settings: {count: 2, float: true , local: false}", () => { + const parser = new CoordParser({ + count: 2, + float: true, + local: false }); + const tester = testParser(parser)(); + snapshot(tester, "~1 ~", "2 3", "5 ~20", "~ ~", "^ ^3"); }); }); diff --git a/src/test/parsers/minecraft/entity.test.ts b/src/test/parsers/minecraft/entity.test.ts index 07bef02..fbd2e39 100644 --- a/src/test/parsers/minecraft/entity.test.ts +++ b/src/test/parsers/minecraft/entity.test.ts @@ -2,58 +2,26 @@ import { NBTNode } from "mc-nbt-paths"; import { EntityBase } from "../../../parsers/minecraft/entity"; import { CommandData } from "../../../types"; -import { testParser } from "../../assertions"; +import { snapshot, testParser } from "../../assertions"; describe("entity parser", () => { - describe("player name", () => { + it("should parse player names", () => { const parser = new EntityBase(false, false); - const tester = testParser(parser)(); - it("should parse a player name", () => { - tester("FooBar", { - succeeds: true - }); - }); - it("should fail if the name is empty", () => { - tester("", { - succeeds: false - }); - }); + const playerTester = testParser(parser)(); + snapshot(playerTester, "FooBar", ""); }); - describe("UUID", () => { + it("should parse UUIDS", () => { const parser = new EntityBase(false, false); - const tester = testParser(parser)(); - it("should parse a valid UUID", () => { - tester("f65c863a-747e-4fac-9828-33c3e825d00d", { - errors: [ - { - code: "argument.entity.uuid", - range: { - end: 36, - start: 0 - } - } - ], - succeeds: true - }); - }); - it("should parse a valid UUID with less numbers", () => { - tester("ec-0-0-0-1", { - errors: [ - { - code: "argument.entity.uuid", - range: { - end: 10, - start: 0 - } - } - ], - succeeds: true - }); - }); + const uuidTester = testParser(parser)(); + snapshot( + uuidTester, + "f65c863a-747e-4fac-9828-33c3e825d00d", + "ec-0-0-0-1" + ); }); - describe("fake player name", () => { + it("should suggest players in the scoreboard", () => { const parser = new EntityBase(true, false); - const tester = testParser(parser)({ + const fakePlayerTester = testParser(parser)({ data: { localData: { nbt: { @@ -73,113 +41,17 @@ describe("entity parser", () => { } } as CommandData }); - const nodatatester = testParser(parser)(); - it("should parse correctly", () => { - tester("Player1", { - succeeds: true, - suggestions: [ - { - start: 0, - text: "Player1" - } - ] - }); - }); - it("should give the correct suggestions", () => { - tester("", { - succeeds: false, - suggestions: [ - { - start: 0, - text: "Player2" - }, - { - start: 0, - text: "Player1" - } - ] - }); - }); - it("should succeed if there is no scoreboard data", () => { - nodatatester("Foobar", { - succeeds: true - }); - }); + snapshot(fakePlayerTester, "Player1", ""); }); - describe("entity selector", () => { + describe("entity selectors", () => { const parser = new EntityBase(false, true); const testerBuilder = testParser(parser); - describe("basic selector", () => { - const tester = testerBuilder(); - it("should succeed with @p", () => { - tester("@p", { - succeeds: true, - suggestions: [ - { - start: 1, - text: "p" - }, - { - start: 2, - text: "[" - } - ] - }); - }); - it("should give the suggestions for all of the selector types", () => { - tester("@", { - errors: [ - { - code: "parsing.expected.option", - range: { - end: 1, - start: 1 - } - } - ], - succeeds: false, - suggestions: [ - { - start: 0, - text: "@" - }, - { - start: 1, - text: "p" - }, - { - start: 1, - text: "a" - }, - { - start: 1, - text: "r" - }, - { - start: 1, - text: "s" - }, - { - start: 1, - text: "e" - } - ] - }); - }); - it("should suggest @", () => { - tester("", { - succeeds: false, - suggestions: [ - { - start: 0, - text: "@" - } - ] - }); - }); + it("should parse basic selectors", () => { + const basicSelectorTester = testerBuilder(); + snapshot(basicSelectorTester, "@p", "@p", "@", ""); }); - describe("argument selector", () => { - const tester = testerBuilder({ + it("should parse inputs with arguments", () => { + const argumentTester = testerBuilder({ data: { globalData: { nbt_docs: new Map([ @@ -214,327 +86,26 @@ describe("entity parser", () => { } } as CommandData }); - it("should succeed with no arguments", () => { - tester("@a[]", { - succeeds: true, - suggestions: [ - { - start: 3, - text: "]" - } - ] - }); - }); - it("should fail if there isn't a closing bracket", () => { - tester("@e[", { - errors: [ - { - code: "argument.entity.argument.unknown", - range: { - end: 3, - start: 3 - } - } - ], - start: 3, - succeeds: false, - suggestions: [ - { - start: 2, - text: "[" - }, - "]", - "advancements", - "distance", - "dx", - "dy", - "dz", - "gamemode", - "level", - "limit", - "name", - "nbt", - "scores", - "sort", - "tag", - "team", - "type", - "x", - "x_rotation", - "y", - "y_rotation", - "z" - ] - }); - }); - describe("x, y, z", () => { - it("should succeed with a coord argument", () => { - tester("@e[x=12]", { - succeeds: true, - suggestions: [ - { - start: 7, - text: "]" - } - ] - }); - }); - it("should add errors if the coord is out of bounds", () => { - tester("@e[x=30000000]", { - errors: [ - { - code: "argument.entity.option.number.abovemax", - range: { - end: 13, - start: 5 - } - } - ], - succeeds: true, - suggestions: [ - { - start: 13, - text: "]" - } - ] - }); - }); - it("should add errors if the arg is already present", () => { - tester("@p[x=10,x=13]", { - errors: [ - { - code: "argument.entity.option.duplicate", - range: { - end: 12, - start: 8 - } - } - ], - succeeds: true, - suggestions: [ - { - start: 12, - text: "]" - } - ] - }); - }); - }); - describe("dx, dy, dz", () => { - it("should succeed with a distance argument", () => { - tester("@a[dx=123]", { - succeeds: true, - suggestions: [ - { - start: 9, - text: "]" - } - ] - }); - }); - }); - describe("gamemode", () => { - it("should parse correctly", () => { - tester("@p[gamemode=survival]", { - succeeds: true, - suggestions: [ - { - start: 20, - text: "]" - } - ] - }); - }); - it("should add errors when using an incorrect option", () => { - tester("@a[gamemode=error]", { - errors: [ - { - code: "argument.entity.option.gamemode.invalid", - range: { - end: 17, - start: 12 - } - } - ], - succeeds: true, - suggestions: [ - { - start: 17, - text: "]" - } - ] - }); - }); - it("should add all of the correct suggestions", () => { - tester("@p[gamemode=", { - errors: [ - { - code: - "argument.entity.option.gamemode.expected", - range: { - end: 12, - start: 12 - } - } - ], - succeeds: false, - suggestions: [ - { - start: 11, - text: "=" - }, - { - start: 12, - text: "!" - }, - { - start: 12, - text: "survival" - }, - { - start: 12, - text: "creative" - }, - { - start: 12, - text: "adventure" - }, - { - start: 12, - text: "spectator" - } - ] - }); - }); - it("should add errors when an argument is a duplicate", () => { - tester("@a[gamemode=creative,gamemode=creative]", { - errors: [ - { - code: "argument.entity.option.noinfo", - range: { - end: 38, - start: 30 - } - } - ], - succeeds: true, - suggestions: [ - { - start: 38, - text: "]" - } - ] - }); - }); - it("should add errors when an argument is impossible", () => { - tester("@a[gamemode=survival,gamemode=!survival]", { - errors: [ - { - code: "argument.entity.option.nointersect", - range: { - end: 39, - start: 30 - } - } - ], - succeeds: true, - suggestions: [ - { - start: 39, - text: "]" - } - ] - }); - }); - }); - describe("tag", () => { - it("should be valid with one tag", () => { - tester("@e[tag=foo]", { - succeeds: true, - suggestions: [ - { - start: 10, - text: "]" - } - ] - }); - }); - it("should be valid with many tags", () => { - tester("@a[tag=one,tag=two,tag=three]", { - succeeds: true, - suggestions: [ - { - start: 28, - text: "]" - } - ] - }); - }); - it("should add errors if there is conflicting specific tags", () => { - tester("@p[tag=foo,tag=!foo]", { - errors: [ - { - code: "argument.entity.option.nointersect", - range: { - end: 19, - start: 15 - } - } - ], - succeeds: true, - suggestions: [ - { - start: 19, - text: "]" - } - ] - }); - }); - it("should add errors if there is tags but there cannot be any tags", () => { - tester("@r[tag=foo,tag=]", { - errors: [ - { - code: "argument.entity.option.nointersect", - range: { - end: 15, - start: 15 - } - } - ], - succeeds: true, - suggestions: [ - { - start: 15, - text: "]" - } - ] - }); - }); - it("should not add an error if there is a blank tag (not inverted)", () => { - tester("@e[tag=]", { - succeeds: true, - suggestions: [ - { - start: 7, - text: "]" - } - ] - }); - }); - }); - describe("type", () => { - it("should parse correctly", () => { - tester("@e[type=cow]", { - succeeds: true, - suggestions: [ - { - start: 11, - text: "]" - } - ] - }); - }); - }); + snapshot( + argumentTester, + "@a[]", + "@e[", + "@e[x=12]", + "@e[x=30000000]", + "@p[x=10,x=13]", + "@a[dx=123]", + "@p[gamemode=survival]", + "@a[gamemode=error]", + "@p[gamemode=", + "@a[gamemode=creative,gamemode=creative]", + "@a[gamemode=survival,gamemode=!survival]", + "@e[tag=foo]", + "@a[tag=one,tag=two,tag=three]", + "@p[tag=foo,tag=!foo]", + "@r[tag=foo,tag=]", + "@e[tag=]", + "@e[type=cow]" + ); }); }); }); diff --git a/src/test/parsers/minecraft/item.test.ts b/src/test/parsers/minecraft/item.test.ts index c190bce..5b2c568 100644 --- a/src/test/parsers/minecraft/item.test.ts +++ b/src/test/parsers/minecraft/item.test.ts @@ -1,10 +1,10 @@ import { GlobalData } from "../../../data/types"; -import { convertToNamespace } from "../../../misc-functions"; +import { convertToID } from "../../../misc-functions"; import { predicate as predicateParser, stack as stackParser } from "../../../parsers/minecraft/item"; -import { testParser } from "../../assertions"; +import { snapshot, testParser } from "../../assertions"; const global: GlobalData = { meta_info: { @@ -23,194 +23,45 @@ const global: GlobalData = { }, resources: { item_tags: [ - convertToNamespace("test:item_tag_one"), - convertToNamespace("test:item_tag_two"), - convertToNamespace("test:item_tag_two_one") + convertToID("test:item_tag_one"), + convertToID("test:item_tag_two"), + convertToID("test:item_tag_two_one") ] } } as any; -describe("item parser (no tags)", () => { - const tester = testParser(stackParser)({ +describe("item stack parser (no tags)", () => { + const stacktester = testParser(stackParser)({ data: { globalData: global } }); - describe("parse", () => { - it("should parse a valid item", () => { - tester("test:item_one", { - succeeds: true, - suggestions: [ - { - start: 0, - text: "test:item_one" - }, - { - start: 13, - text: "{" - } - ] - }); - }); - it("should throw when encountering a correct tag", () => { - tester("#test:item_tag_one", { - errors: [ - { - code: "argument.item.tag.disallowed", - range: { - end: 18, - start: 0 - } - } - ], - succeeds: false - }); - }); - it("should throw when encountering a bad tag", () => { - tester("#test:bad_tag", { - errors: [ - { - code: "argument.item.tag.disallowed", - range: { - end: 13, - start: 0 - } - } - ], - succeeds: false - }); - }); - it("should give multiple item suggestion", () => { - tester("test:item_four", { - succeeds: true, - suggestions: [ - { - start: 0, - text: "test:item_four" - }, - { - start: 0, - text: "test:item_four_one" - }, - { - start: 14, - text: "{" - } - ] - }); - }); - it("should return an error on an invalid item", () => { - tester("test:bad_item", { - errors: [ - { - code: "argument.item.id.invalid", - range: { - end: 13, - start: 0 - } - } - ], - succeeds: true, - suggestions: [ - { - start: 13, - text: "{" - } - ] - }); - }); - it("should succeed with an item with the 'minecraft' namespace", () => { - tester("minecraft:coal", { - succeeds: true, - suggestions: [ - { - start: 0, - text: "minecraft:coal" - }, - { - start: 14, - text: "{" - } - ] - }); - }); + it("should have the correct output for various inputs", () => { + snapshot( + stacktester, + "test:item_one", + "#test:item_tag_one", + "#test:bad_tag", + "test:item_four", + "test:bad_item", + "minecraft:coal" + ); }); }); + describe("item predicate parser", () => { - describe("parse", () => { - const tester = testParser(predicateParser)({ - data: { - globalData: global - } - }); - it("should parse a valid item", () => { - tester("test:item_one", { - succeeds: true, - suggestions: [ - { - start: 0, - text: "test:item_one" - }, - { - start: 13, - text: "{" - } - ] - }); - }); - it("should parse a valid tag", () => { - tester("#test:item_tag_one", { - succeeds: true, - suggestions: [ - { - start: 1, - text: "test:item_tag_one" - }, - { - start: 18, - text: "{" - } - ] - }); - }); - it("should give multiple tag suggestions", () => { - tester("#test:item_tag_two", { - succeeds: true, - suggestions: [ - { - start: 1, - text: "test:item_tag_two" - }, - { - start: 1, - text: "test:item_tag_two_one" - }, - { - start: 18, - text: "{" - } - ] - }); - }); - it("should return an error on an invalid tag", () => { - tester("#test:bad_tag", { - errors: [ - { - code: "arguments.item.tag.unknown", - range: { - end: 13, - start: 0 - } - } - ], - succeeds: true, - suggestions: [ - { - start: 13, - text: "{" - } - ] - }); - }); + const predicateTester = testParser(predicateParser)({ + data: { + globalData: global + } + }); + it("should give the correct output for various inputs", () => { + snapshot( + predicateTester, + "test:item_one", + "#test:item_tag_one", + "#test:item_tag_two", + "#test:bad_tag" + ); }); }); diff --git a/src/test/parsers/minecraft/lists.test.ts b/src/test/parsers/minecraft/lists.test.ts index 6ab3399..a865afc 100644 --- a/src/test/parsers/minecraft/lists.test.ts +++ b/src/test/parsers/minecraft/lists.test.ts @@ -1,8 +1,8 @@ import { CommandErrorBuilder } from "../../../brigadier/errors"; import { ListParser } from "../../../parsers/minecraft/lists"; -import { testParser } from "../../assertions"; +import { snapshot, testParser } from "../../assertions"; -const list = ["foo", "bar", "baz", "hello", "world"]; +const list = ["foo", "bar", "baz", "hello", "world", "++"]; describe("list tests", () => { describe("parse()", () => { @@ -12,42 +12,8 @@ describe("list tests", () => { ); const tester = testParser(parser)(); - list.forEach(v => - it(`should parse '${v}' correctly`, () => { - tester(v, { - succeeds: true, - suggestions: [v] - }); - }) - ); - it("should fail for a different input", () => { - tester("badinput", { - errors: [ - { - code: "arg.ex", - range: { - end: 8, - start: 0 - } - } - ], - succeeds: false - }); - }); - it("should suggest all values for an empty string", () => { - tester("", { - errors: [ - { - code: "arg.ex", - range: { - end: 0, - start: 0 - } - } - ], - succeeds: false, - suggestions: list - }); + it("should parse all of the values correctly", () => { + snapshot(tester, ...list, "badinput", ""); }); }); }); diff --git a/src/test/parsers/minecraft/namespace-list.test.ts b/src/test/parsers/minecraft/namespace-list.test.ts index c0e14d3..5bd66c9 100644 --- a/src/test/parsers/minecraft/namespace-list.test.ts +++ b/src/test/parsers/minecraft/namespace-list.test.ts @@ -1,8 +1,8 @@ import { CommandErrorBuilder } from "../../../brigadier/errors"; -import { NamespaceListParser } from "../../../parsers/minecraft/namespace-list"; -import { testParser } from "../../assertions"; +import { RegistryListParser } from "../../../parsers/minecraft/namespace-list"; +import { snapshot, testParser } from "../../assertions"; -const parser = new NamespaceListParser( +const parser = new RegistryListParser( "minecraft:biome", new CommandErrorBuilder("namespace.test.unknown", "Unkown") ); @@ -23,49 +23,14 @@ const tester = testParser(parser)({ } as any); describe("NamespaceListParser", () => { - it("should allow a known namespace", () => { - tester("minecraft:test", { - succeeds: true, - suggestions: ["minecraft:test", "minecraft:test2"] - }); - }); - it("should accept a non-minecraft namespace", () => { - tester("something:hello", { - succeeds: true, - suggestions: ["something:hello"] - }); - }); - it("should give the given error for an incorrect namespace", () => { - tester("unknown:unknown", { - errors: [ - { - code: "namespace.test.unknown", - range: { start: 0, end: 15 } - } - ], - succeeds: true - }); - }); - it("should fail for an unparseable namespace", () => { - tester("fail:fail:fail", { - errors: [ - { code: "argument.id.invalid", range: { start: 9, end: 10 } } - ], - succeeds: false - }); - }); - it("should suggest all values for an empty string", () => { - tester("", { - errors: [ - { code: "namespace.test.unknown", range: { start: 0, end: 0 } } - ], - succeeds: true, - suggestions: [ - "minecraft:test", - "minecraft:test2", - "minecraft:other", - "something:hello" - ] - }); + it("should work correctly for various inputs", () => { + snapshot( + tester, + "minecraft:test", + "something:hello", + "unknown:unknown", + "fail:fail:fail", + "" + ); }); }); diff --git a/src/test/parsers/minecraft/nbt/nbt.test.ts b/src/test/parsers/minecraft/nbt/nbt.test.ts index 417546f..18765fb 100644 --- a/src/test/parsers/minecraft/nbt/nbt.test.ts +++ b/src/test/parsers/minecraft/nbt/nbt.test.ts @@ -1,158 +1,47 @@ -import { StringReader } from "../../../../brigadier/string-reader"; import { loadNBTDocs } from "../../../../data/noncached"; -import { - nbtParser, - validateParse -} from "../../../../parsers/minecraft/nbt/nbt"; -import { ParserInfo, SuggestResult } from "../../../../types"; -import { - assertSuggestions, - SuggestedOption, - testParser -} from "../../../assertions"; +import { GlobalData } from "../../../../data/types"; +import { convertToID } from "../../../../misc-functions"; +import { nbtParser } from "../../../../parsers/minecraft/nbt/nbt"; +import { ParserInfo } from "../../../../types"; +import { snapshot, testParser } from "../../../assertions"; import { testDocs } from "./test-data"; -describe("nbt parser test", () => { - describe("parse()", () => { - let info: ParserInfo; - let reginfo: ParserInfo; +describe("SNBT Parser", () => { + const vanillaInfo: ParserInfo = { + data: { + globalData: { + nbt_docs: loadNBTDocs() + } + }, + suggesting: true + } as ParserInfo; + const testInfo: ParserInfo = { + context: { + otherEntity: { ids: [convertToID("minecraft:zombie")] } + }, + data: { + globalData: { + nbt_docs: testDocs + } as GlobalData + }, + node_properties: {}, + path: ["summon", "entity"], + suggesting: true + }; - before(async () => { - info = { - data: { - globalData: { - nbt_docs: testDocs - } - }, - suggesting: true - } as ParserInfo; - reginfo = { - data: { - globalData: { - nbt_docs: loadNBTDocs() - } - }, - suggesting: true - } as ParserInfo; - }); - const tester = testParser(nbtParser); - it("should parse correctly", () => { - tester(info)("{foo:{bar:baz}}", { - succeeds: true, - suggestions: [ - { - start: 14, - text: "}" - } - ] - }); - }); + const tester = testParser(nbtParser); + it("should correctly parse a compound", () => { + snapshot(tester(testInfo), "{foo:{bar:baz}}"); + }); - it("should return correct suggestions when nested in a compound", () => { - const reader = new StringReader("{display:{"); - const out = validateParse(reader, reginfo, { - ids: "minecraft:apple", - kind: "item" - }); - assertSuggestions( - [ - { - start: 9, - text: "{" - }, - "}", - "Name", - "color", - "Lore: [" - ] as SuggestResult[], - out.suggestions, - 10 - ); - }); - it("should return the correct suggestions when nested in a list", () => { - const reader = new StringReader("{AttributeModifiers:[{"); - const out = validateParse(reader, reginfo, { - ids: "minecraft:stick", - kind: "item" - }); - assertSuggestions( - [ - { - start: 21, - text: "{" - }, - { - start: 22, - text: "}" - }, - { - start: 22, - text: "AttributeName" - }, - { - start: 22, - text: "Name" - }, - { - start: 22, - text: "Slot" - }, - { - start: 22, - text: "Amount" - }, - { - start: 22, - text: "Operation" - }, - { - start: 22, - text: "UUIDLeast" - }, - { - start: 22, - text: "UUIDMost" - } - ] as SuggestedOption[], - out.suggestions - ); - }); - it("should return the correct suggestion when nested in a list part 2", () => { - const reader = new StringReader("{Items:[{"); - const out = validateParse(reader, reginfo, { - ids: "minecraft:chest", - kind: "block" - }); - assertSuggestions( - [ - { - start: 8, - text: "{" - }, - { - start: 9, - text: "}" - }, - { - start: 9, - text: "Slot" - }, - { - start: 9, - text: "tag: {" - }, - { - start: 9, - text: "Count" - }, - { - start: 9, - text: "id" - } - ], - out.suggestions - ); - }); + it("should return give the right results with vanilla data for a zombie", () => { + snapshot( + tester(vanillaInfo), + "{", + "{HandItems:[{", + "{HandItems:[{display:{", + "{HandItems:[{display:{" + ); }); }); diff --git a/src/test/parsers/minecraft/nbt/tag-parser.test.ts b/src/test/parsers/minecraft/nbt/tag-parser.test.ts index f0463ed..c236e17 100644 --- a/src/test/parsers/minecraft/nbt/tag-parser.test.ts +++ b/src/test/parsers/minecraft/nbt/tag-parser.test.ts @@ -1,115 +1,54 @@ -import * as assert from "assert"; import { NBTNode } from "mc-nbt-paths"; import { StringReader } from "../../../../brigadier/string-reader"; -import { NBTTag, TagType } from "../../../../parsers/minecraft/nbt/tag/nbt-tag"; +import { NBTDocs } from "../../../../data/types"; +import { + NBTTag, + ParseReturn +} from "../../../../parsers/minecraft/nbt/tag/nbt-tag"; import { NBTTagNumber } from "../../../../parsers/minecraft/nbt/tag/number"; -import { Correctness } from "../../../../parsers/minecraft/nbt/util/nbt-util"; import { NBTWalker } from "../../../../parsers/minecraft/nbt/walker"; -import { returnAssert, ReturnAssertionInfo } from "../../../assertions"; +import { ReturnSuccess } from "../../../../types"; +import { snapshot } from "../../../assertions"; -interface ParseTest { - expected: ReturnAssertionInfo; - text: string; -} - -interface ValidateTest { - expected: ReturnAssertionInfo; - node: NBTNode; - walker?: NBTWalker; -} - -interface ValueTest { - type?: TagType; - value: any; -} - -const testTag = ( - tag: NBTTag, - parse: ParseTest, - validate?: ValidateTest, - value?: ValueTest -) => { - const response = tag.parse(new StringReader(parse.text)); - returnAssert(response, parse.expected); - if ( - response.data === Correctness.MAYBE || - response.data === Correctness.CERTAIN - ) { - if (validate) { - const valres = tag.validate( - { path: "", node: validate.node }, - validate.walker || new NBTWalker(new Map()) - ); - returnAssert(valres, validate.expected); - } - if (value) { - assert.deepStrictEqual(tag.getValue(), value.value); - assert.strictEqual( - // @ts-ignore - tag.tagType, - value.type - ); +function testTag( + tagConstructor: new (path: string[]) => NBTTag, + node: NBTNode | string[] = [], + docs?: NBTDocs +): (value: string) => [ParseReturn, ReturnSuccess] | ParseReturn { + function testTagInner( + value: string + ): [ParseReturn, ReturnSuccess] | ParseReturn { + const tag = new tagConstructor([]); + const reader = new StringReader(value); + const parseReturn = tag.parse(reader); + if (docs) { + const walker = new NBTWalker(docs); + const realNode = Array.isArray(node) + ? walker.getInitialNode(node) + : { path: "", node }; + return [parseReturn, tag.validate(realNode, walker)]; } + return parseReturn; } -}; + return testTagInner; +} -describe("SNBT Tag Parser Tests", () => { +describe("SNBT Tag Parsers", () => { describe("Byte Tag", () => { it("should parse correctly", () => - testTag( - new NBTTagNumber([]), - { - expected: { - succeeds: true - }, - text: "123b" - }, - { - expected: { - succeeds: true - }, - node: { - type: "byte" - } - }, - { - value: 123 - } - )); - it("should parse as a short and fail validation", () => - testTag( - new NBTTagNumber([]), - { - expected: { - succeeds: true - }, - text: "321s" - }, - { - expected: { - succeeds: true - }, - node: { - type: "short" - } - } + snapshot( + testTag(NBTTagNumber, { + type: "byte" + }), + "123b", + "321s", + "hello", + "true", + "false", + "130b", + "-10b", + "-130b" )); - it("should give an invalid number error", () => - testTag(new NBTTagNumber([]), { - expected: { - errors: [ - { - code: "parsing.float.expected", - range: { - end: 5, - start: 0 - } - } - ], - succeeds: false - }, - text: "hello" - })); }); }); diff --git a/src/test/parsers/minecraft/range.test.ts b/src/test/parsers/minecraft/range.test.ts index 0738519..6adb32f 100644 --- a/src/test/parsers/minecraft/range.test.ts +++ b/src/test/parsers/minecraft/range.test.ts @@ -1,156 +1,18 @@ import { floatRange, intRange } from "../../../parsers/minecraft/range"; -import { testParser } from "../../assertions"; +import { snapshot, testParser } from "../../assertions"; describe("range parser", () => { + const intTests: string[] = ["10", "..34", "12..34", "..", "7..-12", "5..5"]; describe("int range", () => { const tester = testParser(intRange)(); - it("should succeed with a single int", () => { - tester("10", { - succeeds: true, - suggestions: [ - { - start: 2, - text: ".." - } - ] - }); - }); - it("should succeed with a right unbounded range", () => { - tester("3..", { - succeeds: true, - suggestions: [ - { - start: 1, - text: ".." - } - ] - }); - }); - it("should succeed with a left unbounded range", () => { - tester("..34", { - succeeds: true - }); - }); - it("should succeed with a bounded range", () => { - tester("12..34", { - succeeds: true - }); - }); - it("should fail when no numbers", () => { - tester("..", { - errors: [ - { - code: "parsing.int.expected", - range: { - end: 2, - start: 2 - } - } - ], - succeeds: false, - suggestions: [ - { - start: 0, - text: ".." - } - ] - }); - }); - it("should have errors when min as greater than max", () => { - tester("7..-12", { - actions: [ - { - data: "-12..7", - high: 6, - low: 0, - type: "format" - } - ], - errors: [ - { - code: "argument.range.swapped", - range: { - end: 6, - start: 0 - } - } - ], - succeeds: true - }); - }); - it("should have a hint when min equals max", () => { - tester("5..5", { - actions: [ - { - data: "5", - high: 4, - low: 0, - type: "format" - } - ], - errors: [ - { - code: "argument.range.equal", - range: { - end: 4, - start: 0 - } - } - ], - succeeds: true - }); + it("should do the right thing for different inputs", () => { + snapshot(tester, ...intTests); }); }); describe("float range", () => { const tester = testParser(floatRange)(); - it("should succeed with a single float", () => { - tester("9.32", { - succeeds: true, - suggestions: [ - { - start: 4, - text: ".." - } - ] - }); - }); - it("should succeed with a right unbounded range", () => { - tester("3.12..", { - succeeds: true, - suggestions: [ - { - start: 4, - text: ".." - } - ] - }); - }); - it("should succeed with a left unbounded range & float without starting '0'", () => { - tester("...4", { - succeeds: true - }); - }); - it("should have errors when min as greater than max", () => { - tester("3.4..0.2", { - actions: [ - { - data: "0.2..3.4", - high: 8, - low: 0, - type: "format" - } - ], - errors: [ - { - code: "argument.range.swapped", - range: { - end: 8, - start: 0 - } - } - ], - succeeds: true - }); + it("should do the right thing for different inputs", () => { + snapshot(tester, ...intTests, "9.32", "3.12..", "...4", "3.4..0.2"); }); }); }); diff --git a/src/test/parsers/minecraft/resources.test.ts b/src/test/parsers/minecraft/resources.test.ts index ef7e43c..61ee9f5 100644 --- a/src/test/parsers/minecraft/resources.test.ts +++ b/src/test/parsers/minecraft/resources.test.ts @@ -1,7 +1,7 @@ import * as resourceParsers from "../../../parsers/minecraft/resources"; -import { convertToResource, testParser } from "../../assertions"; +import { convertToResource, snapshot, testParser } from "../../assertions"; -const functionTester = testParser(resourceParsers.functionParser)({ +const testFunction = testParser(resourceParsers.functionParser)({ data: { globalData: { resources: { @@ -16,38 +16,13 @@ const functionTester = testParser(resourceParsers.functionParser)({ } as any); describe("function parser", () => { - it("should accept a known function", () => { - functionTester("minecraft:test", { - succeeds: true, - suggestions: ["minecraft:test", "minecraft:test2"] - }); - }); - it("should give an error for an unknown function", () => { - functionTester("minecraft:unknown", { - errors: [ - { - code: "arguments.function.unknown", - range: { start: 0, end: 17 } - } - ], - succeeds: true - }); - }); - it("should allow a known tag", () => { - functionTester("#minecraft:tag", { - succeeds: true, - suggestions: [{ start: 1, text: "minecraft:tag" }] - }); - }); - it("should give an error for an unknown tag", () => { - functionTester("#minecraft:unknowntag", { - errors: [ - { - code: "arguments.function.tag.unknown", - range: { start: 0, end: 21 } - } - ], - succeeds: true - }); + it("should have the correct behaviour for various inputs", () => { + snapshot( + testFunction, + "minecraft:test", + "minecraft:unknown", + "#minecraft:tag", + "#minecraft:unknowntag" + ); }); }); diff --git a/src/test/parsers/minecraft/scoreboard.test.ts b/src/test/parsers/minecraft/scoreboard.test.ts index 6d0a78b..bbabc94 100644 --- a/src/test/parsers/minecraft/scoreboard.test.ts +++ b/src/test/parsers/minecraft/scoreboard.test.ts @@ -1,53 +1,28 @@ import { criteriaParser } from "../../../parsers/minecraft/scoreboard"; -import { testParser } from "../../assertions"; +import { snapshot, testParser } from "../../assertions"; -describe("scoreboard parsers", () => { - describe("criterion parser", () => { - const test = testParser(criteriaParser)({ - data: { - globalData: { - registries: { - "minecraft:custom_stat": new Set([ - "minecraft:some_custom" - ]), - "minecraft:item": new Set([ - "minecraft:some_item", - "minecraft:some_other_item" - ]) - } +describe("criterion parser", () => { + const test = testParser(criteriaParser)({ + data: { + globalData: { + registries: { + "minecraft:custom_stat": new Set(["minecraft:some_custom"]), + "minecraft:item": new Set([ + "minecraft:some_item", + "minecraft:some_other_item" + ]) } - } as any - }); - it("should support parsing a simple criteria", () => { - test("air", { succeeds: true, suggestions: ["air"] }); - }); - it("should support a color requiring criterion", () => { - test("teamkill.aqua", { - start: 9, - succeeds: true, - suggestions: ["aqua"] - }); - }); - it("should support a custom criterion", () => { - test("minecraft.custom:minecraft.some_custom", { - start: 17, - succeeds: true, - suggestions: ["minecraft.some_custom"] - }); - }); - it("should support a custom criterion without the namespace", () => { - test("minecraft.custom:some_custom", { - start: 17, - succeeds: true, - suggestions: ["minecraft.some_custom"] - }); - }); - it("should support a custom criterion without any namespace", () => { - test("custom:some_custom", { - start: 7, - succeeds: true, - suggestions: ["minecraft.some_custom"] - }); - }); + } + } as any + }); + it("should parse correctly", () => { + snapshot( + test, + "air", + "teamkill.aqua", + "minecraft.custom:minecraft.some_custom", + "minecraft.custom:some_custom", + "custom:some_custom" + ); }); }); diff --git a/src/test/parsers/minecraft/time.test.ts b/src/test/parsers/minecraft/time.test.ts index bf7b573..d5379e8 100644 --- a/src/test/parsers/minecraft/time.test.ts +++ b/src/test/parsers/minecraft/time.test.ts @@ -5,28 +5,18 @@ const test = testParser(timeParser)(); describe("time parser", () => { it("should allow a plain integer", () => { - test("123", { succeeds: true, suggestions: ["t", "s", "d"], start: 3 }); + test("123"); }); it("should allow an integer number of ticks", () => { - test("123t", { start: 3, succeeds: true, suggestions: ["t"] }); + test("123t"); }); it("should allow an integer number of seconds", () => { - test("123s", { start: 3, succeeds: true, suggestions: ["s"] }); + test("123s"); }); it("should allow a nice float number of seconds", () => { - test("0.5s", { start: 3, succeeds: true, suggestions: ["s"] }); + test("0.5s"); }); it("should not allow a negative integer", () => { - test("-123", { - errors: [ - { - code: "argument.time.not_nonnegative_integer", - range: { start: 0, end: 4 } - } - ], - start: 4, - succeeds: true, - suggestions: ["t", "s", "d"] - }); + test("-123"); }); }); diff --git a/src/test/parsers/tests/dummy1.test.ts b/src/test/parsers/tests/dummy1.test.ts index e6ca697..4623227 100644 --- a/src/test/parsers/tests/dummy1.test.ts +++ b/src/test/parsers/tests/dummy1.test.ts @@ -1,36 +1,23 @@ -import * as assert from "assert"; - -import { testParser } from "../../assertions"; +import { snapshot, testParser } from "../../assertions"; import { dummyParser } from "./dummy1"; const dummyParserTester = testParser(dummyParser); describe("dummyParser1", () => { - describe("parse", () => { - it("should read the specified number of characters", () => { - const result = dummyParserTester({ + it("should read the specified number of characters", () => { + snapshot( + dummyParserTester({ node_properties: { number: 4 } - })("test hello", { - succeeds: true, - suggestions: ["hello", { text: "welcome", start: 2 }] - }); - assert.strictEqual(result[1].cursor, 4); - }); + })("test hello") + ); + }); - it("should default to 3 when not given any properties", () => { - const result = dummyParserTester()("test hello", { - succeeds: true, - suggestions: ["hello", { text: "welcome", start: 1 }] - }); - assert.strictEqual(result[1].cursor, 3); - }); + it("should default to 3 when not given any properties", () => { + snapshot(dummyParserTester()("test hello")); + }); - it("should not succeed if there is not enough room", () => { - dummyParserTester()("te", { - succeeds: false, - suggestions: ["hello", { text: "welcome", start: 1 }] - }); - }); + it("should not succeed if there is not enough room", () => { + snapshot(dummyParserTester()("te")); }); }); diff --git a/src/types.ts b/src/types.ts index 55f0fb0..67f0f61 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,24 +1,203 @@ +import { NBTNode, ValueList } from "mc-nbt-paths"; import { DataInterval, Interval, IntervalTree } from "node-interval-tree"; -import { JSONDocument } from "vscode-json-languageservice"; +import { + CompletionItem, + JSONDocument, + LanguageService +} from "vscode-json-languageservice"; import { CompletionItemKind, - InsertTextFormat, MarkupContent, TextDocument -} from "vscode-languageserver/lib/main"; +} from "vscode-languageserver"; import { BlankCommandError, CommandError } from "./brigadier/errors"; import { StringReader } from "./brigadier/string-reader"; -import { - CommandNodePath, - GlobalData, - LocalData, - NamespacedName -} from "./data/types"; +import { Level, Scoreboard } from "./data/nbt/nbt-types"; import { PackLocationSegments } from "./misc-functions"; +import { IDMap, IdSet } from "./misc-functions/id-map"; import { TypedNode } from "./parsers/minecraft/nbt/util/doc-walker-util"; -//#region Document +//#region Minecraft types + +// tslint:disable-next-line: interface-name +export interface ID { + namespace?: string; + path: string; +} + +export interface Tag { + replace?: boolean; + values: string[]; +} + +export interface Advancement { + criteria: Set; +} +export interface McmetaFile { + pack?: { description?: string; pack_format?: number }; +} + +// Undefined represents minecraft +export type DataPackReference = DataPackID | undefined; +export type DataPackID = number; + +export interface Datapack { + id: DataPackID; + mcmeta?: McmetaFile; + name: string; +} + +export type RegistryNames = + | "minecraft:sound_event" + | "minecraft:fluid" + | "minecraft:mob_effect" + | "minecraft:block" + | "minecraft:enchantment" + | "minecraft:entity_type" + | "minecraft:item" + | "minecraft:potion" + | "minecraft:carver" + | "minecraft:surface_builder" + | "minecraft:feature" + | "minecraft:decorator" + | "minecraft:biome" + | "minecraft:particle_type" + | "minecraft:biome_source_type" + | "minecraft:block_entity_type" + | "minecraft:chunk_generator_type" + | "minecraft:dimension_type" + | "minecraft:motive" + | "minecraft:custom_stat" + | "minecraft:chunk_status" + | "minecraft:structure_feature" + | "minecraft:structure_piece" + | "minecraft:rule_test" + | "minecraft:structure_processor" + | "minecraft:structure_pool_element" + | "minecraft:menu" + | "minecraft:recipe_type" + | "minecraft:recipe_serializer" + | "minecraft:stat_type" + | "minecraft:villager_type" + | "minecraft:villager_profession"; + +export type RegistryData = Record; + +export interface WorldNBT { + level?: Level; + scoreboard?: Scoreboard; +} + +/** + * A node with children. + */ +export interface MCNode { + children?: { [id: string]: T }; +} + +/** + * The root of the commands. + */ +export interface CommandTree extends MCNode { + type: "root"; +} + +/** + * The Path which describes the route taken to get to a node. + */ +export type CommandNodePath = string[]; + +/** + * A node in the command tree. + * See for the format + */ +export interface CommandNode extends MCNode { + executable?: boolean; + /** + * The parser for this node. Only Applicable if type of argument + */ + parser?: string; + properties?: Dictionary; + redirect?: CommandNodePath; + type: "literal" | "argument"; +} +//#endregion + +//#region Internal types +export type Blocks = IDMap>>; +export interface CommandData { + blocks: Blocks; + commands: CommandTree; + data_info: { version: string }; + jsonService: LanguageService; + /** + * Data from datapacks + */ + localData?: LocalData; + nbt_docs: NBTDocs; + registries: RegistryData; + resources: Resources; +} + +export type NBTDocs = Map; + +export interface WorldInfo { + datapacksFolder: string; + nbt: WorldNBT; + packnamesmap: Map; + packs: Map; +} + +export interface LocalData extends WorldInfo { + // TODO: Is this used? + current: DataPackReference; +} + +export type PackMap = Map; + +export type ResourcesMap = IDMap< + PackMap, + R, + CommandData +>; +// Used for tags + +export interface Resolved { + resolved: R; + resources: PackMap; +} + +export interface ResolvedTag { + /** + * Directly referenced types + */ + finals: IdSet; + /** + * Resolved types + */ + resolved: IdSet; + /** + * Tags referenced + */ + tags: IdSet; +} + +export type TagMap = ResourcesMap; + +export interface Resources { + advancements: ResourcesMap; + block_tags: TagMap; + entity_tags: TagMap; + fluid_tags: TagMap; + function_tags: TagMap; + functions: ResourcesMap; + item_tags: TagMap; + loot_tables: ResourcesMap; + recipes: ResourcesMap; + structures: ResourcesMap; +} + export interface FunctionInfo { lines: CommandLine[]; /** @@ -42,9 +221,7 @@ export interface CommandLine { parseInfo?: StoredParseResult | false; text: string; } -//#endregion -//#region Interaction with parsers export interface ParserInfo { /** * The immutable context @@ -59,18 +236,8 @@ export interface ParserInfo { suggesting: boolean; } -export interface CommandData { - globalData: GlobalData; - /** - * Data from datapacks - */ - localData?: LocalData; -} - -export interface Suggestion { +export interface Suggestion extends Partial { description?: string | MarkupContent; - insertTextFormat?: InsertTextFormat; - kind?: CompletionItemKind; label?: string; /** * The start from where value should be replaced. 0 indexed character gaps. @@ -81,6 +248,8 @@ export interface Suggestion { text: string; } +// If suggestion is a string, then it is from the start of the input +// TODO: Remove the string fallback export type SuggestResult = Suggestion | string; /** @@ -111,7 +280,7 @@ export interface EntityInfo { /** * The possible entity types of this entity */ - ids?: NamespacedName[]; + ids?: ID[]; } export interface Parser { @@ -129,8 +298,6 @@ export interface Parser { ): ReturnedInfo; } -//#endregion -//#region ParsingData export interface ParseNode extends Interval { context: CommandContext; final?: CommandContext; @@ -153,8 +320,6 @@ export interface JSONDocInfo { } export type SubAction = - // See https://github.com/Microsoft/language-server-protocol/issues/518. - // tslint:disable-next-line:deprecation - TODO: this needs a PR to fix | SubActionBase<"hover", string> | SubActionBase<"format", string> | SubActionBase<"source", string> @@ -162,14 +327,12 @@ export type SubAction = // | SubActionBase<"highlight", HighlightScope>; // | SubActionBase<"rename", RenameRequest>; -//#endregion export type Success = true; export const success: Success = true; export const failure: Failure = false; export type Failure = false; -//#region ReturnData export interface ReturnData { actions: SubAction[]; errors: ErrorKind[]; @@ -218,13 +381,11 @@ export interface ReturnSuccess data: T; kind: Success; } -//#endregion -// Helper types to lower the amount of repetition of the names + +// Helper types to give these commonly used types shorter names export type BCE = BlankCommandError; export type CE = CommandError; -//#region Misc types - export interface LineRange { end: number; start: number; diff --git a/tslint.json b/tslint.json index 318581a..d125bb6 100644 --- a/tslint.json +++ b/tslint.json @@ -21,7 +21,9 @@ "helper-return": true, "max-file-line-count": false, "ban-ts-ignore": false, - "increment-decrement": false + "increment-decrement": false, + "max-classes-per-file": false, + "variable-name": { "options": "allow-leading-underscore" } }, "linterOptions": { "exclude": ["lintrules/**"]