@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 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
+ }
@@ -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 };