turbo-editors/turbo-jspublic Fork 0
main
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-js.git
git clone ssh://git@rickub.com/turbo-editors/turbo-js.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

📦 Turbo JS 91999d1 · on main · k33g · 9h ago
reference_test.go · 107 lines · 5.0 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
package jslang

import (
	"strings"
	"testing"

	"rickub.com/turbo-editors/turbo-core/syntax"
)

// TestTheLanguagesReferenceIsTrue holds docs/*/reference/languages.md to the
// scanners. Every row of its JavaScript and JSON tables that no other test
// here covers is checked, so a reference claim and the code cannot drift apart
// quietly.
//
// The MAX_SIZE and 1.toString cases are the ones that document a limitation
// rather than a feature: the leading-capital rule colours a
// SCREAMING_SNAKE_CASE constant as a class, the reference says so, and this is
// what stops somebody "fixing" it without also fixing the sentence.
func TestTheLanguagesReferenceIsTrue(t *testing.T) {
	tests := []struct {
		highlight func(string) [][]syntax.Span
		src       string
		word      string
		want      syntax.Class
	}{
		// JavaScript: the "Recognised" table, row by row.
		{Highlight, "let x = 1; enum E {}", "enum", syntax.ClassKeyword},
		{Highlight, "x = Infinity;", "Infinity", syntax.ClassConstant},
		{Highlight, "x = structuredClone(y);", "structuredClone", syntax.ClassBuiltin},
		{Highlight, "x = __dirname;", "__dirname", syntax.ClassBuiltin},
		{Highlight, "x = performance.now();", "performance", syntax.ClassBuiltin},
		{Highlight, "x = window.document;", "window", syntax.ClassBuiltin},
		{Highlight, "async function fetchAll() {}", "fetchAll", syntax.ClassFunction},
		{Highlight, "class widget extends Base {}", "widget", syntax.ClassType},
		{Highlight, "const e = new EventEmitter();", "EventEmitter", syntax.ClassType},
		{Highlight, "obj.method();", "method", syntax.ClassFunction},
		{Highlight, "const c = user?.class;", "class", syntax.ClassIdentifier},
		{Highlight, "const v = set(k, v);", "set", syntax.ClassFunction},
		{Highlight, "this.#reset();", "#reset", syntax.ClassFunction},
		{Highlight, "@observable.ref count = 0;", "@observable.ref", syntax.ClassAttribute},
		{Highlight, "const $el = 1;", "$el", syntax.ClassIdentifier},
		{Highlight, "const 名前 = 1;", "名前", syntax.ClassIdentifier},
		{Highlight, "const n = 0xFFn;", "0xFFn", syntax.ClassNumber},
		{Highlight, "const n = 2E+10;", "2E+10", syntax.ClassNumber},
		{Highlight, "x = y >>>= 2;", ">>>=", syntax.ClassOperator},
		{Highlight, "x = a ** b;", "**", syntax.ClassOperator},
		{Highlight, "x = a === b;", "===", syntax.ClassOperator},
		{Highlight, "f(...args);", "...", syntax.ClassPunctuation},
		{Highlight, "x = a?.b;", "?.", syntax.ClassOperator},
		// JavaScript: the prose rules under the table.
		{Highlight, "x = y / z;", "/", syntax.ClassOperator},
		{Highlight, "x = [1] / 2;", "/", syntax.ClassOperator},
		{Highlight, "const n = 0xE+1;", "+", syntax.ClassOperator},
		{Highlight, "const MAX_SIZE = 10;", "MAX_SIZE", syntax.ClassType},
		{Highlight, "const Count = 1;", "Count", syntax.ClassType},
		// JavaScript: the "Not recognised" table, each row as the scanner reads it.
		{Highlight, "const t = `a ${b} c`;", "${b}", syntax.ClassString},
		{Highlight, "const el = <div>;", "<", syntax.ClassOperator},
		{Highlight, "const el = <div>;", "div", syntax.ClassIdentifier},
		{Highlight, "/* a /* b */ let x;", "let", syntax.ClassKeyword},
		{Highlight, "x = 1 # not a comment", "#", syntax.ClassIdentifier},
		// JSON: every row of its table.
		{HighlightJSON, `{"name": "demo"}`, `"name"`, syntax.ClassAttribute},
		{HighlightJSON, `{"name": "demo"}`, `"demo"`, syntax.ClassString},
		{HighlightJSON, `{"n": -12.5e+3}`, "-12.5e+3", syntax.ClassNumber},
		{HighlightJSON, `{"n": 0.5}`, "0.5", syntax.ClassNumber},
		{HighlightJSON, `{"a": false}`, "false", syntax.ClassConstant},
		{HighlightJSON, `{"a": [1]}`, "[", syntax.ClassPunctuation},
		{HighlightJSON, `{"a": 1} // trailing`, "// trailing", syntax.ClassComment},
		{HighlightJSON, `{"a": NaN}`, "NaN", syntax.ClassIdentifier},
		{HighlightJSON, `{"a" 1}`, `"a"`, syntax.ClassString},
	}

	for _, test := range tests {
		t.Run(test.src+"/"+test.word, func(t *testing.T) {
			index := strings.Index(test.src, test.word)
			if index < 0 {
				t.Fatalf("%q not in %q", test.word, test.src)
			}
			col := len([]rune(test.src[:index]))
			got, ok := classAt(test.highlight(test.src), 0, col)
			if !ok && test.want == syntax.ClassIdentifier && test.word == "#" {
				// A stray # outside a private name is stepped over uncoloured,
				// which is what "not a comment" means for the reference's row.
				return
			}
			if !ok || got != test.want {
				t.Errorf("%q in %q is %v (covered %v), want %v", test.word, test.src, got, ok, test.want)
			}
		})
	}
}

// TestASingleQuoteInJSONIsSteppedOver pins the JSON5 row of the reference: a
// ' is not a string delimiter in JSON, and the scanner neither colours it nor
// stops at it.
func TestASingleQuoteInJSONIsSteppedOver(t *testing.T) {
	const src = `{'a': 1}`
	spans := HighlightJSON(src)

	if _, ok := classAt(spans, 0, 1); ok {
		t.Errorf("the single quote is coloured; the reference says JSON5 is not recognised")
	}
	if got, ok := classAt(spans, 0, 6); !ok || got != syntax.ClassNumber {
		t.Errorf("the number after the single-quoted word is %v (covered %v), want number", got, ok)
	}
}