The problem
We need a better way to represent our test cases for scope handlers, because today we end up needing to record a bunch of commands to test edge cases, and the tests aren't easy to read. In addition, once #1523 is merged, we expect that most scope type development will be done by viewing the output of the scope visualizer, so we'd like test cases that mirror the scope visualizer as closely as possible
The solution
We'd like an ascii representation that is readable by humans and parsable by machine. The idea is as follows:
- We intersperse comment lines among the source code that point out regions in the line above
- The comment lines begin with the language's comment token (eg
//), followed by !
- Each source code line is indented by extra whitespace at the beginning to allow the comment lines to be able to point at the start of the line above if necessary. If we didn't do this, then a source code line could begin at the same spot as the
// on the line below, so we couldn't point out the beginning of the line.
- The content range is marked by
{^^^}
- The domain is marked by
[---]
- The removal range is marked by
(xxx)
- The iteration scope is marked by
<***>
- We need to figure out
interior / leadingDelimiter / trailingDelimiter
- Would like to avoid unicode if possible
- Here are suggestions from chatGPT:
- The
interior is marked by /###\
- The
leadingDelimiter is marked by «@@@»
- The
trailingDelimiter is marked by §$$$¶
- If any range is equal to
contentRange, it does not get a line, as that is the implicit assumption
- In each of the above, the surrounding brackets are outside of the range, so that we can handle empty ranges by using eg
{}
- Each range type (eg content, domain, removal, etc) gets its own comment line
- If there are overlapping ranges, the range with later start or earlier end gets its own set of lines after the other scope, beginning with
//!2 (or //!3, etc). Note that ranges are overlapping even if they are 1 character apart, because otherwise there's no room for the brackets
- The start of the file will contain metadata lines to indicate scope type being tested and language id, eg
//!! scope: {type: namedFunction}
//!! languageId: typescript, plaintext
- Note that multiple examples of the same scope type could be present in one file, so that we don't need a million test files like we have today
- Note that we don't handle indicating
isPreferredOver; need special tests for that
Components
Considerations
Examples
value
//!! scopeType: {type: value}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
//! xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
//! ----------------------------------------------------------------------------
name
//!! scopeType: {type: name}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! ^^^^^^^^^^^^
//! ----------------------------------------------------------------------------
statement
//!! scopeType: {type: statement}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
namedFunction
//!! scopeType: {type: namedFunction}
//!! languageId: typescript
function aaa() {
//!1 {^^^^^^^^^^^^^^^^
function bbb() {
//!2 {^^^^^^^^^^^^^^^^
const ccc = "ddd";
}
//!2 ^^^}
}
//!1 ^}
collectionKey
//!! scopeType: {type: collectionKey}
//!! languageId: typescript
const aaa = {
bbb: {
//!1 ^^^
//!1 [------
ccc: "ddd",
//!2 ^^^
//!2 [-----------]
},
//!1 --]
}
token
#!! scopeType: {type: token}
#!! languageId: plaintext
foo.bar.baz
#!1 ^^^
#!2 ^
#!3 ^^^
#!4 ^
#!5 ^^^
Here are some alternative representations to consider:
Merge ranges onto one line
value
//!! scopeType: {type: value}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! ------------------xxx^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^-
name
//!! scopeType: {type: name}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! ------^^^^^^^^^^^^----------------------------------------------------------
statement
//!! scopeType: {type: statement}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
namedFunction
//!! scopeType: {type: namedFunction}
//!! languageId: typescript
function aaa() {
//!1 {^^^^^^^^^^^^^^^^
function bbb() {
//!2 {^^^^^^^^^^^^^^^^
const ccc = "ddd";
}
//!2 ^^^}
}
//!1 ^}
collectionKey
//!! scopeType: {type: collectionKey}
//!! languageId: typescript
const aaa = {
bbb: {
//!1 ^^^
//!1 [------
ccc: "ddd",
//!2 ^^^--------
},
//!1 --]
}
token
#!! scopeType: {type: token}
#!! languageId: plaintext
foo.bar.baz
#!1 ^^^
#!2 ^
#!3 ^^^
#!4 ^
#!5 ^^^
Always surrounding brackets
value
//!! scopeType: {type: value}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! {^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^}
//! (xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx)
//! [----------------------------------------------------------------------------]
name
//!! scopeType: {type: name}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! {^^^^^^^^^^^^}
//! [----------------------------------------------------------------------------]
statement
//!! scopeType: {type: statement}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! {^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^}
namedFunction
//!! scopeType: {type: namedFunction}
//!! languageId: typescript
function aaa() {
//! {^^^^^^^^^^^^^^^^
function bbb() {
//!2 {^^^^^^^^^^^^^^^^
const ccc = "ddd";
}
//!2 ^^^}
}
//! ^}
collectionKey
//!! scopeType: {type: collectionKey}
//!! languageId: typescript
const aaa = {
bbb: {
//! {^^^}
//! [------
ccc: "ddd",
//!2 {^^^}
//!2 [-----------]
},
//! --]
}
token
#!! scopeType: {type: token}
#!! languageId: plaintext
foo.bar.baz
#! {^^^} {^}
#!2 {^} {^^^}
#!3 {^^^}
Internal markers on every line of range
value, name, statement, and token
(as above)
namedFunction
//!! scopeType: {type: namedFunction}
//!! languageId: typescript
function aaa() {
//! {^^^^^^^^^^^^^^^^
function bbb() {
//! ^^^^^^^^^^^^^^^^^^
//!2 {^^^^^^^^^^^^^^^^
const ccc = "ddd";
//! ^^^^^^^^^^^^^^^^^^^^^^
//!2 ^^^^^^^^^^^^^^^^^^^^^^
}
//! ^^^
//!2 ^^^}
}
//! ^}
collectionKey
//!! scopeType: {type: collectionKey}
//!! languageId: typescript
const aaa = {
bbb: {
//! {^^^}
//! [------
ccc: "ddd",
//! -------------
//!2 {^^^}
//!2 [-----------]
},
//! --]
}
No internal markers
value
//!! scopeType: {type: value}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! { }
//! [ ]
//! ( )
name
//!! scopeType: {type: name}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! { }
//! [ ]
statement
//!! scopeType: {type: statement}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! { }
namedFunction
//!! scopeType: {type: namedFunction}
//!! languageId: typescript
function aaa() {
//! {
function bbb() {
//!2 {
const ccc = "ddd";
}
//!2 }
}
//! }
collectionKey
//!! scopeType: {type: collectionKey}
//!! languageId: typescript
const aaa = {
bbb: {
//! { }
//! [
ccc: "ddd",
//!2 { }
//!2 [ ]
},
//! ]
}
token
#!! scopeType: {type: token}
#!! languageId: plaintext
foo.bar.baz
#! { } { }
#!2 { } { }
#!3 { }
Using `.` to indicate whitespace
value
//!! scopeType: {type: value}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! ....................{......................................................}
//! [............................................................................]
//! .................(.........................................................)
name
//!! scopeType: {type: name}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! .....{............}.........................................................
//! [............................................................................]
statement
//!! scopeType: {type: statement}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! {............................................................................}
namedFunction
//!! scopeType: {type: namedFunction}
//!! languageId: typescript
function aaa() {
//! {................
function bbb() {
//!2 .{................
const ccc = "ddd";
}
//!2 ...}
}
//! .}
collectionKey
//!! scopeType: {type: collectionKey}
//!! languageId: typescript
const aaa = {
bbb: {
//! .{...}..
//! .[......
ccc: "ddd",
//!2 ...{...}.......
//!2 ...[...........]
},
//! ....]
}
token
#!! scopeType: {type: token}
#!! languageId: plaintext
foo.bar.baz
#! {...}..{.}..
#!2 ..{.}..{...}
#!3 ...{...}...
Using `.` only where necessary
value
//!! scopeType: {type: value}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! . . {. .}
//! [. . ..]
//! (. .)
name
//!! scopeType: {type: name}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! . {. .} .
//! [. .]
statement
//!! scopeType: {type: statement}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! {. .}
namedFunction
//!! scopeType: {type: namedFunction}
//!! languageId: typescript
function aaa() {
//! {.
function bbb() {
//!2 {.
const ccc = "ddd";
}
//!2 .}
}
//! .}
collectionKey
//!! scopeType: {type: collectionKey}
//!! languageId: typescript
const aaa = {
bbb: {
//! {. .}
//! [.
ccc: "ddd",
//!2 {. .} .
//!2 [. .]
},
//! .]
}
token
#!! scopeType: {type: token}
#!! languageId: plaintext
foo.bar.baz
#! {. .}. {.}
#!2 {.} .{. .}
#!3 {. .}
Using markers only where necessary
value
//!! scopeType: {type: value}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! . . {^ ^}
//! [- . .-]
//! (x x)
name
//!! scopeType: {type: name}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! . {^ ^} .
//! [- -]
statement
//!! scopeType: {type: statement}
//!! languageId: typescript
const removalRange = getRelatedRange(match, scopeTypeType, "removal", true);
//! {^ ^}
namedFunction
//!! scopeType: {type: namedFunction}
//!! languageId: typescript
function aaa() {
//! {^
function bbb() {
//!2 {^
const ccc = "ddd";
}
//!2 ^}
}
//! ^}
collectionKey
//!! scopeType: {type: collectionKey}
//!! languageId: typescript
const aaa = {
bbb: {
//! {^ ^}
//! [-
ccc: "ddd",
//!2 {^ ^} .
//!2 [- -]
},
//! -]
}
token
#!! scopeType: {type: token}
#!! languageId: plaintext
foo.bar.baz
#! {^ ^}. {^} .
#!2 {^} .{^ ^}
#!3 {^ ^}
The problem
We need a better way to represent our test cases for scope handlers, because today we end up needing to record a bunch of commands to test edge cases, and the tests aren't easy to read. In addition, once #1523 is merged, we expect that most scope type development will be done by viewing the output of the scope visualizer, so we'd like test cases that mirror the scope visualizer as closely as possible
The solution
We'd like an ascii representation that is readable by humans and parsable by machine. The idea is as follows:
//), followed by!//on the line below, so we couldn't point out the beginning of the line.{^^^}[---](xxx)<***>interior/leadingDelimiter/trailingDelimiterinterioris marked by/###\leadingDelimiteris marked by«@@@»trailingDelimiteris marked by§$$$¶contentRange, it does not get a line, as that is the implicit assumption{}//!2(or//!3, etc). Note that ranges are overlapping even if they are 1 character apart, because otherwise there's no room for the bracketsisPreferredOver; need special tests for thatComponents
{}), as the internal markers (eg^^^) are just for readabilityConsiderations
Examples
valuenamestatementnamedFunctioncollectionKeytokenHere are some alternative representations to consider:
Merge ranges onto one line
valuenamestatementnamedFunctioncollectionKeytokenAlways surrounding brackets
valuenamestatementnamedFunctioncollectionKeytokenInternal markers on every line of range
value,name,statement, andtoken(as above)
namedFunctioncollectionKeyNo internal markers
valuenamestatementnamedFunctioncollectionKeytokenUsing `.` to indicate whitespace
valuenamestatementnamedFunctioncollectionKeytokenUsing `.` only where necessary
valuenamestatementnamedFunctioncollectionKeytokenUsing markers only where necessary
valuenamestatementnamedFunctioncollectionKeytoken