Skip to content

Commit de3e150

Browse files
nzakaslumirlumir
andauthored
feat: Add token methods to JSONSourceCode (#112)
* feat: Add token methods to JSONSourceCode fixes #83 * Remove package-lock.json * Fix test comment * Update src/languages/json-source-code.js Co-authored-by: 루밀LuMir <rpfos@naver.com> * Update src/languages/json-source-code.js Co-authored-by: 루밀LuMir <rpfos@naver.com> * Update src/languages/json-source-code.js Co-authored-by: 루밀LuMir <rpfos@naver.com> * Update tests/languages/json-source-code.test.js Co-authored-by: 루밀LuMir <rpfos@naver.com> * Remove getTokenOrCommentBefore/After * Update tests/languages/json-source-code.test.js Co-authored-by: 루밀LuMir <rpfos@naver.com> * Fix formatting error * Move comment processing into constructor * Fix getTokenAfter() to work with nodes of multiple tokens --------- Co-authored-by: 루밀LuMir <rpfos@naver.com>
1 parent 0daac1d commit de3e150

4 files changed

Lines changed: 470 additions & 5 deletions

File tree

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,6 @@ dist
6060
*.code-workspace
6161
.idea
6262
.cursor
63+
64+
# Lock files
65+
package-lock.json

docs/rules/no-unnormalized-keys.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ The following options are available on this rule:
6060
- `form: "NFC" | "NFD" | "NFKC" | "NFKD"` - specifies which Unicode normalization form to use when checking keys. Must be one of: `"NFC"` (default), `"NFD"`, `"NFKC"`, or `"NFKD"`.
6161

6262
Each normalization form has specific characteristics:
63-
6463
- **NFC**: Canonical Decomposition followed by Canonical Composition (default)
6564
- **NFD**: Canonical Decomposition
6665
- **NFKC**: Compatibility Decomposition followed by Canonical Composition

src/languages/json-source-code.js

Lines changed: 128 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,37 @@ class JSONTraversalStep extends VisitNodeStep {
5959
}
6060
}
6161

62+
/**
63+
* Processes tokens to extract comments and their starting tokens.
64+
* @param {Array<Token>} tokens The tokens to process.
65+
* @returns {{ comments: Array<Token>, starts: Map<number, number>, ends: Map<number, number>}}
66+
* An object containing an array of comments, a map of starting token range to token index, and
67+
* a map of ending token range to token index.
68+
*/
69+
function processTokens(tokens) {
70+
/** @type {Array<Token>} */
71+
const comments = [];
72+
73+
/** @type {Map<number, number>} */
74+
const starts = new Map();
75+
76+
/** @type {Map<number, number>} */
77+
const ends = new Map();
78+
79+
for (let i = 0; i < tokens.length; i++) {
80+
const token = tokens[i];
81+
82+
if (token.type.endsWith("Comment")) {
83+
comments.push(token);
84+
}
85+
86+
starts.set(token.range[0], i);
87+
ends.set(token.range[1], i);
88+
}
89+
90+
return { comments, starts, ends };
91+
}
92+
6293
//-----------------------------------------------------------------------------
6394
// Exports
6495
//-----------------------------------------------------------------------------
@@ -93,11 +124,23 @@ export class JSONSourceCode extends TextSourceCodeBase {
93124
ast = undefined;
94125

95126
/**
96-
* The comment node in the source code.
127+
* The comment tokens in the source code.
97128
* @type {Array<Token>|undefined}
98129
*/
99130
comments;
100131

132+
/**
133+
* A map of token start positions to their corresponding index.
134+
* @type {Map<number, number>}
135+
*/
136+
#tokenStarts;
137+
138+
/**
139+
* A map of token end positions to their corresponding index.
140+
* @type {Map<number, number>}
141+
*/
142+
#tokenEnds;
143+
101144
/**
102145
* Creates a new instance.
103146
* @param {Object} options The options for the instance.
@@ -107,9 +150,11 @@ export class JSONSourceCode extends TextSourceCodeBase {
107150
constructor({ text, ast }) {
108151
super({ text, ast });
109152
this.ast = ast;
110-
this.comments = ast.tokens
111-
? ast.tokens.filter(token => token.type.endsWith("Comment"))
112-
: [];
153+
154+
const { comments, starts, ends } = processTokens(this.ast.tokens ?? []);
155+
this.comments = comments;
156+
this.#tokenStarts = starts;
157+
this.#tokenEnds = ends;
113158
}
114159

115160
/**
@@ -283,4 +328,83 @@ export class JSONSourceCode extends TextSourceCodeBase {
283328

284329
return steps;
285330
}
331+
332+
/**
333+
* Gets the token before the given node or token, optionally including comments.
334+
* @param {AnyNode|Token} nodeOrToken The node or token to get the previous token for.
335+
* @param {Object} [options] Options object.
336+
* @param {boolean} [options.includeComments] If true, return comments when they are present.
337+
* @returns {Token|null} The previous token or comment, or null if there is none.
338+
*/
339+
getTokenBefore(nodeOrToken, { includeComments = false } = {}) {
340+
const index = this.#tokenStarts.get(nodeOrToken.range[0]);
341+
342+
if (index === undefined) {
343+
return null;
344+
}
345+
346+
let previousIndex = index - 1;
347+
if (previousIndex < 0) {
348+
return null;
349+
}
350+
351+
const tokens = this.ast.tokens;
352+
let tokenOrComment = tokens[previousIndex];
353+
354+
if (includeComments) {
355+
return tokenOrComment;
356+
}
357+
358+
// skip comments
359+
while (tokenOrComment?.type.endsWith("Comment")) {
360+
previousIndex--;
361+
362+
if (previousIndex < 0) {
363+
return null;
364+
}
365+
366+
tokenOrComment = tokens[previousIndex];
367+
}
368+
return tokenOrComment;
369+
}
370+
371+
/**
372+
* Gets the token after the given node or token, skipping any comments unless includeComments is true.
373+
* @param {AnyNode|Token} nodeOrToken The node or token to get the next token for.
374+
* @param {Object} [options] Options object.
375+
* @param {boolean} [options.includeComments=false] If true, return comments when they are present.
376+
* @returns {Token|null} The next token or comment, or null if there is none.
377+
*/
378+
getTokenAfter(nodeOrToken, { includeComments = false } = {}) {
379+
const index = this.#tokenEnds.get(nodeOrToken.range[1]);
380+
381+
if (index === undefined) {
382+
return null;
383+
}
384+
385+
let nextIndex = index + 1;
386+
const tokens = this.ast.tokens;
387+
if (nextIndex >= tokens.length) {
388+
return null;
389+
}
390+
391+
let tokenOrComment = tokens[nextIndex];
392+
393+
if (includeComments) {
394+
return tokenOrComment;
395+
}
396+
397+
// skip comments
398+
while (tokenOrComment?.type.endsWith("Comment")) {
399+
nextIndex++;
400+
401+
if (nextIndex >= tokens.length) {
402+
return null;
403+
}
404+
405+
tokenOrComment = tokens[nextIndex];
406+
}
407+
408+
return tokenOrComment;
409+
}
286410
}

0 commit comments

Comments
 (0)