Closed
Description
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§$$$¶
- The
- 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
- Automatically generate test case from source code. Can probably steal some code from Scope visualizer #1523
- Parse output to run as a test. Before running the scope handler, it should remove all the special comment lines and dedent every line by the correct amount. Note that it should be able to only look at the delimiters (eg
{}
), as the internal markers (eg^^^
) are just for readability
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 {^ ^}
Metadata
Metadata
Assignees
Type
Projects
Status
Done