Add a minimally working test infrastructure
This commit is contained in:
parent
3dcf91c520
commit
608728ade9
10 changed files with 802 additions and 37 deletions
137
package-lock.json
generated
137
package-lock.json
generated
|
@ -403,6 +403,14 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.11.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.11.tgz",
|
||||||
"integrity": "sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg=="
|
"integrity": "sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg=="
|
||||||
},
|
},
|
||||||
|
"@types/ora": {
|
||||||
|
"version": "3.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ora/-/ora-3.2.0.tgz",
|
||||||
|
"integrity": "sha512-jll99xUKpiFbIFZSQcxm4numfsLaOWBzWNaRk3PvTSE7BPqTzzOCFmS0mQ7m8qkTfmYhuYbehTGsxkvRLPC++w==",
|
||||||
|
"requires": {
|
||||||
|
"ora": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/semver": {
|
"@types/semver": {
|
||||||
"version": "7.2.0",
|
"version": "7.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.2.0.tgz",
|
||||||
|
@ -656,7 +664,6 @@
|
||||||
"version": "3.2.1",
|
"version": "3.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"color-convert": "^1.9.0"
|
"color-convert": "^1.9.0"
|
||||||
}
|
}
|
||||||
|
@ -1211,6 +1218,19 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"cli-cursor": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==",
|
||||||
|
"requires": {
|
||||||
|
"restore-cursor": "^3.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cli-spinners": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-Xs2Hf2nzrvJMFKimOR7YR0QwZ8fc0u98kdtwN1eNAZzNQgH3vK2pXzff6GJtKh7S5hoJ87ECiAiZFS2fb5Ii2w=="
|
||||||
|
},
|
||||||
"cliui": {
|
"cliui": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
|
||||||
|
@ -1221,6 +1241,11 @@
|
||||||
"wrap-ansi": "^6.2.0"
|
"wrap-ansi": "^6.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"clone": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
|
||||||
|
"integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4="
|
||||||
|
},
|
||||||
"collection-visit": {
|
"collection-visit": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
|
||||||
|
@ -1235,7 +1260,6 @@
|
||||||
"version": "1.9.3",
|
"version": "1.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"color-name": "1.1.3"
|
"color-name": "1.1.3"
|
||||||
}
|
}
|
||||||
|
@ -1243,8 +1267,7 @@
|
||||||
"color-name": {
|
"color-name": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"commander": {
|
"commander": {
|
||||||
"version": "2.20.3",
|
"version": "2.20.3",
|
||||||
|
@ -1576,6 +1599,14 @@
|
||||||
"type-detect": "^4.0.0"
|
"type-detect": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"defaults": {
|
||||||
|
"version": "1.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz",
|
||||||
|
"integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=",
|
||||||
|
"requires": {
|
||||||
|
"clone": "^1.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"define-properties": {
|
"define-properties": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||||
|
@ -1802,8 +1833,7 @@
|
||||||
"escape-string-regexp": {
|
"escape-string-regexp": {
|
||||||
"version": "1.0.5",
|
"version": "1.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"eslint-scope": {
|
"eslint-scope": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
|
@ -2273,8 +2303,7 @@
|
||||||
"has-flag": {
|
"has-flag": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"has-symbols": {
|
"has-symbols": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -2615,6 +2644,11 @@
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"is-interactive": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w=="
|
||||||
|
},
|
||||||
"is-number": {
|
"is-number": {
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
|
@ -2798,7 +2832,6 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz",
|
||||||
"integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==",
|
"integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"chalk": "^2.4.2"
|
"chalk": "^2.4.2"
|
||||||
},
|
},
|
||||||
|
@ -2807,7 +2840,6 @@
|
||||||
"version": "2.4.2",
|
"version": "2.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^3.2.1",
|
"ansi-styles": "^3.2.1",
|
||||||
"escape-string-regexp": "^1.0.5",
|
"escape-string-regexp": "^1.0.5",
|
||||||
|
@ -2818,7 +2850,6 @@
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"has-flag": "^3.0.0"
|
"has-flag": "^3.0.0"
|
||||||
}
|
}
|
||||||
|
@ -3055,8 +3086,7 @@
|
||||||
"mimic-fn": {
|
"mimic-fn": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
|
||||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"minimalistic-assert": {
|
"minimalistic-assert": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
|
@ -3320,6 +3350,11 @@
|
||||||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"mute-stream": {
|
||||||
|
"version": "0.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz",
|
||||||
|
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA=="
|
||||||
|
},
|
||||||
"nanomatch": {
|
"nanomatch": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||||
|
@ -3556,6 +3591,62 @@
|
||||||
"wrappy": "1"
|
"wrappy": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"onetime": {
|
||||||
|
"version": "5.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz",
|
||||||
|
"integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==",
|
||||||
|
"requires": {
|
||||||
|
"mimic-fn": "^2.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ora": {
|
||||||
|
"version": "4.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/ora/-/ora-4.0.4.tgz",
|
||||||
|
"integrity": "sha512-77iGeVU1cIdRhgFzCK8aw1fbtT1B/iZAvWjS+l/o1x0RShMgxHUZaD2yDpWsNCPwXg9z1ZA78Kbdvr8kBmG/Ww==",
|
||||||
|
"requires": {
|
||||||
|
"chalk": "^3.0.0",
|
||||||
|
"cli-cursor": "^3.1.0",
|
||||||
|
"cli-spinners": "^2.2.0",
|
||||||
|
"is-interactive": "^1.0.0",
|
||||||
|
"log-symbols": "^3.0.0",
|
||||||
|
"mute-stream": "0.0.8",
|
||||||
|
"strip-ansi": "^6.0.0",
|
||||||
|
"wcwidth": "^1.0.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"ansi-styles": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/color-name": "^1.1.1",
|
||||||
|
"color-convert": "^2.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chalk": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==",
|
||||||
|
"requires": {
|
||||||
|
"ansi-styles": "^4.1.0",
|
||||||
|
"supports-color": "^7.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-convert": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
|
"requires": {
|
||||||
|
"color-name": "~1.1.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"color-name": {
|
||||||
|
"version": "1.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"os-browserify": {
|
"os-browserify": {
|
||||||
"version": "0.3.0",
|
"version": "0.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz",
|
||||||
|
@ -3992,6 +4083,15 @@
|
||||||
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
|
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"restore-cursor": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==",
|
||||||
|
"requires": {
|
||||||
|
"onetime": "^5.1.0",
|
||||||
|
"signal-exit": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ret": {
|
"ret": {
|
||||||
"version": "0.1.15",
|
"version": "0.1.15",
|
||||||
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
|
"resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
|
||||||
|
@ -4134,8 +4234,7 @@
|
||||||
"signal-exit": {
|
"signal-exit": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
|
||||||
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==",
|
"integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"snapdragon": {
|
"snapdragon": {
|
||||||
"version": "0.8.2",
|
"version": "0.8.2",
|
||||||
|
@ -5135,6 +5234,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"wcwidth": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
|
||||||
|
"integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=",
|
||||||
|
"requires": {
|
||||||
|
"defaults": "^1.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"webpack": {
|
"webpack": {
|
||||||
"version": "4.43.0",
|
"version": "4.43.0",
|
||||||
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz",
|
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.43.0.tgz",
|
||||||
|
|
|
@ -9,7 +9,8 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"watch": "webpack --watch --config webpack.dev.js",
|
"watch": "webpack --watch --config webpack.dev.js",
|
||||||
"prepare": "webpack --config webpack.dev.js",
|
"prepare": "webpack --config webpack.dev.js",
|
||||||
"test": "mocha lib/test"
|
"test": "node build/bin/bolt-test.js",
|
||||||
|
"update-lkg": "node build/bin/bolt-test.js snapshot lkg"
|
||||||
},
|
},
|
||||||
"author": "Sam Vervaeck <vervaeck.sam@skynet.be>",
|
"author": "Sam Vervaeck <vervaeck.sam@skynet.be>",
|
||||||
"license": "GPL-3.0",
|
"license": "GPL-3.0",
|
||||||
|
@ -21,6 +22,7 @@
|
||||||
"@types/microtime": "^2.1.0",
|
"@types/microtime": "^2.1.0",
|
||||||
"@types/minimist": "^1.2.0",
|
"@types/minimist": "^1.2.0",
|
||||||
"@types/node": "^14.0.11",
|
"@types/node": "^14.0.11",
|
||||||
|
"@types/ora": "^3.2.0",
|
||||||
"@types/semver": "^7.2.0",
|
"@types/semver": "^7.2.0",
|
||||||
"@types/yargs": "^15.0.5",
|
"@types/yargs": "^15.0.5",
|
||||||
"chalk": "^4.0.0",
|
"chalk": "^4.0.0",
|
||||||
|
@ -30,6 +32,7 @@
|
||||||
"microtime": "^3.0.0",
|
"microtime": "^3.0.0",
|
||||||
"minimist": "^1.2.5",
|
"minimist": "^1.2.5",
|
||||||
"moment": "^2.26.0",
|
"moment": "^2.26.0",
|
||||||
|
"ora": "^4.0.4",
|
||||||
"pegjs": "^0.11.0-master.b7b87ea",
|
"pegjs": "^0.11.0-master.b7b87ea",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"semver": "^7.3.2",
|
"semver": "^7.3.2",
|
||||||
|
|
370
src/bin/bolt-test.ts
Normal file
370
src/bin/bolt-test.ts
Normal file
|
@ -0,0 +1,370 @@
|
||||||
|
|
||||||
|
import "source-map-support/register"
|
||||||
|
|
||||||
|
import * as fs from "fs-extra"
|
||||||
|
import * as path from "path"
|
||||||
|
import * as crypto from "crypto"
|
||||||
|
|
||||||
|
import yargs from "yargs"
|
||||||
|
import yaml from "js-yaml"
|
||||||
|
import { sync as globSync } from "glob"
|
||||||
|
import ora from "ora"
|
||||||
|
|
||||||
|
import { Parser } from "../parser"
|
||||||
|
import { Scanner } from "../scanner"
|
||||||
|
import { SyntaxKind, Syntax } from "../ast"
|
||||||
|
import { Json, serialize, JsonObject, MapLike, upsearchSync, deepEqual } from "../util"
|
||||||
|
import { DiagnosticIndex } from "../diagnostics"
|
||||||
|
import { TextFile, TextPos, TextSpan } from "../text"
|
||||||
|
|
||||||
|
const PACKAGE_ROOT = path.dirname(upsearchSync('package.json')!);
|
||||||
|
const STORAGE_DIR = path.join(PACKAGE_ROOT, '.test-storage');
|
||||||
|
|
||||||
|
const spinner = ora(`Initializing test session ...`).start();
|
||||||
|
|
||||||
|
function toArray<T>(value: T | T[]): T[] {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
return value === undefined || value === null ? [] : [ value ]
|
||||||
|
}
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
|
||||||
|
public key: string;
|
||||||
|
|
||||||
|
public result?: Json;
|
||||||
|
public error: Error | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public readonly span: TextSpan,
|
||||||
|
public readonly type: string,
|
||||||
|
public readonly text: string,
|
||||||
|
public readonly data: JsonObject,
|
||||||
|
) {
|
||||||
|
this.key = hash([text, data]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LoadTestsOptions {
|
||||||
|
include: string[];
|
||||||
|
exclude: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestSession {
|
||||||
|
|
||||||
|
private failCount = 0;
|
||||||
|
|
||||||
|
public key: string;
|
||||||
|
|
||||||
|
constructor(private tests: Test[] = []) {
|
||||||
|
this.key = `${Date.now().toString()}-${Math.random()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getAllTests() {
|
||||||
|
return this.tests[Symbol.iterator]();;
|
||||||
|
}
|
||||||
|
|
||||||
|
public scanForTests(options?: LoadTestsOptions) {
|
||||||
|
const includes = options?.include ?? ['test/**/*.md'];
|
||||||
|
const excludes = options?.exclude ?? [];
|
||||||
|
spinner.text = 'Scanning for tests [0 found]';
|
||||||
|
for (const include of includes) {
|
||||||
|
for (const filepath of globSync(include, { ignore: excludes })) {
|
||||||
|
spinner.info(`Found file ${filepath}`)
|
||||||
|
for (const test of loadTests(filepath)) {
|
||||||
|
this.tests.push(test);
|
||||||
|
spinner.text = `Scanning for tests [${this.tests.length} found]`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public run() {
|
||||||
|
let i = 1;
|
||||||
|
//let failed = [];
|
||||||
|
for (const test of this.tests) {
|
||||||
|
spinner.text = `Running tests [${i}/${this.tests.length}]`
|
||||||
|
const runner = TEST_RUNNERS[test.type]
|
||||||
|
if (runner === undefined) {
|
||||||
|
spinner.warn(`Test runner '${test.type}' not found.`)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
test.result = runner(test);
|
||||||
|
} catch (e) {
|
||||||
|
test.error = e;
|
||||||
|
this.failCount++;
|
||||||
|
//failed.push(test);
|
||||||
|
spinner.warn(`The following test from ${path.relative(process.cwd(), test.span.file.fullPath)} failed with "${e.message}":\n\n${test.text}\n`)
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (this.failCount > 0) {
|
||||||
|
spinner.fail(`${this.failCount} tests failed.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public save() {
|
||||||
|
fs.mkdirpSync(path.join(STORAGE_DIR, 'snapshots', this.key))
|
||||||
|
for (const test of this.tests) {
|
||||||
|
fs.writeFileSync(path.join(STORAGE_DIR, 'snapshots', this.key, test.key), JSON.stringify(test.result), 'utf8');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasFailedTests() {
|
||||||
|
return this.failCount > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function compare(actual: string, expected: string) {
|
||||||
|
for (const testKey of fs.readdirSync(path.join(STORAGE_DIR, 'snapshots', actual))) {
|
||||||
|
const actualTestResult = readJson(path.join(STORAGE_DIR, 'snapshots', actual, testKey))!;
|
||||||
|
const expectedTestResult = readJson(path.join(STORAGE_DIR, 'snapshots', expected, testKey));
|
||||||
|
if (!deepEqual(actualTestResult, expectedTestResult)) {
|
||||||
|
spinner.warn(`Test ${testKey} does not compare to its expected value.`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isWhiteSpace(ch: string): boolean {
|
||||||
|
return /[\n\r\t ]/.test(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TestFileMetadata {
|
||||||
|
type: string;
|
||||||
|
expect: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function* loadTests(filepath: string): IterableIterator<Test> {
|
||||||
|
|
||||||
|
const file = new TextFile(filepath);
|
||||||
|
const contents = file.getText('utf8');
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
let column = 1
|
||||||
|
let line = 1;
|
||||||
|
let atNewLine = true;
|
||||||
|
|
||||||
|
assertText('---');
|
||||||
|
let yamlStr = '';
|
||||||
|
i += 3;
|
||||||
|
while (!lookaheadEquals('---')) {
|
||||||
|
yamlStr += contents[i++];
|
||||||
|
}
|
||||||
|
i += 3;
|
||||||
|
const metadata = yaml.safeLoad(yamlStr);
|
||||||
|
|
||||||
|
while (i < contents.length) {
|
||||||
|
skipWhiteSpace();
|
||||||
|
if (atNewLine && column >= 5) {
|
||||||
|
const startPos = new TextPos(i, line, column);
|
||||||
|
const text = scanCodeBlock();
|
||||||
|
const endPos = new TextPos(i, line, column);
|
||||||
|
if (metadata['split-lines']) {
|
||||||
|
for (const line of text.split('\n')) {
|
||||||
|
if (line.trim() !== '') {
|
||||||
|
yield new Test(new TextSpan(file, startPos.clone(), endPos), metadata.type, line, metadata);
|
||||||
|
startPos.advance(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
yield new Test(new TextSpan(file, startPos, endPos), metadata.type, text, metadata);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getChar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getChar() {
|
||||||
|
const ch = contents[i++];
|
||||||
|
if (ch === '\n') {
|
||||||
|
column = 1;
|
||||||
|
line++;
|
||||||
|
atNewLine = true;
|
||||||
|
} else {
|
||||||
|
if (!isEmpty(ch)) {
|
||||||
|
atNewLine = false;
|
||||||
|
}
|
||||||
|
column++;
|
||||||
|
}
|
||||||
|
return ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertText(str: string) {
|
||||||
|
for (let k = 0; k < str.length; k++) {
|
||||||
|
if (contents[i+k] !== str[k]) {
|
||||||
|
throw new Error(`Expected '${str}' but got ${contents.substr(i, i+str.length)}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isEmpty(ch: string): boolean {
|
||||||
|
return /[\t ]/.test(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
function lookaheadEquals(str: string): boolean {
|
||||||
|
for (let k = 0; k < str.length; k++) {
|
||||||
|
if (contents[i+k] !== str[k]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function scanCodeBlock() {
|
||||||
|
let out = ''
|
||||||
|
while (i < contents.length) {
|
||||||
|
const ch = getChar();
|
||||||
|
if (ch === '\n') {
|
||||||
|
out += ch;
|
||||||
|
skipWhiteSpace();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (column < 5) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
out += ch;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function skipWhiteSpace() {
|
||||||
|
takeWhile(isWhiteSpace)
|
||||||
|
}
|
||||||
|
|
||||||
|
function takeWhile(pred: (ch: string) => boolean) {
|
||||||
|
let out = '';
|
||||||
|
while (true) {
|
||||||
|
const c0 = contents[i];
|
||||||
|
if (!pred(c0)) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
out += getChar();
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function readJson(filename: string): Json | null {
|
||||||
|
try {
|
||||||
|
return JSON.parse(fs.readFileSync(filename, 'utf8'));
|
||||||
|
} catch (e) {
|
||||||
|
if (e.code === 'ENOENT') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hash(value: Json) {
|
||||||
|
const hasher = crypto.createHash('sha256');
|
||||||
|
hasher.update(JSON.stringify(value));
|
||||||
|
return hasher.digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestRunner = (test: Test) => Json;
|
||||||
|
|
||||||
|
const TEST_RUNNERS: MapLike<TestRunner> = {
|
||||||
|
|
||||||
|
scan(test: Test): Json {
|
||||||
|
const diagnostics = new DiagnosticIndex;
|
||||||
|
const scanner = new Scanner(test.span.file, test.text, test.span.start);
|
||||||
|
const tokens = []
|
||||||
|
while (true) {
|
||||||
|
const token = scanner.scan();
|
||||||
|
if (token.kind === SyntaxKind.EndOfFile) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokens.push(token);
|
||||||
|
}
|
||||||
|
return serialize({
|
||||||
|
diagnostics: [...diagnostics.getAllDiagnostics()],
|
||||||
|
tokens,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
parse(test: Test): Json {
|
||||||
|
const kind = test.data.expect ?? 'SourceFile';
|
||||||
|
const diagnostics = new DiagnosticIndex;
|
||||||
|
const parser = new Parser();
|
||||||
|
const tokens = new Scanner(test.span.file, test.text);
|
||||||
|
let results: Syntax[];
|
||||||
|
switch (kind) {
|
||||||
|
case 'SourceFile':
|
||||||
|
results = [ parser.parseSourceFile(tokens) ];
|
||||||
|
break;
|
||||||
|
case 'SourceElements':
|
||||||
|
results = parser.parseSourceElements(tokens);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`I did not know how to parse ${kind}`)
|
||||||
|
}
|
||||||
|
return serialize({
|
||||||
|
diagnostics: [...diagnostics.getAllDiagnostics()],
|
||||||
|
results,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
yargs
|
||||||
|
.command(['$0 [pattern..]', 'run [pattern..]'], 'Run all tests on the current version of the compiler',
|
||||||
|
yargs => yargs
|
||||||
|
.array('pattern')
|
||||||
|
.describe('pattern', 'Only run the tests matching the given pattern')
|
||||||
|
.array('include')
|
||||||
|
.describe('include', 'Files to scan for tests')
|
||||||
|
.default('include', ['test/**/*.md'])
|
||||||
|
.array('exclude')
|
||||||
|
.describe('exclude', 'Files to never scan for tests')
|
||||||
|
.default('exclude', [])
|
||||||
|
, args => {
|
||||||
|
|
||||||
|
const session = new TestSession();
|
||||||
|
session.scanForTests(args as LoadTestsOptions);
|
||||||
|
session.run();
|
||||||
|
session.save();
|
||||||
|
|
||||||
|
if (session.hasFailedTests()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedKey = fs.readFileSync(path.join(STORAGE_DIR, 'aliases', 'lkg'), 'utf8')
|
||||||
|
compare(session.key, expectedKey)
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.command(['snapshot [name]'], 'Create a new snapshot from the output of the current compiler',
|
||||||
|
yargs => yargs
|
||||||
|
.array('include')
|
||||||
|
.describe('include', 'Files to scan for tests')
|
||||||
|
.default('include', ['test/**/*.md'])
|
||||||
|
.array('exclude')
|
||||||
|
.describe('exclude', 'Files to never scan for tests')
|
||||||
|
.default('exclude', [])
|
||||||
|
, args => {
|
||||||
|
|
||||||
|
// Load and run all tests, saving the results to disk
|
||||||
|
const session = new TestSession();
|
||||||
|
session.scanForTests(args as LoadTestsOptions);
|
||||||
|
session.run();
|
||||||
|
session.save();
|
||||||
|
|
||||||
|
// Set the tests that we have run as being the new 'lkg'
|
||||||
|
fs.mkdirpSync(path.join(STORAGE_DIR, 'aliases'));
|
||||||
|
fs.writeFileSync(path.join(STORAGE_DIR, 'aliases', 'lkg'), session.key, 'utf8')
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.command('inspect <pattern..>', 'Inspect the ouput of a given test',
|
||||||
|
yargs => yargs
|
||||||
|
, args => {
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.version()
|
||||||
|
.help()
|
||||||
|
.argv;
|
|
@ -52,6 +52,7 @@ import {
|
||||||
createBoltExMark,
|
createBoltExMark,
|
||||||
createBoltWhereKeyword,
|
createBoltWhereKeyword,
|
||||||
} from "./ast"
|
} from "./ast"
|
||||||
|
import { outputFile } from "fs-extra";
|
||||||
|
|
||||||
export enum PunctType {
|
export enum PunctType {
|
||||||
Paren,
|
Paren,
|
||||||
|
@ -111,11 +112,11 @@ function isNewLine(ch: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function isIdentStart(ch: string) {
|
function isIdentStart(ch: string) {
|
||||||
return /[_\p{L}]/u.test(ch)
|
return /[_\p{ID_Start}]/u.test(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isIdentPart(ch: string) {
|
function isIdentPart(ch: string) {
|
||||||
return /[_\p{L}\p{Nd}]/u.test(ch)
|
return /[_\p{ID_Continue}]/u.test(ch)
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSymbol(ch: string) {
|
function isSymbol(ch: string) {
|
||||||
|
@ -181,6 +182,86 @@ export class Scanner {
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private scanHexDigit(): number {
|
||||||
|
const c0 = this.peekChar();
|
||||||
|
let out;
|
||||||
|
switch (c0) {
|
||||||
|
case '0': out = 0; break;
|
||||||
|
case '1': out = 1; break;
|
||||||
|
case '2': out = 2; break;
|
||||||
|
case '3': out = 3; break;
|
||||||
|
case '4': out = 4; break;
|
||||||
|
case '5': out = 5; break;
|
||||||
|
case '6': out = 6; break;
|
||||||
|
case '7': out = 7; break;
|
||||||
|
case '8': out = 8; break;
|
||||||
|
case '9': out = 9; break;
|
||||||
|
case 'a': out = 10; break;
|
||||||
|
case 'b': out = 11; break;
|
||||||
|
case 'c': out = 12; break;
|
||||||
|
case 'd': out = 13; break;
|
||||||
|
case 'e': out = 14; break;
|
||||||
|
case 'f': out = 15; break;
|
||||||
|
case 'A': out = 10; break;
|
||||||
|
case 'B': out = 11; break;
|
||||||
|
case 'C': out = 12; break;
|
||||||
|
case 'D': out = 13; break;
|
||||||
|
case 'E': out = 14; break;
|
||||||
|
case 'F': out = 15; break;
|
||||||
|
default:
|
||||||
|
throw new ScanError(this.file, this.currPos.clone(), c0);
|
||||||
|
}
|
||||||
|
this.getChar();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private scanEscapeSequence(): string {
|
||||||
|
this.assertChar('\\')
|
||||||
|
const c0 = this.peekChar();
|
||||||
|
switch (c0) {
|
||||||
|
case 'a':
|
||||||
|
this.getChar();
|
||||||
|
return '\n'
|
||||||
|
case 'b':
|
||||||
|
this.getChar();
|
||||||
|
return '\b'
|
||||||
|
case 'f':
|
||||||
|
this.getChar();
|
||||||
|
return '\f'
|
||||||
|
case 'n':
|
||||||
|
this.getChar();
|
||||||
|
return '\n'
|
||||||
|
case 'r':
|
||||||
|
this.getChar();
|
||||||
|
return '\r'
|
||||||
|
case 't':
|
||||||
|
this.getChar();
|
||||||
|
return '\t'
|
||||||
|
case 'v':
|
||||||
|
this.getChar();
|
||||||
|
return '\v'
|
||||||
|
case '0':
|
||||||
|
this.getChar();
|
||||||
|
return '\0'
|
||||||
|
case 'u':
|
||||||
|
{
|
||||||
|
const d0 = this.scanHexDigit();
|
||||||
|
const d1 = this.scanHexDigit();
|
||||||
|
const d2 = this.scanHexDigit();
|
||||||
|
const d3 = this.scanHexDigit();
|
||||||
|
return String.fromCharCode(d0 * (16 ** 3) + d1 * (16 ** 2) + d2 * 16 + d3);
|
||||||
|
}
|
||||||
|
case 'x':
|
||||||
|
{
|
||||||
|
const d0 = this.scanHexDigit();
|
||||||
|
const d1 = this.scanHexDigit();
|
||||||
|
return String.fromCharCode(d0 * 16 + d1);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new ScanError(this.file, this.currPos.clone(), c0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public scan(): BoltToken {
|
public scan(): BoltToken {
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -227,15 +308,17 @@ export class Scanner {
|
||||||
let text = ''
|
let text = ''
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const c1 = this.getChar();
|
const c1 = this.peekChar();
|
||||||
if (c1 === EOF) {
|
if (c1 === EOF) {
|
||||||
throw new ScanError(this.file, this.currPos.clone(), EOF);
|
throw new ScanError(this.file, this.currPos.clone(), EOF);
|
||||||
}
|
}
|
||||||
if (c1 === '"') {
|
if (c1 === '"') {
|
||||||
|
this.getChar();
|
||||||
break;
|
break;
|
||||||
} else if (c1 === '\\') {
|
} else if (c1 === '\\') {
|
||||||
this.scanEscapeSequence()
|
text += this.scanEscapeSequence()
|
||||||
} else {
|
} else {
|
||||||
|
this.getChar();
|
||||||
text += c1
|
text += c1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -246,7 +329,7 @@ export class Scanner {
|
||||||
|
|
||||||
} else if (isDigit(c0)) {
|
} else if (isDigit(c0)) {
|
||||||
|
|
||||||
const digits = this.takeWhile(isDigit)
|
const digits = this.takeWhile(isDigit);
|
||||||
const endPos = this.currPos.clone();
|
const endPos = this.currPos.clone();
|
||||||
return createBoltIntegerLiteral(BigInt(digits), new TextSpan(this.file, startPos, endPos));
|
return createBoltIntegerLiteral(BigInt(digits), new TextSpan(this.file, startPos, endPos));
|
||||||
|
|
||||||
|
|
37
src/text.ts
37
src/text.ts
|
@ -1,6 +1,7 @@
|
||||||
|
|
||||||
import * as path from "path"
|
import * as path from "path"
|
||||||
import * as fs from "fs"
|
import * as fs from "fs"
|
||||||
|
import { serializeTag, serialize } from "./util";
|
||||||
|
|
||||||
export class TextFile {
|
export class TextFile {
|
||||||
|
|
||||||
|
@ -14,11 +15,15 @@ export class TextFile {
|
||||||
return path.resolve(this.origPath)
|
return path.resolve(this.origPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
public getText(): string {
|
[serializeTag]() {
|
||||||
|
return this.origPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getText(encoding: BufferEncoding = 'utf8'): string {
|
||||||
if (this.cachedText !== null) {
|
if (this.cachedText !== null) {
|
||||||
return this.cachedText;
|
return this.cachedText;
|
||||||
}
|
}
|
||||||
const text = fs.readFileSync(this.fullPath, 'utf8');
|
const text = fs.readFileSync(this.fullPath, encoding);
|
||||||
this.cachedText = text;
|
this.cachedText = text;
|
||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
|
@ -39,6 +44,26 @@ export class TextPos {
|
||||||
return new TextPos(this.offset, this.line, this.column)
|
return new TextPos(this.offset, this.line, this.column)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[serializeTag]() {
|
||||||
|
return {
|
||||||
|
offset: this.offset,
|
||||||
|
line: this.line,
|
||||||
|
column: this.column,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public advance(str: string) {
|
||||||
|
for (const ch of str) {
|
||||||
|
if (ch === '\n') {
|
||||||
|
this.line++;
|
||||||
|
this.column = 1;
|
||||||
|
} else {
|
||||||
|
this.column++;
|
||||||
|
}
|
||||||
|
this.offset++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TextSpan {
|
export class TextSpan {
|
||||||
|
@ -55,5 +80,13 @@ export class TextSpan {
|
||||||
return new TextSpan(this.file, this.start.clone(), this.end.clone());
|
return new TextSpan(this.file, this.start.clone(), this.end.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[serializeTag]() {
|
||||||
|
return {
|
||||||
|
file: serialize(this.file),
|
||||||
|
start: serialize(this.start),
|
||||||
|
end: serialize(this.end),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
54
src/util.ts
54
src/util.ts
|
@ -6,14 +6,12 @@ import * as os from "os"
|
||||||
import moment from "moment"
|
import moment from "moment"
|
||||||
import chalk from "chalk"
|
import chalk from "chalk"
|
||||||
import { LOG_DATETIME_FORMAT } from "./constants"
|
import { LOG_DATETIME_FORMAT } from "./constants"
|
||||||
import { E_CANDIDATE_FUNCTION_REQUIRES_THIS_PARAMETER } from "./diagnostics"
|
|
||||||
import { isPrimitive } from "util"
|
|
||||||
export function isPowerOf(x: number, n: number):boolean {
|
export function isPowerOf(x: number, n: number):boolean {
|
||||||
const a = Math.log(x) / Math.log(n);
|
const a = Math.log(x) / Math.log(n);
|
||||||
return Math.pow(a, n) == x;
|
return Math.pow(a, n) == x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function some<T>(iterator: Iterator<T>, pred: (value: T) => boolean): boolean {
|
export function some<T>(iterator: Iterator<T>, pred: (value: T) => boolean): boolean {
|
||||||
while (true) {
|
while (true) {
|
||||||
const { value, done } = iterator.next();
|
const { value, done } = iterator.next();
|
||||||
|
@ -194,16 +192,32 @@ export type Newable<T> = {
|
||||||
new(...args: any): T;
|
new(...args: any): T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isPrimitive(value: any): boolean {
|
||||||
|
return (typeof(value) !== 'function' && typeof(value) !== 'object') || value === null;
|
||||||
|
}
|
||||||
|
|
||||||
export const serializeTag = Symbol('serializer tag');
|
export const serializeTag = Symbol('serializer tag');
|
||||||
|
|
||||||
const serializableClasses = new Map<string, Newable<{ [serializeTag](): Json }>>();
|
const serializableClasses = new Map<string, Newable<{ [serializeTag](): Json }>>();
|
||||||
|
|
||||||
export function serialize(value: any): Json {
|
export function serialize(value: any): Json {
|
||||||
if (isPrimitive(value)) {
|
if (isPrimitive(value)) {
|
||||||
|
if (typeof(value) === 'bigint') {
|
||||||
|
return {
|
||||||
|
type: 'bigint',
|
||||||
|
value: value.toString(),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
return {
|
return {
|
||||||
type: 'primitive',
|
type: 'primitive',
|
||||||
value,
|
value,
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
return {
|
||||||
|
type: 'array',
|
||||||
|
elements: value.map(serialize)
|
||||||
|
}
|
||||||
} else if (isObjectLike(value)) {
|
} else if (isObjectLike(value)) {
|
||||||
if (isPlainObject(value)) {
|
if (isPlainObject(value)) {
|
||||||
return {
|
return {
|
||||||
|
@ -219,13 +233,9 @@ export function serialize(value: any): Json {
|
||||||
data: value[serializeTag](),
|
data: value[serializeTag](),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
console.log(value);
|
||||||
throw new Error(`Could not serialize ${value}: it was a non-primitive object and has no serializer tag.`)
|
throw new Error(`Could not serialize ${value}: it was a non-primitive object and has no serializer tag.`)
|
||||||
}
|
}
|
||||||
} else if (Array.isArray(value)) {
|
|
||||||
return {
|
|
||||||
type: 'array',
|
|
||||||
elements: value.map(serialize)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Could not serialize ${value}: is was not recognised as a primitive type, an object, a class instance, or an array.`)
|
throw new Error(`Could not serialize ${value}: is was not recognised as a primitive type, an object, a class instance, or an array.`)
|
||||||
}
|
}
|
||||||
|
@ -504,7 +514,7 @@ export function upsearchSync(filename: string, startDir = '.') {
|
||||||
return filePath
|
return filePath
|
||||||
}
|
}
|
||||||
const { root, dir } = path.parse(currDir);
|
const { root, dir } = path.parse(currDir);
|
||||||
if (dir === root) {
|
if (currDir === root) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
currDir = dir;
|
currDir = dir;
|
||||||
|
@ -638,3 +648,29 @@ export function format(message: string, data: MapLike<FormatArg>) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deepEqual(a: any, b: any): boolean {
|
||||||
|
if (isPrimitive(a) && isPrimitive(b)) {
|
||||||
|
return a === b;
|
||||||
|
} else if (Array.isArray(a) && Array.isArray(b)) {
|
||||||
|
if (a.length !== b.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (!deepEqual(a[i], b[i])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else if (isPlainObject(a) && isPlainObject(b)) {
|
||||||
|
const unmarked = new Set(Object.keys(b));
|
||||||
|
for (const key of Object.keys(a)) {
|
||||||
|
if (!deepEqual(a[key], b[key])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
unmarked.delete(key);
|
||||||
|
}
|
||||||
|
return unmarked.size === 0;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
53
test/scan/000-bolt-identifier.md
Normal file
53
test/scan/000-bolt-identifier.md
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
---
|
||||||
|
type: scan
|
||||||
|
expect: BoltIdentifier
|
||||||
|
split-lines: true
|
||||||
|
---
|
||||||
|
|
||||||
|
The most simple identifiers are those made out of ASCII letters:
|
||||||
|
|
||||||
|
Foo
|
||||||
|
Bar
|
||||||
|
Baz
|
||||||
|
|
||||||
|
However, they may also contain digits as long as they do not begin with a
|
||||||
|
digit:
|
||||||
|
|
||||||
|
Var1
|
||||||
|
Var2
|
||||||
|
Var10029384
|
||||||
|
|
||||||
|
Identifiers may be as long as you want:
|
||||||
|
|
||||||
|
ThisIsALongAndValidIdentifier
|
||||||
|
ThisIsAnEvenLongButStilCompletelyValidIdentifier
|
||||||
|
|
||||||
|
Moreover, they may have arbitrary underscores (`_`) in their names.
|
||||||
|
|
||||||
|
a_valid_identifier
|
||||||
|
another__0000__valid_identfier
|
||||||
|
_1
|
||||||
|
__2
|
||||||
|
___3
|
||||||
|
|
||||||
|
They may even be nothing more than underscores:
|
||||||
|
|
||||||
|
_
|
||||||
|
__
|
||||||
|
___
|
||||||
|
|
||||||
|
All identifiers starting with a `ID_Start` character are valid identifiers,
|
||||||
|
including `Other_ID_Start`:
|
||||||
|
|
||||||
|
℘rototype
|
||||||
|
℮llipsis
|
||||||
|
|
||||||
|
Likewise, the following code points using `Other_ID_Continue` are also valid:
|
||||||
|
|
||||||
|
α·β
|
||||||
|
ano·teleia
|
||||||
|
|
||||||
|
And, of course, the combination of `ID_Start` and `ID_Continue`:
|
||||||
|
|
||||||
|
alfa·beta
|
||||||
|
|
20
test/scan/001-bolt-string-literal.md
Normal file
20
test/scan/001-bolt-string-literal.md
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
type: scan
|
||||||
|
expect: BoltStringLiteral
|
||||||
|
split-lines: true
|
||||||
|
---
|
||||||
|
|
||||||
|
A string may hold arbirary ASCII characters, including spaces:
|
||||||
|
|
||||||
|
"Foo!"
|
||||||
|
"Once upon a time ..."
|
||||||
|
|
||||||
|
Special ASCII characters have no effect, other than that they are appended to
|
||||||
|
the contents of the string:
|
||||||
|
|
||||||
|
"S+me w3!rd @SCII ch@r$"
|
||||||
|
|
||||||
|
Some special escape sequences:
|
||||||
|
|
||||||
|
"\n\r"
|
||||||
|
"\n"
|
57
test/scan/002-bolt-integer-literal.md
Normal file
57
test/scan/002-bolt-integer-literal.md
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
---
|
||||||
|
type: scan
|
||||||
|
expect: BoltIntegerLiteral
|
||||||
|
split-lines: true
|
||||||
|
---
|
||||||
|
|
||||||
|
All decimal digits are valid integers.
|
||||||
|
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
0
|
||||||
|
|
||||||
|
Any combination of decimal digits are valid integers, including integers
|
||||||
|
prefixed with an abirary amount of zeroes.
|
||||||
|
|
||||||
|
12345
|
||||||
|
99
|
||||||
|
10
|
||||||
|
01
|
||||||
|
000
|
||||||
|
0010
|
||||||
|
|
||||||
|
In binary mode, integers are read in base-2.
|
||||||
|
|
||||||
|
0b0
|
||||||
|
0b1
|
||||||
|
0b10010
|
||||||
|
0b00100
|
||||||
|
0b00000
|
||||||
|
|
||||||
|
This means the following expressions are invalid in binary mode:
|
||||||
|
|
||||||
|
0b20001
|
||||||
|
0b12345
|
||||||
|
0b00003
|
||||||
|
|
||||||
|
In octal mode, integers are read in base-8.
|
||||||
|
|
||||||
|
0o0
|
||||||
|
0o00000
|
||||||
|
0o007
|
||||||
|
0o706
|
||||||
|
0o12345
|
||||||
|
|
||||||
|
This means the following expressions are invalid in octal mode:
|
||||||
|
|
||||||
|
0o8
|
||||||
|
0o9
|
||||||
|
0o123456789
|
||||||
|
|
|
@ -5,9 +5,12 @@ const path = require("path");
|
||||||
module.exports = {
|
module.exports = {
|
||||||
target: 'node',
|
target: 'node',
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
entry: './src/bin/bolt.ts',
|
entry: {
|
||||||
|
'bolt': './src/bin/bolt.ts',
|
||||||
|
'bolt-test': './src/bin/bolt-test.ts',
|
||||||
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: 'bin/bolt.js',
|
filename: 'bin/[name].js',
|
||||||
path: path.resolve(__dirname, 'build'),
|
path: path.resolve(__dirname, 'build'),
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
|
|
Loading…
Reference in a new issue