PluginProbe
Code Snippets / 3.9.1
Code Snippets v3.9.1
3.10.2 3.10.1 3.10.0 3.10.0-beta.2 3.10.0-beta.1 4.0.0-beta.1 3.9.6 trunk 2.10.0 2.10.1 2.12.0 2.12.1 2.13.0 2.13.1 2.13.2 2.13.3 2.14.0 2.14.1 2.14.2 2.14.3 2.14.4 2.14.5 2.14.6 3.0.0 3.0.1 All 64 releases
code-snippets / js / utils / Linter.ts

Linter.ts in Code Snippets 3.9.1, at js/utils/Linter.ts

171 lines 4.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Based on work licensed under the BSD 3-Clause license.
3 *
4 * Copyright (c) 2017, glayzzle
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions are met:
9 *
10 * * Redistributions of source code must retain the above copyright notice, this
11 * list of conditions and the following disclaimer.
12 *
13 * * Redistributions in binary form must reproduce the above copyright notice,
14 * this list of conditions and the following disclaimer in the documentation
15 * and/or other materials provided with the distribution.
16 *
17 * * Neither the name of the copyright holder nor the names of its
18 * contributors may be used to endorse or promote products derived from
19 * this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 */
32
33 import { Engine } from 'php-parser'
34 import CodeMirror from 'codemirror'
35 import type { Block, Location, Node } from 'php-parser'
36
37 export interface Annotation {
38 message: string
39 severity: string
40 from: CodeMirror.Position
41 to: CodeMirror.Position
42 }
43
44 export interface Identifier extends Node {
45 name: string
46 }
47
48 export interface Declaration extends Node {
49 name: Identifier | string
50 }
51
52 export class Linter {
53 private readonly code: string
54
55 private readonly function_names: Set<string>
56
57 private readonly class_names: Set<string>
58
59 public readonly annotations: Annotation[]
60
61 /**
62 * Constructor.
63 * @param code
64 */
65 constructor(code: string) {
66 this.code = code
67 this.annotations = []
68
69 this.function_names = new Set()
70 this.class_names = new Set()
71 }
72
73 /**
74 * Lint the provided code.
75 */
76 lint() {
77 const parser = new Engine({
78 parser: {
79 suppressErrors: true,
80 version: 800
81 },
82 ast: {
83 withPositions: true
84 }
85 })
86
87 try {
88 const program = parser.parseEval(this.code)
89
90 if (0 < program.errors.length) {
91 for (const error of program.errors) {
92 this.annotate(error.message, error.loc)
93 }
94 }
95
96 this.visit(program)
97 } catch (error) {
98 console.error(error)
99 }
100 }
101
102 /**
103 * Visit nodes recursively.
104 * @param node
105 */
106 visit(node: Node) {
107 if (node.kind) {
108 this.validate(node)
109 }
110
111 if ('children' in node) {
112 const block = <Block> node
113 for (const child of block.children) {
114 this.visit(child)
115 }
116 }
117 }
118
119 /**
120 * Check whether a given identifier has already been defined, creating an annotation if so.
121 * @param identifier
122 * @param registry
123 * @param label
124 */
125 checkDuplicateIdentifier(identifier: Identifier, registry: Set<string>, label: string) {
126 if (registry.has(identifier.name)) {
127 this.annotate(`Cannot redeclare ${label} ${identifier.name}()`, identifier.loc)
128 } else {
129 registry.add(identifier.name)
130 }
131 }
132
133 /**
134 * Perform additional validations on nodes.
135 * @param node
136 */
137 validate(node: Node) {
138 const decl = <Declaration> node
139 const ident = <Identifier> decl.name
140
141 if (!('name' in decl && 'name' in ident) || 'identifier' !== ident.kind) {
142 return
143 }
144
145 if ('function' === node.kind) {
146 this.checkDuplicateIdentifier(ident, this.function_names, 'function')
147 } else if ('class' === node.kind) {
148 this.checkDuplicateIdentifier(ident, this.class_names, 'class')
149 }
150 }
151
152 /**
153 * Create a lint annotation.
154 * @param message
155 * @param location
156 * @param severity
157 */
158 annotate(message: string, location: Location | null, severity = 'error') {
159 const [start, end] = location
160 ? location.end.offset < location.start.offset ? [location.end, location.start] : [location.start, location.end]
161 : [{ line: 0, column: 0 }, { line: 0, column: 0 }]
162
163 this.annotations.push({
164 message,
165 severity,
166 from: CodeMirror.Pos(start.line - 1, start.column),
167 to: CodeMirror.Pos(end.line - 1, end.column)
168 })
169 }
170 }
171