@vesk/prettier-plugin 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/package.json +42 -0
- package/src/comments.js +145 -0
- package/src/index.js +77 -0
- package/src/print.js +1382 -0
package/README.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# @vesk/prettier-plugin
|
|
2
|
+
|
|
3
|
+
Prettier plugin for the Vesk component language (`.vsk` files).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install @vesk/prettier-plugin prettier
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"plugins": ["@vesk/prettier-plugin"]
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## License
|
|
20
|
+
|
|
21
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vesk/prettier-plugin",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Prettier plugin for the Vesk component language",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"default": "./src/index.js"
|
|
10
|
+
},
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"src/",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"prettier",
|
|
19
|
+
"plugin",
|
|
20
|
+
"vesk",
|
|
21
|
+
"formatter"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "emeraldlinks",
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/emeraldlinks/veskTs.git"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/emeraldlinks/veskTs#readme",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/emeraldlinks/veskTs/issues"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"prettier": "^3.0.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@vesk/compiler": "^0.0.1"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/comments.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Attaches comments (collected via acorn's `onComment`) to AST nodes so the
|
|
3
|
+
* printer can emit them. Comments are attached as `leadingComments` /
|
|
4
|
+
* `trailingComments` on statement-list items and block bodies; anything that
|
|
5
|
+
* falls outside a statement list is attached to the innermost containing node
|
|
6
|
+
* as `innerComments`.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
function isNode(value) {
|
|
10
|
+
return (
|
|
11
|
+
value !== null &&
|
|
12
|
+
typeof value === 'object' &&
|
|
13
|
+
typeof value.type === 'string' &&
|
|
14
|
+
typeof value.start === 'number'
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function sameLine(a, b) {
|
|
19
|
+
return a.loc && b.loc && a.loc.end.line === b.loc.start.line;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** Statement/child lists where comments between members matter. */
|
|
23
|
+
function childArrays(node) {
|
|
24
|
+
const lists = [];
|
|
25
|
+
if (!isNode(node)) return lists;
|
|
26
|
+
switch (node.type) {
|
|
27
|
+
case 'Program':
|
|
28
|
+
lists.push(node.body);
|
|
29
|
+
break;
|
|
30
|
+
case 'BlockStatement':
|
|
31
|
+
lists.push(node.body);
|
|
32
|
+
break;
|
|
33
|
+
case 'SwitchCase':
|
|
34
|
+
lists.push(node.consequent);
|
|
35
|
+
break;
|
|
36
|
+
case 'ClassBody':
|
|
37
|
+
lists.push(node.body);
|
|
38
|
+
break;
|
|
39
|
+
case 'TSInterfaceBody':
|
|
40
|
+
lists.push(node.body);
|
|
41
|
+
break;
|
|
42
|
+
case 'JSXElement':
|
|
43
|
+
case 'JSXFragment':
|
|
44
|
+
lists.push(node.children || []);
|
|
45
|
+
break;
|
|
46
|
+
case 'TSTypeLiteral':
|
|
47
|
+
lists.push(node.members || node.body || []);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
return lists;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function collectNodes(node, out) {
|
|
54
|
+
if (!isNode(node)) return;
|
|
55
|
+
out.push(node);
|
|
56
|
+
for (const key of Object.keys(node)) {
|
|
57
|
+
if (key === 'loc' || key === 'range' || key === 'start' || key === 'end' || key === 'leadingComments' || key === 'trailingComments' || key === 'innerComments') continue;
|
|
58
|
+
const value = node[key];
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
for (const item of value) collectNodes(item, out);
|
|
61
|
+
} else if (isNode(value)) {
|
|
62
|
+
collectNodes(value, out);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function findInnermostContainer(ast, pos) {
|
|
68
|
+
let best = null;
|
|
69
|
+
const nodes = [];
|
|
70
|
+
collectNodes(ast, nodes);
|
|
71
|
+
for (const n of nodes) {
|
|
72
|
+
if (n.start <= pos && pos <= n.end) {
|
|
73
|
+
if (!best || n.end - n.start < best.end - best.start) best = n;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return best;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Recursively distributes comments into statement/child lists, then falls back
|
|
81
|
+
* to attaching unhandled comments as innerComments of their container.
|
|
82
|
+
*/
|
|
83
|
+
export function attachComments(ast, comments) {
|
|
84
|
+
if (!comments || comments.length === 0) return;
|
|
85
|
+
const remaining = comments.slice().sort((a, b) => a.start - b.start);
|
|
86
|
+
|
|
87
|
+
const processList = (children) => {
|
|
88
|
+
if (!Array.isArray(children) || children.length === 0) return;
|
|
89
|
+
for (let i = 0; i < remaining.length; i++) {
|
|
90
|
+
const c = remaining[i];
|
|
91
|
+
let idx = -1;
|
|
92
|
+
for (let j = 0; j < children.length; j++) {
|
|
93
|
+
if (children[j].start >= c.end) { idx = j; break; }
|
|
94
|
+
}
|
|
95
|
+
if (idx === -1) {
|
|
96
|
+
// Comment after the last child — attach as a trailing comment if it
|
|
97
|
+
// starts on the same line as the previous child.
|
|
98
|
+
const last = children[children.length - 1];
|
|
99
|
+
if (last && sameLine(last, c)) {
|
|
100
|
+
(last.trailingComments ||= []).push(c);
|
|
101
|
+
remaining.splice(i, 1);
|
|
102
|
+
i--;
|
|
103
|
+
}
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const child = children[idx];
|
|
107
|
+
const prev = idx > 0 ? children[idx - 1] : null;
|
|
108
|
+
if (prev && sameLine(prev, c)) {
|
|
109
|
+
(prev.trailingComments ||= []).push(c);
|
|
110
|
+
remaining.splice(i, 1);
|
|
111
|
+
i--;
|
|
112
|
+
} else {
|
|
113
|
+
(child.leadingComments ||= []).push(c);
|
|
114
|
+
remaining.splice(i, 1);
|
|
115
|
+
i--;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const walk = (node) => {
|
|
121
|
+
if (!isNode(node)) return;
|
|
122
|
+
// Recurse into children first so comments that live inside a nested
|
|
123
|
+
// statement list are claimed by the deepest list before an enclosing
|
|
124
|
+
// list (Program/block body) can snap them up as leading comments.
|
|
125
|
+
for (const key of Object.keys(node)) {
|
|
126
|
+
if (['loc', 'range', 'start', 'end', 'leadingComments', 'trailingComments', 'innerComments'].includes(key)) continue;
|
|
127
|
+
const value = node[key];
|
|
128
|
+
if (Array.isArray(value)) {
|
|
129
|
+
for (const item of value) walk(item);
|
|
130
|
+
} else if (isNode(value)) {
|
|
131
|
+
walk(value);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const list of childArrays(node)) processList(list);
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
walk(ast);
|
|
138
|
+
|
|
139
|
+
// Any comment still unplaced is attached as an inner comment of the smallest
|
|
140
|
+
// node that contains it.
|
|
141
|
+
for (const c of remaining) {
|
|
142
|
+
const container = findInnermostContainer(ast, c.start);
|
|
143
|
+
if (container) (container.innerComments ||= []).push(c);
|
|
144
|
+
}
|
|
145
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createBaseParser } from '@vesk/compiler';
|
|
2
|
+
import { preprocessForClauses } from '@vesk/compiler/src/parser';
|
|
3
|
+
import { attachComments } from './comments.js';
|
|
4
|
+
import { printVeskNode } from './print.js';
|
|
5
|
+
|
|
6
|
+
export const languages = [
|
|
7
|
+
{
|
|
8
|
+
name: 'vesk',
|
|
9
|
+
parsers: ['vesk'],
|
|
10
|
+
extensions: ['.vsk'],
|
|
11
|
+
vscodeLanguageIds: ['vsk'],
|
|
12
|
+
tmScope: 'source.vsk',
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {string} text
|
|
18
|
+
* @param {import('prettier').ParserOptions} options
|
|
19
|
+
* @returns {import('estree').Program}
|
|
20
|
+
*/
|
|
21
|
+
function parseVesk(text, options) {
|
|
22
|
+
const parser = createBaseParser();
|
|
23
|
+
const { code, annotations } = preprocessForClauses(text);
|
|
24
|
+
const comments = [];
|
|
25
|
+
const ast = parser.parse(code, {
|
|
26
|
+
ecmaVersion: 'latest',
|
|
27
|
+
sourceType: 'module',
|
|
28
|
+
locations: true,
|
|
29
|
+
ranges: true,
|
|
30
|
+
...(options.filepath ? { sourceFilename: options.filepath } : {}),
|
|
31
|
+
onComment: comments,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (annotations.length > 0) {
|
|
35
|
+
ast.__vskAnnotations = annotations;
|
|
36
|
+
}
|
|
37
|
+
// The printer splices `; key …` / `; index …` clauses back from the original
|
|
38
|
+
// text, so stash both on the options object used by print().
|
|
39
|
+
options.__vskAnnotations = annotations;
|
|
40
|
+
|
|
41
|
+
attachComments(ast, comments);
|
|
42
|
+
|
|
43
|
+
return ast;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {import('estree').Node} node
|
|
48
|
+
* @returns {number}
|
|
49
|
+
*/
|
|
50
|
+
function locStart(node) {
|
|
51
|
+
return node.start;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @param {import('estree').Node} node
|
|
56
|
+
* @returns {number}
|
|
57
|
+
*/
|
|
58
|
+
function locEnd(node) {
|
|
59
|
+
return node.end;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export const parsers = {
|
|
63
|
+
vesk: {
|
|
64
|
+
astFormat: 'vesk-ast',
|
|
65
|
+
parse: parseVesk,
|
|
66
|
+
locStart,
|
|
67
|
+
locEnd,
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export const printers = {
|
|
72
|
+
'vesk-ast': {
|
|
73
|
+
print: printVeskNode,
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export default { languages, parsers, printers };
|