Skip to content

Commit cad5151

Browse files
committed
fix positioning of comment blocks
now no more double backtick or leading single line # is necessary - everything should be positioned where it belongs in output JS. This doesn't apply to actual CS compiler output of course, but for internal JSDoc intellisense it works well. Solved internally by inserting the # lines where necessary. This required new logic for remembering inserted lines and then adjusting sourcemaps and fake_line afterwards. fixes #1
1 parent 54a60a9 commit cad5151

File tree

5 files changed

+116
-35
lines changed

5 files changed

+116
-35
lines changed

server/src/services/transpileService.ts

+51-10
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ const transpilation_cache: Map<string,ITranspilationResult> = new VolatileMap(18
7878
/** The resulting coffee must still always be valid and parsable by the compiler,
7979
and should not shift characters around much (otherwise source maps would need changes too) */
8080
function preprocess_coffee(coffee_doc: TextDocument) {
81-
const tmp = coffee_doc.getText()
81+
const tmp = (coffee_doc.getText() as string)
8282
// Enable autocomplete at `@|`. Replace with magic snippet that allows for both @|
8383
// and standalone @ sign. Cursor needs to be adjusted properly in doComplete().
8484
// .____CoffeeSenseAtSign is replaced with (this.valueOf(),this) in postprocess_js.
@@ -115,7 +115,32 @@ function preprocess_coffee(coffee_doc: TextDocument) {
115115
logger.logDebug(`replace trailing comma inside { } with ↯ ${coffee_doc.uri}`)
116116
return `,↯${ws}}`
117117
})
118+
118119
const tmp_lines = tmp.split('\n')
120+
121+
const starts_with_block_comment_lines = tmp_lines.map((line) =>
122+
line.match(/^\s*###([^#]|$)/mg)
123+
).map((match, line_i) =>
124+
match ? line_i : -1
125+
).filter(line_i =>
126+
line_i > -1
127+
).map((line_i, i) =>
128+
// Eg. if block comment lines were detected and prefixed with a single # line
129+
// at i=2 and i=4, the array will contain [2,5] so we can iterate and modify
130+
// arrays from the beginning here and in postprocess_js.
131+
line_i + i)
132+
133+
for(const line_i of starts_with_block_comment_lines) {
134+
// Arrange for block comments to be placed directly before their below line in JS (#1)
135+
// Inserts extra lines that need to be tracked so the source maps can be adjusted. That's
136+
// also why this needs to happen before object_tweak_coffee_lines.
137+
// Couldn't find a solution that does not insert extra lines:
138+
// - prefix all ### with another "# " -> long block comment sections become code
139+
// - prefix with backticks: ### -> ``### -> fails inside objects or multiline assignments
140+
logger.logDebug(`replace: prefix ### with single # line ${coffee_doc.uri}`)
141+
tmp_lines.splice(line_i, 0, '#')
142+
}
143+
119144
const object_tweak_coffee_lines: number[] = []
120145
tmp_lines.forEach((line, line_i) => {
121146
// Enable autocomplete on empty lines inside object properties.
@@ -137,12 +162,14 @@ function preprocess_coffee(coffee_doc: TextDocument) {
137162
if(next_textual_line_indentation > empty_line_indentation.length)
138163
return
139164
}
165+
logger.logDebug(`replace append empty line with 𒐛:𒐛 ${coffee_doc.uri}`)
140166
tmp_lines[line_i] = empty_line_indentation + '𒐛:𒐛'
141167
object_tweak_coffee_lines.push(line_i)
142168
}
143169
})
144170
const coffee = tmp_lines.join('\n')
145-
return { coffee, object_tweak_coffee_lines }
171+
const inserted_coffee_lines = starts_with_block_comment_lines
172+
return { coffee, inserted_coffee_lines, object_tweak_coffee_lines }
146173
}
147174

148175
/** further transforms that *can break* cs compilation, to be used if compilation could not succeed without it anyway */
@@ -263,7 +290,7 @@ const try_translate_coffee = (coffee_doc: TextDocument): ITranspilationResult =>
263290
} else {
264291
fake_line_mechanism = 'coffee_in_js'
265292
normal_compilation_diagnostics = result.diagnostics
266-
293+
267294
coffee_error_line_no = result.diagnostics![0]!.range.start.line
268295
coffee_error_offset = coffee_doc.offsetAt(Position.create(coffee_error_line_no, 0))
269296
const coffee_error_next_newline_position = coffee.slice(coffee_error_offset).indexOf('\n')
@@ -410,7 +437,7 @@ const try_translate_coffee = (coffee_doc: TextDocument): ITranspilationResult =>
410437
* Applies some transformations to the JS in result and updates source_map accordingly.
411438
* These transforms do not depend on any previous information.
412439
*/
413-
function postprocess_js(result: ITranspilationResult, object_tweak_coffee_lines: number[]) {
440+
function postprocess_js(result: ITranspilationResult, object_tweak_coffee_lines: number[], inserted_coffee_lines: number[]) {
414441
if(!result.js || !result.source_map)
415442
return
416443

@@ -442,9 +469,23 @@ function postprocess_js(result: ITranspilationResult, object_tweak_coffee_lines:
442469
source_map_entry.column = 0
443470
}
444471
}
472+
473+
let skip_lines_count = inserted_coffee_lines.findIndex(inserted_line_i =>
474+
source_map_entry.sourceLine < inserted_line_i)
475+
if(skip_lines_count < 0)
476+
skip_lines_count = inserted_coffee_lines.length
477+
source_map_entry.sourceLine -= skip_lines_count
445478
})
446479
})
447480

481+
if(result.fake_line) {
482+
let fake_line_skip_lines_count = inserted_coffee_lines.findIndex(inserted_line_i =>
483+
result.fake_line! < inserted_line_i)
484+
if(fake_line_skip_lines_count < 0)
485+
fake_line_skip_lines_count = inserted_coffee_lines.length
486+
result.fake_line -= fake_line_skip_lines_count
487+
}
488+
448489
// console.time('var-decl-fix')
449490
//////////////////////////////////////
450491
///////// Modify variable declarations to solve various TS compiler errors:
@@ -461,7 +502,7 @@ function postprocess_js(result: ITranspilationResult, object_tweak_coffee_lines:
461502
// var a;
462503
// a = 1;
463504
// a = 'one';
464-
/////// and now `a` is of type `number | string` (https://github.com/microsoft/TypeScript/issues/45369).
505+
/////// and now `a` is of type `number | string` (https://github.com/microsoft/TypeScript/issues/45369).
465506
// Below is a hacky workaround that should fix these issues in most cases. It moves the
466507
// declaration part (`var`) down to the variable's first implementation position.
467508
// This works only with easy implementations and single-variable array destructuring:
@@ -592,7 +633,7 @@ const transpile_service: ITranspileService = {
592633
return cached
593634
}
594635

595-
const { coffee: preprocessed_coffee, object_tweak_coffee_lines } = preprocess_coffee(orig_coffee_doc)
636+
const { coffee: preprocessed_coffee, object_tweak_coffee_lines, inserted_coffee_lines } = preprocess_coffee(orig_coffee_doc)
596637
// As coffee was modified, offsets and positions are changed and for these purposes,
597638
// we need to construct a new doc
598639
let mod_coffee_doc = TextDocument.create(orig_coffee_doc.uri, 'coffeescript', 1, preprocessed_coffee)
@@ -605,7 +646,7 @@ const transpile_service: ITranspileService = {
605646
}
606647

607648
if(result.js && result.source_map) {
608-
postprocess_js(result, object_tweak_coffee_lines)
649+
postprocess_js(result, object_tweak_coffee_lines, inserted_coffee_lines)
609650
} else {
610651
// Nothing worked at all. As a last resort, just pass the coffee to tsserver,
611652
// with minimal transforms:
@@ -657,7 +698,7 @@ const transpile_service: ITranspileService = {
657698
line_i++
658699
}
659700
}
660-
701+
661702
if(mapped)
662703
coffee_pos = Position.create(mapped.sourceLine, mapped.sourceColumn)
663704
else
@@ -714,7 +755,7 @@ const transpile_service: ITranspileService = {
714755
}
715756
}
716757
}
717-
758+
718759
// TODO: revise this function, maybe this should be always all line matches by default instead
719760
const get_fitting_js_matches = () => {
720761
const js_matches_by_line = result.source_map!
@@ -805,7 +846,7 @@ const transpile_service: ITranspileService = {
805846
return abcdefg(js_matches)
806847
}
807848
const js_match = choose_js_match()
808-
849+
809850
let line = js_match?.line
810851
let column = js_match?.column
811852
if(js_match && line != null && result.fake_line == coffee_position.line && result.fake_line_mechanism === 'coffee_in_js') {

test/diagnosticHelper.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@ import { sleep } from './util';
55
import { showFile } from './editorHelper';
66
import { performance } from 'perf_hooks';
77

8-
export async function testDiagnostics(docUri: vscode.Uri, expectedDiagnostics: vscode.Diagnostic[]) {
8+
export async function testDiagnostics(docUri: vscode.Uri, expectedDiagnostics: vscode.Diagnostic[], allow_unspecified = false) {
99
await showFile(docUri);
1010

1111
const result = await getDiagnosticsAndTimeout(docUri);
1212

13-
assert.equal(expectedDiagnostics.length, result.length);
13+
if(!allow_unspecified)
14+
assert.equal(expectedDiagnostics.length, result.length);
1415

1516
expectedDiagnostics.forEach(ed => {
1617
assert.ok(

test/lsp/features/diagnostics/basic.test.ts

+22-21
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { testDiagnostics } from '../../../diagnosticHelper'
33
import { sameLineRange } from '../../../util'
44
import { getDocUri } from '../../path'
55

6+
const string_to_number_error = "Type 'string' is not assignable to type 'number'."
7+
68
describe('Should find diagnostics', () => {
79

810
it('shows diagnostic errors for coffeescript compilation errors', async () => {
@@ -23,7 +25,7 @@ describe('Should find diagnostics', () => {
2325
range: sameLineRange(2, 0, 14),
2426
severity: vscode.DiagnosticSeverity.Error,
2527
// This also tests basic "pushes down variable declaration to assignment" logic, as the type is fixed number, not number|string as it would be with classical cs compiler (declaration at file head)
26-
message: "Type 'string' is not assignable to type 'number'."
28+
message: string_to_number_error
2729
},
2830
{
2931
range: sameLineRange(6, 14, 17),
@@ -54,32 +56,31 @@ describe('Should find diagnostics', () => {
5456
{
5557
range: sameLineRange(7, 0, 20),
5658
severity: vscode.DiagnosticSeverity.Error,
57-
message: "Type 'string' is not assignable to type 'number'."
59+
message: string_to_number_error
5860
}
5961
])
6062
})
6163

62-
// TODO: go back to new CS branch, https://github.com/jashkenas/coffeescript/issues/5366#issuecomment-1021366654
64+
// TODO: go back to new CS branch, https://github.com/jashkenas/coffeescript/issues/5366#issuecomment-1021366654 < outdated?
6365
it('pushes down variable declaration to assignment even with comment block before it', async () => {
6466
const docUri = getDocUri('diagnostics/declaration-with-commentblock.coffee')
6567
await testDiagnostics(docUri, [
66-
{
67-
range: sameLineRange(3, 0, 15),
68-
severity: vscode.DiagnosticSeverity.Error,
69-
message: "Type 'string' is not assignable to type 'number'."
70-
}
71-
])
72-
})
73-
74-
// TODO: issue #1, need another `#` before comment blocks
75-
xit('positions multiple comment blocks before each var assignment, not declaration', async () => {
76-
const docUri = getDocUri('diagnostics/declaration-with-commentblock.coffee')
77-
await testDiagnostics(docUri, [
78-
{
79-
range: sameLineRange(1, 0, 28),
80-
severity: vscode.DiagnosticSeverity.Error,
81-
message: "Type 'string' is not assignable to type 'number'."
82-
}
83-
])
68+
[3, 0, 15],
69+
[6, 0, 28],
70+
[11, 1, 31],
71+
[12, 1, 2],
72+
[17, 2, 5],
73+
[19, 1, 31],
74+
[23, 1, 31],
75+
[28, 1, 31],
76+
[32, 1, 31],
77+
[34, 1, 31],
78+
[41, 1, 31],
79+
[44, 0, 30]
80+
].map(x => ({
81+
range: sameLineRange(x[0], x[1], x[2]),
82+
severity: vscode.DiagnosticSeverity.Error,
83+
message: string_to_number_error
84+
})), true)
8485
})
8586
})

test/lsp/fixture/definition/basic.coffee

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ comprehension_6 = v * 2 for v in comprehension_5
1919
item_1
2020
item_2
2121

22-
``###*
22+
###*
2323
# @typedef {any} DefJSDoc1
2424
###
2525
###* @typedef {any} DefJSDoc2 ###

test/lsp/fixture/diagnostics/declaration-with-commentblock.coffee

+39-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,42 @@ diagnostics_ts2 = 1
44
diagnostics_ts2 = '1'
55

66
###* @type {number} ###
7-
diagnostics_should_be_number = '123'
7+
diagnostics_should_be_number = '123'
8+
9+
10+
do =>
11+
###* @type {number} ###
12+
diagnostics_should_be_number_x = '1'
13+
a =
14+
###* @type {number} ###
15+
diagnostics_should_be_number_b = '1'
16+
c =
17+
###* @type {number} ###
18+
d: '1'
19+
###* @type {number} ###
20+
diagnostics_should_be_number_y = '1'
21+
###*
22+
# @type {number}
23+
###
24+
diagnostics_should_be_number_g = '1'
25+
#
26+
###*
27+
# @type {number}
28+
###
29+
diagnostics_should_be_number_i = '1'
30+
``###*
31+
# @type {number}
32+
###
33+
diagnostics_should_be_number_j = '1'
34+
``###* @type {number} ###
35+
diagnostics_should_be_number_k = '1'
36+
###
37+
some.comment
38+
###
39+
###*
40+
@type {number}
41+
###
42+
diagnostics_should_be_number_h = '1'
43+
1
44+
###* @type {number} ###
45+
diagnostics_should_be_number_e = '2'

0 commit comments

Comments
 (0)