Giter Club home page Giter Club logo

Comments (6)

Ptival avatar Ptival commented on July 17, 2024 1

Ok, I have a pull request ready for reviewing. Here's my final array of tests in case anyone in the future tries to fix this:

var fun = () => {}
var x

// Things are correctly indented if their HERE aligns with this one:
//-----------------------HERE

const a = 0 >
    1 //-----------------HERE
const b = 0 < 1 && 2 >
    3 //-----------------HERE
const c = 0 < 1 || 2 >
    3 //-----------------HERE
const d = 0 < 1 ? 2 : 3 + 4 >
    5 ? 6 : 7 //---------HERE
const d2 = 0 < 1 ? 2 : 3 * 4 >
    5 ? 6 : 7 //---------HERE
const d3 = 0 < 1 ? 2 : 3 / 4 >
    5 ? 6 : 7 //---------HERE
const d4 = 0 < 1 ? 2 : 3 % 4 >
    5 ? 6 : 7 //---------HERE
const e = [
    1 <
        2,
    3 >
        4, //------------HERE
]
const f = [] as Array<number>
fun() //-----------------HERE
x = 1 as number >
    2 //-----------------HERE
var g = 1 as number >
    2 //-----------------HERE
type dummy1 = number
type dummy2 = dummy1
g = 1 as number < 2 , 2 as number >
    2 //-----------------HERE

// Fortunately, TypeScript disallows:
//
// x = 0 as a < b , c >
// f()
//
// when a is not generic (and also is not a built-in type)

type A = Array<number>
fun() //-----------------HERE
type B = Array<Array<number>>
fun() //-----------------HERE
type One<A> = number
type Two<A, B> = number
type C = Two <
    number , number[
    ]
>
fun() //-----------------HERE
type D = Array<number[]>
fun() //-----------------HERE

type Nested = Two < One < number >
    , number > //--------HERE

// Unfortunately, something like `foo < bar , baz > \n f(x)` can look syntactically like:
// - a type `foo` with two parameters, then `f(x)` on the next line
// - the sequencing of two comparisons, `foo < bar`, and `baz > f(x)`
var foo
var bar
var beep
type foo<A,B> = {}
type bar = {}

// So the two following examples are hard to distinguish!
beep = foo <
    bar , bar >
    fun() //-------------HERE

type beep = foo <
    bar , bar >
fun() //-----------------HERE

// And we cannot easily rely on the keyword `type` being there, because of those:
type baz = bar
beep = foo <
    bar , bar >
    fun() //-------------HERE

type bazz
    = bar
beep = foo <
    bar , bar >
    fun() //-------------HERE

type bazzz<A>
    = bar //-------------HERE
beep = foo <
    bar , bar >
    fun() //-------------HERE

type Num<T> = number
const g1 = 1 as Num<A> >
    2 //-----------------HERE

They all align correctly after my patch!

from typescript.el.

lddubeau avatar lddubeau commented on July 17, 2024

I can reproduce the problem here. A PR to fix the issue is welcome.

from typescript.el.

Ptival avatar Ptival commented on July 17, 2024

Well, I am currently looking into it, but it turns out to be very complicated!

The crux of the problem is to figure out whether a > symbol at the end of a line stands for the greater-than sign, or for the end tag of a <type argument>. While this still sounds like a simple problem, the fact that TypeScript accomodates for most of the JavaScript syntax makes this harder than it seems.

Consider:

type a = b < c , d >
f()

a = b < c , d >
f() 

The former is a valid type declaration, given that b accepts two type arguments, followed by a function call.

The latter is a valid assignment, first computing b < c, throwing away the result, then computing d > f() and saving this result.


One of my early guesses was to search backwards for the first =.
From there, you can search backwards for either = or type.
If you find =, that was a greater-than sign.
If you find type, that was a type argument.

While this solves the problems mentioned, it does not work for the following examples:

const f = [] as Array<number>
f()

So it seems a regexp-based solution might be compromised! At the very least, we'd need to search backwards for the matching <, which involves counting the opening/closing < and > we cross. But even if we had that, we still need to figure out whether we are in a type declaration or not.

I'm still trying to build a decent approximation for the correct behavior, but it'll be hard to be sure it works.

In the meantime, here's a large set of examples to see if a given attempt is misbehaving:

var fun = () => {}

// Things are correctly indented if their HERE aligns with this one:
//-----------------------HERE

const a = 0 >
    1 //-----------------HERE
const b = 0 < 1 && 2 >
    3 //-----------------HERE
const c = 0 < 1 || 2 >
    3 //-----------------HERE
const d = 0 < 1 ? 2 : 3 + 4 >
    5 ? 6 : 7 //---------HERE
const d2 = 0 < 1 ? 2 : 3 * 4 >
    5 ? 6 : 7 //---------HERE
const d3 = 0 < 1 ? 2 : 3 / 4 >
    5 ? 6 : 7 //---------HERE
const d4 = 0 < 1 ? 2 : 3 % 4 >
    5 ? 6 : 7 //---------HERE
const e = [
    1 <
        2,
    3 >
        4, //------------HERE
]
const f = [] as Array<number>
fun() //-----------------HERE
const g = 1 as number >
    2 //-----------------HERE

type A = Array<number>
fun() //-----------------HERE
type B = Array<Array<number>>
fun() //-----------------HERE
type Two<A, B> = number
type C = Two <
    number , number[
    ]
>
fun() //-----------------HERE
type D = Array<number[]>
fun() //-----------------HERE

// Unfortunately, something like `foo < bar , baz > \n f(x)` can look syntactically like:
// - a type `foo` with two parameters, then `f(x)` on the next line
// - the sequencing of two comparisons, `foo < bar`, and `baz > f(x)`
var foo
var bar
var beep
type foo<A,B> = {}
type bar = {}

// So the two following examples are hard to distinguish!
beep = foo <
    bar , bar >
    fun() //-------------HERE

type beep = foo <
    bar , bar >
fun() //-----------------HERE

// And we cannot easily rely on the keyword `type` being there, because of those:
type baz = bar
beep = foo <
    bar , bar >
    fun() //-------------HERE

type bazz
    = bar
beep = foo <
    bar , bar >
    fun() //-------------HERE

type bazzz<A>
    = bar //-------------HERE
beep = foo <
    bar , bar >
    fun() //-------------HERE

A good implementation should let all those HERE where they are. The current implementation gives:

var fun = () => {}

// Things are correctly indented if their HERE aligns with this one:
//-----------------------HERE

const a = 0 >
    1 //-----------------HERE
const b = 0 < 1 && 2 >
    3 //-----------------HERE
const c = 0 < 1 || 2 >
    3 //-----------------HERE
const d = 0 < 1 ? 2 : 3 + 4 >
    5 ? 6 : 7 //---------HERE
const d2 = 0 < 1 ? 2 : 3 * 4 >
    5 ? 6 : 7 //---------HERE
const d3 = 0 < 1 ? 2 : 3 / 4 >
    5 ? 6 : 7 //---------HERE
const d4 = 0 < 1 ? 2 : 3 % 4 >
    5 ? 6 : 7 //---------HERE
const e = [
    1 <
        2,
    3 >
        4, //------------HERE
]
const f = [] as Array<number>
    fun() //-----------------HERE (WRONG)
const g = 1 as number >
    2 //-----------------HERE

type A = Array<number>
    fun() //-----------------HERE (WRONG)
type B = Array<Array<number>>
    fun() //-----------------HERE (WRONG)
type Two<A, B> = number
type C = Two <
    number , number[
    ]
    >
    fun() //-----------------HERE (WRONG)
type D = Array<number[]>
    fun() //-----------------HERE (WRONG)

// Unfortunately, something like `foo < bar , baz > \n f(x)` can look syntactically like:
// - a type `foo` with two parameters, then `f(x)` on the next line
// - the sequencing of two comparisons, `foo < bar`, and `baz > f(x)`
var foo
var bar
var beep
type foo<A,B> = {}
type bar = {}

// So the two following examples are hard to distinguish!
beep = foo <
    bar , bar >
    fun() //-------------HERE

type beep = foo <
    bar , bar >
    fun() //-----------------HERE (WRONG)

// And we cannot easily rely on the keyword `type` being there, because of those:
type baz = bar
beep = foo <
    bar , bar >
    fun() //-------------HERE

type bazz
    = bar
beep = foo <
    bar , bar >
    fun() //-------------HERE

type bazzz<A>
    = bar //-------------HERE
beep = foo <
    bar , bar >
    fun() //-------------HERE

from typescript.el.

lddubeau avatar lddubeau commented on July 17, 2024

Yes, the traditional way of doing indentation in Emacs strains under the complexity of TypeScript's syntax. The strategy is to look at the first non-blank character of the line and then, starting from that point, got back in the buffer to figure out what context we're in. For languages with simple syntax, that's fairly easy to do, and results in fairly simple code. By the time you get to languages of TS' complexity, it is much harder to pull off, and results in complicated Elisp logic.

For some languages, the complexity may be such that switching to a system that performs indentation on the basis of an abstract syntax tree (AST) may be the best way, or perhaps the only way, to get valid and consistent results in all cases. If you ever use tide-format, that's essentially how it works behind the scenes: tide sends the buffer to tsserver, and tsserver produces an AST which it uses to indent the file, and sends the result back to tide. (I've omitted from this description details not important to this discussion.)

Indenting code that does not use semicolons to delimit statements may be the proverbial "straw that breaks the camel's back" and requires an AST to pull off correctly.

from typescript.el.

josteink avatar josteink commented on July 17, 2024

Indenting code that does not use semicolons to delimit statements may be the proverbial "straw that breaks the camel's back" and requires an AST to pull off correctly.

Agreed.

But as we all know, that would be a massive undertaking. For me personally, to justify such an effort I would have to be able to get some pretty massive benefits in returns for that effort.

And as someone using "traditional" TypeScript-syntax with semicolon at the end of my lines, this is definitely not going to be the one who pushes me into implementing this.

If someone else feels like doing it, I'll be happy to review though.

from typescript.el.

Ptival avatar Ptival commented on July 17, 2024

I think I came up with a somewhat good heuristic that preserves all good existing behaviors, fixes many of my issues, and only possibly breaks on really weird inputs. I will try and post a PR later today, with an explanation of the heuristic, and a lot of examples of what works and what does not.

Requiring AST, or even worse, type information, for the indentation would indeed be annoying to write, and to maintain! At that point, one would probably just want some language-server type and use tsc itself. I also don't have a personal justification for putting this much effort into solving my small annoyance! :-)

from typescript.el.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.