davidbonnet / astring

🌳 Tiny and fast JavaScript code generator from an ESTree-compliant AST.
https://david.bonnet.cc/astring/demo/
MIT License
1.16k stars 55 forks source link

Line counter should track all line ends, not just at end of segment #703

Open martypdx opened 5 months ago

martypdx commented 5 months ago

Motivation

Want to use lineEnd anywhere code chunk with source maps:

// normally calculated from state.indent, lineEnd, etc.
state.write(`\n   `);

Expected behavior

state.line increments for each occurance of lineEnd in code

Actual behavior

state.line only increments once when newLine is at end of code chunk

martypdx commented 5 months ago

Here's a possible algorithm:

function testTrack(code, lineEnd) {
    const state = { line: 1, column: 0 };
    // these lines replace those in writeAndMap
    if(code.length > 0) {
        const segments = code.split(lineEnd);
        state.line += (segments.length - 1) * lineEnd.length;
        state.column += segments.at(-1).length;
    }
    return state;
}

test('track new line', ({ expect }) => {
    expect(testTrack(`    `, `\n`)).toEqual({ column: 4, line: 1, });
    expect(testTrack(`\n    `, `\n`)).toEqual({ column: 4, line: 2, });
    expect(testTrack(`    \n`, `\n`)).toEqual({ column: 0, line: 2, });
    expect(testTrack(`    \n    `, `\n`)).toEqual({ column: 4, line: 2, });
    expect(testTrack(`\n\n`, `\n`)).toEqual({ column: 0, line: 3, });
    expect(testTrack(`\n\n    `, `\n`)).toEqual({ column: 4, line: 3, });
    expect(testTrack(`    \n\n    `, `\n`)).toEqual({ column: 4, line: 3, });
    expect(testTrack(`    \n\n`, `\n`)).toEqual({ column: 0, line: 3, });
    expect(testTrack(`\n    \n`, `\n`)).toEqual({ column: 0, line: 3, });
});

test('track \r\n', ({ expect }) => {
    expect(testTrack(`    `, `\r\n`)).toEqual({ column: 4, line: 1, });
    expect(testTrack(`\r\n    `, `\r\n`)).toEqual({ column: 4, line: 3, });
    expect(testTrack(`    \r\n`, `\r\n`)).toEqual({ column: 0, line: 3, });
    expect(testTrack(`    \r\n    `, `\r\n`)).toEqual({ column: 4, line: 3, });
    expect(testTrack(`\r\n\r\n`, `\r\n`)).toEqual({ column: 0, line: 5, });
    expect(testTrack(`\r\n\r\n    `, `\r\n`)).toEqual({ column: 4, line: 5, });
    expect(testTrack(`    \r\n\r\n    `, `\r\n`)).toEqual({ column: 4, line: 5, });
    expect(testTrack(`    \r\n\r\n`, `\r\n`)).toEqual({ column: 0, line: 5, });
    expect(testTrack(`\r\n    \r\n`, `\r\n`)).toEqual({ column: 0, line: 5, });
});
martypdx commented 5 months ago

Ugh, but it doesn't count in a string literal, right? Like let s = '\n';

davidbonnet commented 1 month ago

How about doing this instead?

state.write('\n');
state.write('   ');
martypdx commented 1 month ago

Yep, that's I ended up doing:

Process (write to state) line endings independently before the new line indentation.

To me, this limitation is fine, so feel free to close. Probably good to add a note in the docs.

davidbonnet commented 1 week ago

Makes sense, it should indeed be clarified in the documentation. Note that alternate approaches would considerably slow down the code generation.