@player-tools/complexity-check-plugin 0.8.1--canary.169.3663

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.
@@ -0,0 +1,261 @@
1
+ import {
2
+ Diagnostic,
3
+ DiagnosticSeverity,
4
+ Range,
5
+ TextDocument,
6
+ } from "vscode-languageserver-types";
7
+ import { resolveDataRefs } from "@player-ui/player";
8
+ import {
9
+ getProperty,
10
+ isPropertyNode,
11
+ AssetASTNode,
12
+ type ASTNode,
13
+ type ViewASTNode,
14
+ type PlayerLanguageService,
15
+ type PlayerLanguageServicePlugin,
16
+ type ASTVisitor,
17
+ } from "@player-tools/json-language-service";
18
+
19
+ const makeRange = (
20
+ start: number,
21
+ end: number,
22
+ document: TextDocument
23
+ ): Range => {
24
+ return {
25
+ start: document.positionAt(start),
26
+ end: document.positionAt(end),
27
+ };
28
+ };
29
+
30
+ export interface ComplexityCheckConfig {
31
+ /** Cutoff for content to be acceptable */
32
+ maxAcceptableComplexity: number;
33
+ /**
34
+ * If set, any score above this number but below `maxAcceptableComplexity` will be logged as a warning
35
+ */
36
+ maxWarningLevel?: number;
37
+ /** If set, maps complexity based on asset or view type */
38
+ assetComplexity?: Record<string, number>;
39
+ /** A way to pass in configurable options */
40
+ options?: {
41
+ /** Read out all logging messages */
42
+ verbose?: boolean;
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Estimates complexity of content
48
+ */
49
+ export class ComplexityCheck implements PlayerLanguageServicePlugin {
50
+ name = "complexity-check";
51
+
52
+ private config: ComplexityCheckConfig;
53
+ private contentScore: number;
54
+ private range: {
55
+ start: number;
56
+ end: number;
57
+ };
58
+
59
+ constructor(config: ComplexityCheckConfig) {
60
+ this.config = config;
61
+ this.contentScore = 0;
62
+ this.range = { start: 0, end: 0 };
63
+ }
64
+
65
+ apply(service: PlayerLanguageService): void {
66
+ service.hooks.validate.tap(this.name, async (_ctx, validation) => {
67
+ validation.useASTVisitor(this.createContentChecker());
68
+ });
69
+
70
+ service.hooks.onDocumentUpdate.tap(this.name, () => {
71
+ this.contentScore = 0;
72
+ });
73
+
74
+ service.hooks.onValidateEnd.tap(this.name, (diagnostics, context) => {
75
+ let diagnostic: Diagnostic;
76
+
77
+ if (this.contentScore < this.config.maxAcceptableComplexity) {
78
+ if (
79
+ this.config.maxWarningLevel &&
80
+ this.contentScore > this.config.maxWarningLevel
81
+ ) {
82
+ diagnostic = {
83
+ message: `Warning: Content complexity is ${this.contentScore}`,
84
+ severity: DiagnosticSeverity.Warning,
85
+ range: makeRange(
86
+ this.range.start,
87
+ this.range.end,
88
+ context.documentContext.document
89
+ ),
90
+ };
91
+ } else {
92
+ diagnostic = {
93
+ message: `Info: Content complexity is ${this.contentScore}`,
94
+ severity: DiagnosticSeverity.Error,
95
+ range: makeRange(
96
+ this.range.start,
97
+ this.range.end,
98
+ context.documentContext.document
99
+ ),
100
+ };
101
+ }
102
+ } else {
103
+ diagnostic = {
104
+ message: `Error: Content complexity is ${this.contentScore}`,
105
+ severity: DiagnosticSeverity.Information,
106
+ range: makeRange(
107
+ this.range.start,
108
+ this.range.end,
109
+ context.documentContext.document
110
+ ),
111
+ };
112
+ }
113
+ return [...diagnostics, diagnostic];
114
+ });
115
+ }
116
+
117
+ /** Create a validation visitor for dealing with transition states */
118
+ createContentChecker = (): ASTVisitor => {
119
+ return {
120
+ FlowNode: (flowNode) => {
121
+ this.range = {
122
+ start: flowNode.jsonNode.offset,
123
+ end: flowNode.jsonNode.offset + flowNode.jsonNode.length,
124
+ };
125
+ },
126
+
127
+ FlowStateNode: (flowState) => {
128
+ // add complexity per expression in a state
129
+ if (flowState.stateType) {
130
+ if (flowState.stateType.valueNode?.value === "ACTION") {
131
+ const numExp = getProperty(flowState, "exp");
132
+ if (numExp?.valueNode?.type === "array") {
133
+ this.contentScore += numExp.valueNode.children.length;
134
+ if (this.config.options?.verbose) {
135
+ console.log(
136
+ `state exp (x${numExp.valueNode.children.length}): ${this.contentScore}`
137
+ );
138
+ }
139
+ } else {
140
+ this.contentScore += 1;
141
+ if (this.config.options?.verbose) {
142
+ console.log(`state exp: ${this.contentScore}`);
143
+ }
144
+ }
145
+ }
146
+ }
147
+ },
148
+ AssetNode: (assetNode: AssetASTNode) => {
149
+ let scoreModifier = 1;
150
+ // recursively check parent nodes for templates
151
+ const checkParentTemplate = (node: ASTNode) => {
152
+ if (node.parent) {
153
+ if (
154
+ isPropertyNode(node.parent) &&
155
+ node.parent.keyNode.value === "template"
156
+ ) {
157
+ // incerases the score modifier for each template parent
158
+ scoreModifier += 1;
159
+
160
+ if (this.config.options?.verbose) {
161
+ console.log(`found a template parent (+1)`);
162
+ }
163
+ return checkParentTemplate(node.parent);
164
+ }
165
+ return checkParentTemplate(node.parent);
166
+ }
167
+ return node;
168
+ };
169
+
170
+ checkParentTemplate(assetNode);
171
+ this.contentScore += scoreModifier;
172
+
173
+ const assetType = assetNode.assetType?.valueNode?.value;
174
+
175
+ // Map the assetComplexity score based on the asset type
176
+ const assetComplexity = assetType
177
+ ? this.config.assetComplexity?.[assetType]
178
+ : undefined;
179
+
180
+ // Only run if assetComplexity is set in the config
181
+ if (this.config.assetComplexity) {
182
+ if (assetComplexity) {
183
+ this.contentScore += assetComplexity;
184
+ if (this.config.options?.verbose) {
185
+ console.log(
186
+ `assetNode (+${assetComplexity} for ${assetType}): ${this.contentScore}`
187
+ );
188
+ }
189
+ } else {
190
+ if (this.config.options?.verbose) {
191
+ console.log(
192
+ `assetNode (${assetType} complexity type not found): ${this.contentScore}`
193
+ );
194
+ }
195
+ }
196
+ } else if (this.config.options?.verbose) {
197
+ console.log("assetNode:", this.contentScore);
198
+ }
199
+ },
200
+ ViewNode: (viewNode: ViewASTNode) => {
201
+ this.contentScore += 1;
202
+
203
+ const viewType = viewNode.viewType?.valueNode?.value;
204
+
205
+ // Map the assetComplexity score based on the view type
206
+ const viewComplexity = viewType
207
+ ? this.config.assetComplexity?.[viewType]
208
+ : undefined;
209
+
210
+ // Only run if assetComplexity is set in the config
211
+ if (this.config.assetComplexity) {
212
+ if (viewComplexity) {
213
+ this.contentScore += viewComplexity;
214
+ if (this.config.options?.verbose) {
215
+ console.log(
216
+ `viewNode (+${viewComplexity} for ${viewType}): ${this.contentScore}`
217
+ );
218
+ }
219
+ } else {
220
+ if (this.config.options?.verbose) {
221
+ console.log(
222
+ `viewNode (${viewType} complexity type not found): ${this.contentScore}`
223
+ );
224
+ }
225
+ }
226
+ } else if (this.config.options?.verbose) {
227
+ console.log("viewNode:", this.contentScore);
228
+ }
229
+ },
230
+ StringNode: (stringNode) => {
231
+ const stringContent = stringNode.value;
232
+ resolveDataRefs(stringContent, {
233
+ model: {
234
+ get: (binding) => {
235
+ this.contentScore += 2;
236
+ if (this.config.options?.verbose) {
237
+ console.log(`model (get: ${binding}): ${this.contentScore}`);
238
+ }
239
+ return binding;
240
+ },
241
+ set: (binding) => {
242
+ this.contentScore += 2;
243
+ if (this.config.options?.verbose) {
244
+ console.log(`model (set: ${binding}): ${this.contentScore}`);
245
+ }
246
+ return [];
247
+ },
248
+ delete: () => {},
249
+ },
250
+ evaluate: (str) => {
251
+ this.contentScore += 2;
252
+ if (this.config.options?.verbose) {
253
+ console.log(`model (evaluate: ${str}): ${this.contentScore}`);
254
+ }
255
+ return str;
256
+ },
257
+ });
258
+ },
259
+ };
260
+ };
261
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./complexity-check";
@@ -0,0 +1,30 @@
1
+ import { type PlayerLanguageService, type PlayerLanguageServicePlugin, type ASTVisitor } from "@player-tools/json-language-service";
2
+ export interface ComplexityCheckConfig {
3
+ /** Cutoff for content to be acceptable */
4
+ maxAcceptableComplexity: number;
5
+ /**
6
+ * If set, any score above this number but below `maxAcceptableComplexity` will be logged as a warning
7
+ */
8
+ maxWarningLevel?: number;
9
+ /** If set, maps complexity based on asset or view type */
10
+ assetComplexity?: Record<string, number>;
11
+ /** A way to pass in configurable options */
12
+ options?: {
13
+ /** Read out all logging messages */
14
+ verbose?: boolean;
15
+ };
16
+ }
17
+ /**
18
+ * Estimates complexity of content
19
+ */
20
+ export declare class ComplexityCheck implements PlayerLanguageServicePlugin {
21
+ name: string;
22
+ private config;
23
+ private contentScore;
24
+ private range;
25
+ constructor(config: ComplexityCheckConfig);
26
+ apply(service: PlayerLanguageService): void;
27
+ /** Create a validation visitor for dealing with transition states */
28
+ createContentChecker: () => ASTVisitor;
29
+ }
30
+ //# sourceMappingURL=complexity-check.d.ts.map
@@ -0,0 +1,2 @@
1
+ export * from "./complexity-check";
2
+ //# sourceMappingURL=index.d.ts.map