Skip to content

New representation for scope handler tests #1524

Closed
@pokey

Description

@pokey

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

  • 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

Labels

code qualityImprovements to code qualityto discussPlan to discuss at meet-up

Type

No type

Projects

Status

Done

Relationships

None yet

Development

No branches or pull requests

Issue actions