Skip to content

Commit 989d37d

Browse files
feat(unstable): support comments in lint plugin
1 parent 61574bb commit 989d37d

File tree

11 files changed

+345
-7
lines changed

11 files changed

+345
-7
lines changed

cli/js/40_lint.js

+116
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,79 @@ export class SourceCode {
263263
return ancestors;
264264
}
265265

266+
/**
267+
* @returns {Array<Deno.lint.LineComment | Deno.lint.BlockComment>}
268+
*/
269+
getAllComments() {
270+
materializeComments(this.#ctx);
271+
return this.#ctx.comments;
272+
}
273+
274+
/**
275+
* @param {Deno.lint.Node} node
276+
* @returns {Array<Deno.lint.LineComment | Deno.lint.BlockComment>}
277+
*/
278+
getCommentsBefore(node) {
279+
materializeComments(this.#ctx);
280+
281+
/** @type {Array<Deno.lint.LineComment | Deno.lint.BlockComment>} */
282+
const before = [];
283+
284+
const { comments } = this.#ctx;
285+
for (let i = 0; i < comments.length; i++) {
286+
const comment = comments[i];
287+
if (comment.range[0] <= node.range[0]) {
288+
before.push(comment);
289+
}
290+
}
291+
292+
return before;
293+
}
294+
295+
/**
296+
* @param {Deno.lint.Node} node
297+
* @returns {Array<Deno.lint.LineComment | Deno.lint.BlockComment>}
298+
*/
299+
getCommentsAfter(node) {
300+
materializeComments(this.#ctx);
301+
302+
/** @type {Array<Deno.lint.LineComment | Deno.lint.BlockComment>} */
303+
const after = [];
304+
305+
const { comments } = this.#ctx;
306+
for (let i = 0; i < comments.length; i++) {
307+
const comment = comments[i];
308+
if (comment.range[0] >= node.range[1]) {
309+
after.push(comment);
310+
}
311+
}
312+
313+
return after;
314+
}
315+
316+
/**
317+
* @param {Deno.lint.Node} node
318+
* @returns {Array<Deno.lint.LineComment | Deno.lint.BlockComment>}
319+
*/
320+
getCommentsInside(node) {
321+
materializeComments(this.#ctx);
322+
323+
/** @type {Array<Deno.lint.LineComment | Deno.lint.BlockComment>} */
324+
const inside = [];
325+
326+
const { comments } = this.#ctx;
327+
for (let i = 0; i < comments.length; i++) {
328+
const comment = comments[i];
329+
if (
330+
comment.range[0] >= node.range[0] && comment.range[1] <= node.range[1]
331+
) {
332+
inside.push(comment);
333+
}
334+
}
335+
336+
return inside;
337+
}
338+
266339
/**
267340
* @returns {string}
268341
*/
@@ -345,6 +418,35 @@ export class Context {
345418
}
346419
}
347420

421+
/**
422+
* TODO(@marvinhagemeister): Is it worth it to do this lazily?
423+
* @param {AstContext} ctx
424+
*/
425+
function materializeComments(ctx) {
426+
const { buf, commentsOffset, comments, strTable } = ctx;
427+
428+
let offset = commentsOffset;
429+
const count = readU32(buf, offset);
430+
offset += 4;
431+
432+
if (comments.length === count) return;
433+
434+
while (offset < buf.length && comments.length < count) {
435+
const kind = buf[offset];
436+
offset++;
437+
const spanId = readU32(buf, offset);
438+
offset += 4;
439+
const strId = readU32(buf, offset);
440+
offset += 4;
441+
442+
comments.push({
443+
type: kind === 0 ? "Line" : "Block",
444+
range: readSpan(ctx, spanId),
445+
value: getString(strTable, strId),
446+
});
447+
}
448+
}
449+
348450
/**
349451
* @param {Deno.lint.Plugin[]} plugins
350452
* @param {string[]} exclude
@@ -489,6 +591,7 @@ class FacadeNode {
489591

490592
/** @type {Set<number>} */
491593
const appliedGetters = new Set();
594+
let hasCommenstGetter = false;
492595

493596
/**
494597
* Add getters for all potential properties found in the message.
@@ -515,6 +618,16 @@ function setNodeGetters(ctx) {
515618
},
516619
});
517620
}
621+
622+
if (!hasCommenstGetter) {
623+
hasCommenstGetter = true;
624+
Object.defineProperty(FacadeNode.prototype, "comments", {
625+
get() {
626+
materializeComments(ctx);
627+
return ctx.comments;
628+
},
629+
});
630+
}
518631
}
519632

520633
/**
@@ -994,6 +1107,7 @@ function createAstContext(buf, token) {
9941107

9951108
// The buffer has a few offsets at the end which allows us to easily
9961109
// jump to the relevant sections of the message.
1110+
const commentsOffset = readU32(buf, buf.length - 28);
9971111
const propsOffset = readU32(buf, buf.length - 24);
9981112
const spansOffset = readU32(buf, buf.length - 20);
9991113
const typeMapOffset = readU32(buf, buf.length - 16);
@@ -1060,7 +1174,9 @@ function createAstContext(buf, token) {
10601174
rootOffset,
10611175
spansOffset,
10621176
propsOffset,
1177+
commentsOffset,
10631178
nodes: new Map(),
1179+
comments: [],
10641180
strTableOffset,
10651181
strByProp,
10661182
strByType,

cli/js/40_lint_types.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ export interface AstContext {
88
nodes: Map<number, Deno.lint.Node>;
99
spansOffset: number;
1010
propsOffset: number;
11+
commentsOffset: number;
12+
comments: Array<Deno.lint.LineComment | Deno.lint.BlockComment>;
1113
strByType: number[];
1214
strByProp: number[];
1315
typeByStr: Map<string, number>;

cli/tools/lint/ast_buffer/buffer.rs

+56-6
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,19 @@ struct Node {
142142
parent: u32,
143143
}
144144

145+
#[derive(Debug)]
146+
pub enum CommentKind {
147+
Line,
148+
Block,
149+
}
150+
151+
#[derive(Debug)]
152+
struct Comment {
153+
kind: CommentKind,
154+
str_id: usize,
155+
span_id: usize,
156+
}
157+
145158
#[derive(Debug)]
146159
pub struct SerializeCtx {
147160
root_idx: Index,
@@ -161,6 +174,9 @@ pub struct SerializeCtx {
161174
kind_name_map: Vec<usize>,
162175
/// Maps prop id to string id
163176
prop_name_map: Vec<usize>,
177+
178+
/// Comments
179+
comments: Vec<Comment>,
164180
}
165181

166182
/// This is the internal context used to allocate and fill the buffer. The point
@@ -185,6 +201,7 @@ impl SerializeCtx {
185201
str_table: StringTable::new(),
186202
kind_name_map: vec![0; kind_size],
187203
prop_name_map: vec![0; prop_size],
204+
comments: vec![],
188205
};
189206

190207
let empty_str = ctx.str_table.insert("");
@@ -285,12 +302,7 @@ impl SerializeCtx {
285302
where
286303
K: Into<u8> + Display + Clone,
287304
{
288-
let (start, end) = if *span == DUMMY_SP {
289-
(0, 0)
290-
} else {
291-
// -1 is because swc stores spans 1-indexed
292-
(span.lo.0 - 1, span.hi.0 - 1)
293-
};
305+
let (start, end) = span_to_value(span);
294306
self.append_inner(kind, start, end)
295307
}
296308

@@ -559,6 +571,21 @@ impl SerializeCtx {
559571
self.write_ref_vec(prop, parent_ref, actual)
560572
}
561573

574+
pub fn write_comment(&mut self, kind: CommentKind, value: &str, span: &Span) {
575+
let str_id = self.str_table.insert(value);
576+
577+
let span_id = self.spans.len() / 2;
578+
let (span_lo, span_hi) = span_to_value(span);
579+
self.spans.push(span_lo);
580+
self.spans.push(span_hi);
581+
582+
self.comments.push(Comment {
583+
kind,
584+
str_id,
585+
span_id,
586+
});
587+
}
588+
562589
/// Serialize all information we have into a buffer that can be sent to JS.
563590
/// It has the following structure:
564591
///
@@ -629,10 +656,24 @@ impl SerializeCtx {
629656
let offset_props = buf.len();
630657
buf.append(&mut self.field_buf);
631658

659+
// Serialize comments
660+
let offset_comments = buf.len();
661+
append_usize(&mut buf, self.comments.len());
662+
for comment in &self.comments {
663+
let kind = match comment.kind {
664+
CommentKind::Line => 0,
665+
CommentKind::Block => 1,
666+
};
667+
buf.push(kind);
668+
append_usize(&mut buf, comment.span_id);
669+
append_usize(&mut buf, comment.str_id);
670+
}
671+
632672
// Putting offsets of relevant parts of the buffer at the end. This
633673
// allows us to hop to the relevant part by merely looking at the last
634674
// for values in the message. Each value represents an offset into the
635675
// buffer.
676+
append_usize(&mut buf, offset_comments);
636677
append_usize(&mut buf, offset_props);
637678
append_usize(&mut buf, offset_spans);
638679
append_usize(&mut buf, offset_kind_map);
@@ -643,3 +684,12 @@ impl SerializeCtx {
643684
buf
644685
}
645686
}
687+
688+
fn span_to_value(span: &Span) -> (u32, u32) {
689+
if *span == DUMMY_SP {
690+
(0, 0)
691+
} else {
692+
// -1 is because swc stores spans 1-indexed
693+
(span.lo.0 - 1, span.hi.0 - 1)
694+
}
695+
}

cli/tools/lint/ast_buffer/swc.rs

+9
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ use deno_ast::view::VarDeclKind;
9090
use deno_ast::ParsedSource;
9191

9292
use super::buffer::AstBufSerializer;
93+
use super::buffer::CommentKind;
9394
use super::buffer::NodeRef;
9495
use super::ts_estree::AstNode;
9596
use super::ts_estree::MethodKind as TsEstreeMethodKind;
@@ -134,6 +135,14 @@ pub fn serialize_swc_to_buffer(
134135
}
135136
}
136137

138+
for comment in parsed_source.comments().get_vec() {
139+
let kind = match comment.kind {
140+
deno_ast::swc::common::comments::CommentKind::Line => CommentKind::Line,
141+
deno_ast::swc::common::comments::CommentKind::Block => CommentKind::Block,
142+
};
143+
ctx.write_comment(kind, &comment.text, &comment.span);
144+
}
145+
137146
ctx.map_utf8_spans_to_utf16(utf16_map);
138147
ctx.serialize()
139148
}

cli/tools/lint/ast_buffer/ts_estree.rs

+5
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use deno_ast::swc::common::Span;
88
use deno_ast::view::TruePlusMinus;
99

1010
use super::buffer::AstBufSerializer;
11+
use super::buffer::CommentKind;
1112
use super::buffer::NodeRef;
1213
use super::buffer::SerializeCtx;
1314
use crate::util::text_encoding::Utf16Map;
@@ -2890,6 +2891,10 @@ impl TsEsTreeBuilder {
28902891
_ => self.ctx.write_undefined(prop),
28912892
}
28922893
}
2894+
2895+
pub fn write_comment(&mut self, kind: CommentKind, value: &str, span: &Span) {
2896+
self.ctx.write_comment(kind, value, span);
2897+
}
28932898
}
28942899

28952900
#[derive(Debug)]

cli/tsc/dts/lib.deno.unstable.d.ts

+39-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
/// <reference lib="esnext" />
77
/// <reference lib="es2022.intl" />
88

9+
import { Block } from "./typescript.d.ts";
10+
911
declare namespace Deno {
1012
export {}; // stop default export type behavior
1113

@@ -1406,6 +1408,27 @@ declare namespace Deno {
14061408
* current node.
14071409
*/
14081410
getAncestors(node: Node): Node[];
1411+
1412+
/**
1413+
* Get all comments inside the source.
1414+
*/
1415+
getAllComments(): Array<LineComment | BlockComment>;
1416+
1417+
/**
1418+
* Get leading comments before a node.
1419+
*/
1420+
getCommentsBefore(node: Node): Array<LineComment | BlockComment>;
1421+
1422+
/**
1423+
* Get trailing comments after a node.
1424+
*/
1425+
getCommentsAfter(node: Node): Array<LineComment | BlockComment>;
1426+
1427+
/**
1428+
* Get comments inside a node.
1429+
*/
1430+
getCommentsInside(node: Node): Array<LineComment | BlockComment>;
1431+
14091432
/**
14101433
* Get the full source code.
14111434
*/
@@ -1532,6 +1555,7 @@ declare namespace Deno {
15321555
range: Range;
15331556
sourceType: "module" | "script";
15341557
body: Statement[];
1558+
comments: Array<LineComment | BlockComment>;
15351559
}
15361560

15371561
/**
@@ -4335,6 +4359,18 @@ declare namespace Deno {
43354359
| TSUnknownKeyword
43364360
| TSVoidKeyword;
43374361

4362+
export interface LineComment {
4363+
type: "Line";
4364+
range: Range;
4365+
value: string;
4366+
}
4367+
4368+
export interface BlockComment {
4369+
type: "Block";
4370+
range: Range;
4371+
value: string;
4372+
}
4373+
43384374
/**
43394375
* Union type of all possible AST nodes
43404376
* @category Linter
@@ -4394,7 +4430,9 @@ declare namespace Deno {
43944430
| TSIndexSignature
43954431
| TSTypeAnnotation
43964432
| TSTypeParameterDeclaration
4397-
| TSTypeParameter;
4433+
| TSTypeParameter
4434+
| LineComment
4435+
| BlockComment;
43984436

43994437
export {}; // only export exports
44004438
}

0 commit comments

Comments
 (0)