bare-script 3.7.5 → 3.8.0
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/lib/bare.js +62 -39
- package/lib/include/args.bare +0 -7
- package/lib/include/dataTable.bare +0 -7
- package/lib/include/diff.bare +2 -9
- package/lib/include/forms.bare +0 -7
- package/lib/include/markdownUp.bare +0 -8
- package/lib/include/pager.bare +0 -7
- package/lib/include/unittest.bare +64 -40
- package/lib/include/unittestMock.bare +0 -7
- package/lib/library.js +25 -0
- package/lib/model.js +32 -5
- package/lib/runtimeAsync.js +24 -3
- package/package.json +1 -1
package/lib/bare.js
CHANGED
|
@@ -8,6 +8,7 @@ import {executeScriptAsync} from './runtimeAsync.js';
|
|
|
8
8
|
import {fileURLToPath} from 'url';
|
|
9
9
|
import {lintScript} from './model.js';
|
|
10
10
|
import {readFile} from 'fs/promises';
|
|
11
|
+
import {systemGlobalIncludesName} from './library.js';
|
|
11
12
|
import {urlFileRelative} from './options.js';
|
|
12
13
|
import {valueBoolean} from './value.js';
|
|
13
14
|
|
|
@@ -55,12 +56,20 @@ export async function main(options) {
|
|
|
55
56
|
|
|
56
57
|
// Get the scripts to run
|
|
57
58
|
let {scripts} = args;
|
|
59
|
+
let ixUserScript = 0;
|
|
58
60
|
if (args.markdownUp) {
|
|
59
61
|
scripts = [['code', 'include <markdownUp.bare>'], ...scripts];
|
|
62
|
+
ixUserScript = 1;
|
|
63
|
+
|
|
64
|
+
// Add unittest.bare argument globals
|
|
65
|
+
globals.vUnittestReport = true;
|
|
66
|
+
if (args.static) {
|
|
67
|
+
globals.vUnittestDisabled = true;
|
|
68
|
+
}
|
|
60
69
|
}
|
|
61
70
|
|
|
62
71
|
// Parse and execute all source files in order
|
|
63
|
-
for (const [scriptType, scriptValue] of scripts) {
|
|
72
|
+
for (const [ixScript, [scriptType, scriptValue]] of scripts.entries()) {
|
|
64
73
|
// Get the script source
|
|
65
74
|
let scriptName;
|
|
66
75
|
let scriptSource;
|
|
@@ -80,16 +89,58 @@ export async function main(options) {
|
|
|
80
89
|
}
|
|
81
90
|
} else {
|
|
82
91
|
inlineCount += 1;
|
|
83
|
-
|
|
92
|
+
const inlineDisplay = inlineCount - ixUserScript;
|
|
93
|
+
scriptName = `<string${inlineDisplay > 1 ? inlineDisplay : ''}>`;
|
|
84
94
|
scriptSource = scriptValue;
|
|
85
95
|
}
|
|
86
96
|
|
|
87
97
|
// Parse the script source
|
|
88
98
|
const script = parseScript(scriptSource, 1, scriptName);
|
|
89
99
|
|
|
100
|
+
// Execute?
|
|
101
|
+
let staticGlobals = null;
|
|
102
|
+
if (args.static !== 's') {
|
|
103
|
+
// Set the globals to use for static analysis (below)
|
|
104
|
+
if (!args.static || ixScript < ixUserScript) {
|
|
105
|
+
staticGlobals = globals;
|
|
106
|
+
} else {
|
|
107
|
+
// Copy global to keep each script as isolated as possible
|
|
108
|
+
staticGlobals = {...globals};
|
|
109
|
+
const globalIncludes = staticGlobals[systemGlobalIncludesName] ?? null;
|
|
110
|
+
if (globalIncludes !== null && typeof globalIncludes === 'object') {
|
|
111
|
+
staticGlobals[systemGlobalIncludesName] = {...globalIncludes};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Execute the script
|
|
116
|
+
const timeBegin = performance.now();
|
|
117
|
+
const {fetchFn} = options;
|
|
118
|
+
const fetchIncludeFn = (fetchURL, fetchOptions) => fetchInclude(fetchFn, fetchURL, fetchOptions);
|
|
119
|
+
const result = await executeScriptAsync(script, {
|
|
120
|
+
'debug': args.debug ?? false,
|
|
121
|
+
'fetchFn': fetchIncludeFn,
|
|
122
|
+
'globals': staticGlobals,
|
|
123
|
+
'logFn': options.logFn,
|
|
124
|
+
'systemPrefix': fetchIncludePrefix,
|
|
125
|
+
'urlFn': scriptType === 'file' ? (url) => urlFileRelative(scriptName, url) : null
|
|
126
|
+
|
|
127
|
+
});
|
|
128
|
+
if (Number.isInteger(result) && result >= 0 && result <= 255) {
|
|
129
|
+
statusCode = result || statusCode;
|
|
130
|
+
} else {
|
|
131
|
+
statusCode = (valueBoolean(result) ? 1 : 0) || statusCode;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Log script execution end with timing
|
|
135
|
+
if (args.debug && ixScript >= ixUserScript) {
|
|
136
|
+
const timeEnd = performance.now();
|
|
137
|
+
options.logFn(`BareScript executed in ${(timeEnd - timeBegin).toFixed(1)} milliseconds`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
90
141
|
// Run the bare-script linter?
|
|
91
|
-
if (args.static
|
|
92
|
-
const warnings = lintScript(script);
|
|
142
|
+
if (args.static && ixScript >= ixUserScript) {
|
|
143
|
+
const warnings = lintScript(script, staticGlobals);
|
|
93
144
|
if (warnings.length === 0) {
|
|
94
145
|
options.logFn(`BareScript static analysis "${scriptName}" ... OK`);
|
|
95
146
|
} else {
|
|
@@ -99,43 +150,12 @@ export async function main(options) {
|
|
|
99
150
|
for (const warning of warnings) {
|
|
100
151
|
options.logFn(warning);
|
|
101
152
|
}
|
|
102
|
-
|
|
103
|
-
statusCode = 1;
|
|
104
|
-
break;
|
|
105
|
-
}
|
|
153
|
+
statusCode = 1;
|
|
106
154
|
}
|
|
107
155
|
}
|
|
108
|
-
if (args.static) {
|
|
109
|
-
continue;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Execute the script
|
|
113
|
-
const timeBegin = performance.now();
|
|
114
|
-
const {fetchFn} = options;
|
|
115
|
-
const fetchIncludeFn = (fetchURL, fetchOptions) => fetchInclude(fetchFn, fetchURL, fetchOptions);
|
|
116
|
-
const result = await executeScriptAsync(script, {
|
|
117
|
-
'debug': args.debug ?? false,
|
|
118
|
-
'fetchFn': fetchIncludeFn,
|
|
119
|
-
'globals': globals,
|
|
120
|
-
'logFn': options.logFn,
|
|
121
|
-
'systemPrefix': fetchIncludePrefix,
|
|
122
|
-
'urlFn': scriptType === 'file' ? (url) => urlFileRelative(scriptName, url) : null
|
|
123
|
-
|
|
124
|
-
});
|
|
125
|
-
if (Number.isInteger(result) && result >= 0 && result <= 255) {
|
|
126
|
-
statusCode = result;
|
|
127
|
-
} else {
|
|
128
|
-
statusCode = valueBoolean(result) ? 1 : 0;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
// Log script execution end with timing
|
|
132
|
-
if (args.debug) {
|
|
133
|
-
const timeEnd = performance.now();
|
|
134
|
-
options.logFn(`BareScript executed in ${(timeEnd - timeBegin).toFixed(1)} milliseconds`);
|
|
135
|
-
}
|
|
136
156
|
|
|
137
157
|
// Stop on error status code
|
|
138
|
-
if (statusCode !== 0) {
|
|
158
|
+
if (statusCode !== 0 && !args.static) {
|
|
139
159
|
break;
|
|
140
160
|
}
|
|
141
161
|
}
|
|
@@ -202,7 +222,9 @@ export function parseArgs(argv) {
|
|
|
202
222
|
} else if (arg === '-m' || arg === '--markdown-up') {
|
|
203
223
|
args.markdownUp = true;
|
|
204
224
|
} else if (arg === '-s' || arg === '--static') {
|
|
205
|
-
args.static =
|
|
225
|
+
args.static = 's';
|
|
226
|
+
} else if (arg === '-x' || arg === '--staticx') {
|
|
227
|
+
args.static = 'x';
|
|
206
228
|
} else if (arg === '-v' || arg === '--var') {
|
|
207
229
|
if (iArg + 2 >= argv.length) {
|
|
208
230
|
throw new Error('argument -v/--var: expected 2 arguments');
|
|
@@ -222,7 +244,7 @@ export function parseArgs(argv) {
|
|
|
222
244
|
|
|
223
245
|
// The command-line interface (CLI) help text
|
|
224
246
|
export const helpText = `\
|
|
225
|
-
usage: bare [-h] [-c CODE] [-d] [-m] [-s] [-v VAR EXPR] [file ...]
|
|
247
|
+
usage: bare [-h] [-c CODE] [-d] [-m] [-s] [-x] [-v VAR EXPR] [file ...]
|
|
226
248
|
|
|
227
249
|
The BareScript command-line interface
|
|
228
250
|
|
|
@@ -235,4 +257,5 @@ options:
|
|
|
235
257
|
-d, --debug enable debug mode
|
|
236
258
|
-m, --markdown-up run with MarkdownUp stubs
|
|
237
259
|
-s, --static perform static analysis
|
|
260
|
+
-x, --staticx perform static analysis with execution
|
|
238
261
|
-v, --var VAR EXPR set a global variable to an expression value`;
|
package/lib/include/args.bare
CHANGED
|
@@ -2,13 +2,6 @@
|
|
|
2
2
|
# https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
# Include sentinel
|
|
6
|
-
if systemGlobalGet('argsSentinel'):
|
|
7
|
-
return
|
|
8
|
-
endif
|
|
9
|
-
argsSentinel = true
|
|
10
|
-
|
|
11
|
-
|
|
12
5
|
# The URL arguments model
|
|
13
6
|
argsTypes = schemaParse( \
|
|
14
7
|
'group "args.bare"', \
|
|
@@ -2,13 +2,6 @@
|
|
|
2
2
|
# https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
# Include sentinel
|
|
6
|
-
if systemGlobalGet('dataTableSentinel'):
|
|
7
|
-
return
|
|
8
|
-
endif
|
|
9
|
-
dataTableSentinel = true
|
|
10
|
-
|
|
11
|
-
|
|
12
5
|
# The data table model's Schema Markdown
|
|
13
6
|
dataTableTypes = schemaParse( \
|
|
14
7
|
'group "Data Table"', \
|
package/lib/include/diff.bare
CHANGED
|
@@ -2,13 +2,6 @@
|
|
|
2
2
|
# https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
# Include sentinel
|
|
6
|
-
if systemGlobalGet('diffSentinel'):
|
|
7
|
-
return
|
|
8
|
-
endif
|
|
9
|
-
diffSentinel = true
|
|
10
|
-
|
|
11
|
-
|
|
12
5
|
# The text line difference model
|
|
13
6
|
diffTypes = schemaParse( \
|
|
14
7
|
'group "diff.bare"', \
|
|
@@ -104,7 +97,7 @@ function diffLines(left, right):
|
|
|
104
97
|
endif
|
|
105
98
|
|
|
106
99
|
# Look ahead to find next matching point
|
|
107
|
-
foundMatch =
|
|
100
|
+
foundMatch = false
|
|
108
101
|
ixLeftTmp = ixLeft
|
|
109
102
|
while ixLeftTmp < leftLength:
|
|
110
103
|
ixRightTmp = ixRight
|
|
@@ -152,4 +145,4 @@ endfunction
|
|
|
152
145
|
|
|
153
146
|
|
|
154
147
|
# Regex for splitting lines
|
|
155
|
-
diffRegexLineSplit = regexNew(
|
|
148
|
+
diffRegexLineSplit = regexNew('\r?\n')
|
package/lib/include/forms.bare
CHANGED
|
@@ -2,13 +2,6 @@
|
|
|
2
2
|
# https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
# Include sentinel
|
|
6
|
-
if systemGlobalGet('formsSentinel'):
|
|
7
|
-
return
|
|
8
|
-
endif
|
|
9
|
-
formsSentinel = true
|
|
10
|
-
|
|
11
|
-
|
|
12
5
|
# $function: formsTextElements
|
|
13
6
|
# $group: forms.bare
|
|
14
7
|
# $doc: Create a text input [element model](https://github.com/craigahobbs/element-model#readme)
|
package/lib/include/pager.bare
CHANGED
|
@@ -2,69 +2,60 @@
|
|
|
2
2
|
# https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
# Include sentinel
|
|
6
|
-
if systemGlobalGet('unittestSentinel'):
|
|
7
|
-
return
|
|
8
|
-
endif
|
|
9
|
-
unittestSentinel = true
|
|
10
|
-
|
|
11
|
-
|
|
12
5
|
include <args.bare>
|
|
13
6
|
include <dataTable.bare>
|
|
14
7
|
include <diff.bare>
|
|
15
8
|
|
|
16
9
|
|
|
17
|
-
# Test statistics
|
|
18
|
-
unittestTests = {}
|
|
19
|
-
unittestWarnings = []
|
|
20
|
-
|
|
21
|
-
|
|
22
10
|
# $function: unittestRunTest
|
|
23
11
|
# $group: unittest.bare
|
|
24
12
|
# $doc: Run a unit test
|
|
25
13
|
# $arg testName: The test function name
|
|
26
14
|
async function unittestRunTest(testName):
|
|
27
|
-
|
|
28
|
-
if
|
|
29
|
-
|
|
30
|
-
systemGlobalSet('unittestTestName', null)
|
|
15
|
+
# Tests disabled?
|
|
16
|
+
if systemGlobalGet('vUnittestDisabled'):
|
|
17
|
+
return
|
|
31
18
|
endif
|
|
32
|
-
endfunction
|
|
33
19
|
|
|
34
|
-
|
|
35
|
-
# For backward compatibility
|
|
36
|
-
async function unittestRunTestAsync(testName):
|
|
37
|
-
systemLogDebug('unittest.bare: unittestRunTestAsync is deprecated - use unittestRunTest')
|
|
38
|
-
return unittestRunTest(testName)
|
|
39
|
-
endfunction
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
# unittestRunTest helper function
|
|
43
|
-
function unittestRunTestHelper(testName):
|
|
44
20
|
# Single test argument?
|
|
21
|
+
vUnittestTest = systemGlobalGet('vUnittestTest')
|
|
45
22
|
if vUnittestTest && vUnittestTest != testName:
|
|
46
|
-
return
|
|
23
|
+
return
|
|
47
24
|
endif
|
|
48
25
|
|
|
26
|
+
# Get the global unittest data
|
|
27
|
+
unittestData = unittestDataGet()
|
|
28
|
+
unittestTests = objectGet(unittestData, 'tests')
|
|
29
|
+
unittestWarnings = objectGet(unittestData, 'warnings')
|
|
30
|
+
|
|
49
31
|
# Test run multiple times?
|
|
50
32
|
if objectHas(unittestTests, testName):
|
|
51
33
|
arrayPush(unittestWarnings, 'Test "' + testName + '" run multiple times')
|
|
52
|
-
return
|
|
34
|
+
return
|
|
53
35
|
endif
|
|
54
36
|
|
|
55
37
|
# Get the test func
|
|
56
38
|
testFn = systemGlobalGet(testName)
|
|
57
39
|
if !testFn:
|
|
58
40
|
arrayPush(unittestWarnings, 'Test "' + testName + '" not found')
|
|
59
|
-
return
|
|
41
|
+
return
|
|
60
42
|
endif
|
|
61
43
|
|
|
62
44
|
# Add the unit test result array
|
|
63
45
|
testFailures = []
|
|
64
46
|
objectSet(unittestTests, testName, testFailures)
|
|
65
|
-
systemGlobalSet('unittestTestName', testName)
|
|
66
47
|
|
|
67
|
-
|
|
48
|
+
# Run the test
|
|
49
|
+
objectSet(unittestData, 'current', testName)
|
|
50
|
+
testFn()
|
|
51
|
+
objectSet(unittestData, 'current', null)
|
|
52
|
+
endfunction
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# For backward compatibility
|
|
56
|
+
async function unittestRunTestAsync(testName):
|
|
57
|
+
systemLogDebug('unittest.bare: unittestRunTestAsync is deprecated - use unittestRunTest')
|
|
58
|
+
return unittestRunTest(testName)
|
|
68
59
|
endfunction
|
|
69
60
|
|
|
70
61
|
|
|
@@ -79,8 +70,13 @@ function unittestEqual(actual, expected, description):
|
|
|
79
70
|
return
|
|
80
71
|
endif
|
|
81
72
|
|
|
73
|
+
# Get the global unittest data
|
|
74
|
+
unittestData = unittestDataGet()
|
|
75
|
+
unittestTests = objectGet(unittestData, 'tests')
|
|
76
|
+
unittestCurrent = objectGet(unittestData, 'current')
|
|
77
|
+
testFailures = objectGet(unittestTests, unittestCurrent)
|
|
78
|
+
|
|
82
79
|
# Add the test failure error lines
|
|
83
|
-
testFailures = objectGet(unittestTests, unittestTestName)
|
|
84
80
|
errorLines = [ \
|
|
85
81
|
'Equal:', \
|
|
86
82
|
'', \
|
|
@@ -143,8 +139,13 @@ function unittestDeepEqual(actual, expected, description):
|
|
|
143
139
|
arrayPush(errorLines, '```')
|
|
144
140
|
endif
|
|
145
141
|
|
|
142
|
+
# Get the global unittest data
|
|
143
|
+
unittestData = unittestDataGet()
|
|
144
|
+
unittestTests = objectGet(unittestData, 'tests')
|
|
145
|
+
unittestCurrent = objectGet(unittestData, 'current')
|
|
146
|
+
testFailures = objectGet(unittestTests, unittestCurrent)
|
|
147
|
+
|
|
146
148
|
# Add the test failure error lines
|
|
147
|
-
testFailures = objectGet(unittestTests, unittestTestName)
|
|
148
149
|
if description:
|
|
149
150
|
arrayPush(testFailures, arrayExtend([markdownEscape(description), ''], errorLines))
|
|
150
151
|
else:
|
|
@@ -153,6 +154,17 @@ function unittestDeepEqual(actual, expected, description):
|
|
|
153
154
|
endfunction
|
|
154
155
|
|
|
155
156
|
|
|
157
|
+
# Helper to get the global unittest data object
|
|
158
|
+
function unittestDataGet():
|
|
159
|
+
unittestData = systemGlobalGet('unittestData')
|
|
160
|
+
if unittestData == null:
|
|
161
|
+
unittestData = {'tests': {}, 'warnings': [], 'current': null}
|
|
162
|
+
systemGlobalSet('unittestData', unittestData)
|
|
163
|
+
endif
|
|
164
|
+
return unittestData
|
|
165
|
+
endfunction
|
|
166
|
+
|
|
167
|
+
|
|
156
168
|
# $function: unittestReport
|
|
157
169
|
# $group: unittest.bare
|
|
158
170
|
# $doc: Render the unit test report
|
|
@@ -171,10 +183,16 @@ function unittestReport(options):
|
|
|
171
183
|
|
|
172
184
|
# Parse arguments
|
|
173
185
|
args = argsParse(unittestReportArguments)
|
|
174
|
-
|
|
175
|
-
scriptName = objectGet(args, 'script')
|
|
186
|
+
isDisabled = objectGet(args, 'disabled')
|
|
176
187
|
hideTests = objectGet(args, 'hideTests')
|
|
177
188
|
isReport = objectGet(args, 'report')
|
|
189
|
+
scriptName = objectGet(args, 'script')
|
|
190
|
+
testNameArg = objectGet(args, 'test')
|
|
191
|
+
|
|
192
|
+
# Disabled?
|
|
193
|
+
if isDisabled:
|
|
194
|
+
return 0
|
|
195
|
+
endif
|
|
178
196
|
|
|
179
197
|
# Script coverage details page?
|
|
180
198
|
if scriptName:
|
|
@@ -182,6 +200,11 @@ function unittestReport(options):
|
|
|
182
200
|
return 0
|
|
183
201
|
endif
|
|
184
202
|
|
|
203
|
+
# Get the global unittest data
|
|
204
|
+
unittestData = unittestDataGet()
|
|
205
|
+
unittestTests = objectGet(unittestData, 'tests')
|
|
206
|
+
unittestWarnings = objectGet(unittestData, 'warnings')
|
|
207
|
+
|
|
185
208
|
# Compute test statistics
|
|
186
209
|
testNames = arraySort(objectKeys(unittestTests))
|
|
187
210
|
testCount = arrayLength(testNames)
|
|
@@ -318,10 +341,11 @@ endfunction
|
|
|
318
341
|
|
|
319
342
|
# unittestReport application arguments
|
|
320
343
|
unittestReportArguments = argsValidate([ \
|
|
321
|
-
{'name': '
|
|
322
|
-
{'name': 'script', 'global': 'vUnittestScript'}, \
|
|
344
|
+
{'name': 'disabled', 'global': 'vUnittestDisabled', 'type': 'bool', 'default': false}, \
|
|
323
345
|
{'name': 'hideTests', 'global': 'vUnittestHideTests', 'type': 'bool', 'default': false}, \
|
|
324
|
-
{'name': 'report', 'global': 'vUnittestReport', 'type': 'bool', 'default': false} \
|
|
346
|
+
{'name': 'report', 'global': 'vUnittestReport', 'type': 'bool', 'default': false}, \
|
|
347
|
+
{'name': 'script', 'global': 'vUnittestScript'}, \
|
|
348
|
+
{'name': 'test', 'global': 'vUnittestTest'} \
|
|
325
349
|
])
|
|
326
350
|
|
|
327
351
|
|
|
@@ -393,7 +417,7 @@ endfunction
|
|
|
393
417
|
|
|
394
418
|
function unittestReportScriptLines(codeLineElements, lines, color, noNewline):
|
|
395
419
|
if lines:
|
|
396
|
-
linesStr = arrayJoin(lines,
|
|
420
|
+
linesStr = arrayJoin(lines, '\n') + if(noNewline, '', '\n')
|
|
397
421
|
if !color:
|
|
398
422
|
arrayPush(codeLineElements, {'text': linesStr})
|
|
399
423
|
else:
|
|
@@ -2,13 +2,6 @@
|
|
|
2
2
|
# https://github.com/craigahobbs/markdown-up/blob/main/LICENSE
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
# Include sentinel
|
|
6
|
-
if systemGlobalGet('unittestMockSentinel'):
|
|
7
|
-
return
|
|
8
|
-
endif
|
|
9
|
-
unittestMockSentinel = true
|
|
10
|
-
|
|
11
|
-
|
|
12
5
|
# Constants
|
|
13
6
|
unittestMockPixelsPerPoint = 4 / 3
|
|
14
7
|
unittestMockDefaultFontSizePx = 12 * unittestMockPixelsPerPoint
|
package/lib/library.js
CHANGED
|
@@ -1987,6 +1987,29 @@ struct SystemFetchRequest
|
|
|
1987
1987
|
`);
|
|
1988
1988
|
|
|
1989
1989
|
|
|
1990
|
+
// System includes object global variable name
|
|
1991
|
+
export const systemGlobalIncludesName = '__bareScriptIncludes';
|
|
1992
|
+
|
|
1993
|
+
|
|
1994
|
+
// $function: systemGlobalIncludesGet
|
|
1995
|
+
// $group: System
|
|
1996
|
+
// $doc: Get the global system includes object
|
|
1997
|
+
// $return: The global system includes object
|
|
1998
|
+
function systemGlobalIncludesGet(unusedArgs, options) {
|
|
1999
|
+
const globals = (options !== null ? (options.globals ?? null) : null);
|
|
2000
|
+
return (globals !== null ? (globals[systemGlobalIncludesName] ?? null) : null);
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
|
|
2004
|
+
// $function: systemGlobalIncludesName
|
|
2005
|
+
// $group: System
|
|
2006
|
+
// $doc: Get the system includes object global variable name
|
|
2007
|
+
// $return: The system includes object global variable name
|
|
2008
|
+
function systemGlobalIncludesNameFn() {
|
|
2009
|
+
return systemGlobalIncludesName;
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
|
|
1990
2013
|
// $function: systemGlobalGet
|
|
1991
2014
|
// $group: System
|
|
1992
2015
|
// $doc: Get a global variable value
|
|
@@ -2248,6 +2271,8 @@ export const scriptFunctions = {
|
|
|
2248
2271
|
systemCompare,
|
|
2249
2272
|
systemFetch,
|
|
2250
2273
|
systemGlobalGet,
|
|
2274
|
+
systemGlobalIncludesGet,
|
|
2275
|
+
'systemGlobalIncludesName': systemGlobalIncludesNameFn,
|
|
2251
2276
|
systemGlobalSet,
|
|
2252
2277
|
systemIs,
|
|
2253
2278
|
systemLog,
|
package/lib/model.js
CHANGED
|
@@ -320,9 +320,10 @@ export function validateExpression(expr) {
|
|
|
320
320
|
* Lint a BareScript script model
|
|
321
321
|
*
|
|
322
322
|
* @param {Object} script - The [BareScript model](./model/#var.vName='BareScript')
|
|
323
|
+
* @param {?Object} globals - The script global variables
|
|
323
324
|
* @returns {string[]} The array of lint warnings
|
|
324
325
|
*/
|
|
325
|
-
export function lintScript(script) {
|
|
326
|
+
export function lintScript(script, globals = null) {
|
|
326
327
|
const warnings = [];
|
|
327
328
|
const {statements} = script;
|
|
328
329
|
|
|
@@ -341,6 +342,15 @@ export function lintScript(script) {
|
|
|
341
342
|
}
|
|
342
343
|
}
|
|
343
344
|
|
|
345
|
+
// Unknown global variable?
|
|
346
|
+
if (globals !== null) {
|
|
347
|
+
for (const varName of Object.keys(varUses).sort()) {
|
|
348
|
+
if (!(varName in varAssigns) && !(varName in globals) && !builtinGlobals.has(varName)) {
|
|
349
|
+
lintScriptWarning(warnings, script, statements[varUses[varName]], `Unknown global variable "${varName}"`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
344
354
|
// Iterate global statements
|
|
345
355
|
const functionsDefined = {};
|
|
346
356
|
const labelsDefined = {};
|
|
@@ -361,7 +371,8 @@ export function lintScript(script) {
|
|
|
361
371
|
const fnVarAssigns = {};
|
|
362
372
|
const fnVarUses = {};
|
|
363
373
|
const args = (statement.function.args ?? null);
|
|
364
|
-
|
|
374
|
+
const fnStatements = statement.function.statements;
|
|
375
|
+
getVariableAssignmentsAndUses(fnStatements, fnVarAssigns, fnVarUses);
|
|
365
376
|
for (const varName of Object.keys(fnVarAssigns)) {
|
|
366
377
|
// Ignore re-assigned function arguments
|
|
367
378
|
if (args !== null && args.indexOf(varName) !== -1) {
|
|
@@ -369,7 +380,7 @@ export function lintScript(script) {
|
|
|
369
380
|
}
|
|
370
381
|
if (varName in fnVarUses && fnVarUses[varName] <= fnVarAssigns[varName]) {
|
|
371
382
|
lintScriptWarning(
|
|
372
|
-
warnings, script,
|
|
383
|
+
warnings, script, fnStatements[fnVarUses[varName]],
|
|
373
384
|
`Variable "${varName}" of function "${statement.function.name}" used before assignment`
|
|
374
385
|
);
|
|
375
386
|
}
|
|
@@ -379,12 +390,24 @@ export function lintScript(script) {
|
|
|
379
390
|
for (const varName of Object.keys(fnVarAssigns)) {
|
|
380
391
|
if (!(varName in fnVarUses)) {
|
|
381
392
|
lintScriptWarning(
|
|
382
|
-
warnings, script,
|
|
393
|
+
warnings, script, fnStatements[fnVarAssigns[varName]],
|
|
383
394
|
`Unused variable "${varName}" defined in function "${statement.function.name}"`
|
|
384
395
|
);
|
|
385
396
|
}
|
|
386
397
|
}
|
|
387
398
|
|
|
399
|
+
// Unknown global variable?
|
|
400
|
+
if (globals !== null) {
|
|
401
|
+
for (const varName of Object.keys(fnVarUses).sort()) {
|
|
402
|
+
if (!(varName in fnVarAssigns) && (args === null || args.indexOf(varName) === -1) &&
|
|
403
|
+
!(varName in globals) && !builtinGlobals.has(varName)) {
|
|
404
|
+
lintScriptWarning(
|
|
405
|
+
warnings, script, fnStatements[fnVarUses[varName]], `Unknown global variable "${varName}"`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
388
411
|
// Function argument checks
|
|
389
412
|
if (args !== null) {
|
|
390
413
|
const argsDefined = new Set();
|
|
@@ -410,7 +433,7 @@ export function lintScript(script) {
|
|
|
410
433
|
// Iterate function statements
|
|
411
434
|
const fnLabelsDefined = {};
|
|
412
435
|
const fnLabelsUsed = {};
|
|
413
|
-
for (const [ixFnStatement, fnStatement] of
|
|
436
|
+
for (const [ixFnStatement, fnStatement] of fnStatements.entries()) {
|
|
414
437
|
const [fnStatementKey] = Object.keys(fnStatement);
|
|
415
438
|
|
|
416
439
|
// Function expression statement checks
|
|
@@ -498,6 +521,10 @@ export function lintScript(script) {
|
|
|
498
521
|
}
|
|
499
522
|
|
|
500
523
|
|
|
524
|
+
// Builtin global variable names
|
|
525
|
+
const builtinGlobals = new Set(['false', 'if', 'null', 'true']);
|
|
526
|
+
|
|
527
|
+
|
|
501
528
|
// Helper to format static analysis warnings
|
|
502
529
|
function lintScriptWarning(warnings, script, statement, message) {
|
|
503
530
|
const scriptName = script.scriptName ?? '';
|
package/lib/runtimeAsync.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import {BareScriptRuntimeError, evaluateExpression, recordStatementCoverage, scriptFunction} from './runtime.js';
|
|
7
7
|
import {ValueArgsError, valueBoolean, valueCompare, valueString} from './value.js';
|
|
8
|
-
import {coverageGlobalName, defaultMaxStatements, expressionFunctions, scriptFunctions} from './library.js';
|
|
8
|
+
import {coverageGlobalName, defaultMaxStatements, expressionFunctions, scriptFunctions, systemGlobalIncludesName} from './library.js';
|
|
9
9
|
import {lintScript} from './model.js';
|
|
10
10
|
import {parseScript} from './parser.js';
|
|
11
11
|
import {urlFileRelative} from './options.js';
|
|
@@ -123,9 +123,9 @@ async function executeScriptHelperAsync(script, statements, options, locals) {
|
|
|
123
123
|
|
|
124
124
|
// Include?
|
|
125
125
|
} else if (statementKey === 'include') {
|
|
126
|
-
//
|
|
126
|
+
// Compute the include script URLs
|
|
127
127
|
const urlFn = options.urlFn ?? null;
|
|
128
|
-
const
|
|
128
|
+
const unfilteredIncludeURLs = statement.include.includes.map(({url, system = false}) => {
|
|
129
129
|
let includeURL;
|
|
130
130
|
if (system && 'systemPrefix' in options) {
|
|
131
131
|
includeURL = urlFileRelative(options.systemPrefix, url);
|
|
@@ -134,6 +134,21 @@ async function executeScriptHelperAsync(script, statements, options, locals) {
|
|
|
134
134
|
}
|
|
135
135
|
return {includeURL, 'systemInclude': system};
|
|
136
136
|
});
|
|
137
|
+
|
|
138
|
+
// Filter already included
|
|
139
|
+
let globalIncludes = globals[systemGlobalIncludesName] ?? null;
|
|
140
|
+
if (globalIncludes === null || typeof globalIncludes !== 'object') {
|
|
141
|
+
globalIncludes = {};
|
|
142
|
+
globals[systemGlobalIncludesName] = globalIncludes;
|
|
143
|
+
}
|
|
144
|
+
const includeURLs = unfilteredIncludeURLs.filter(({includeURL}) => {
|
|
145
|
+
if (globalIncludes[includeURL]) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return true;
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
// Fetch the include script text
|
|
137
152
|
const responses = await Promise.all(includeURLs.map(async ({includeURL, systemInclude}) => {
|
|
138
153
|
try {
|
|
139
154
|
const response = ('fetchFn' in options ? await options.fetchFn(includeURL) : null);
|
|
@@ -160,6 +175,12 @@ async function executeScriptHelperAsync(script, statements, options, locals) {
|
|
|
160
175
|
throw new BareScriptRuntimeError(script, statement, `Include of "${includeURL}" failed`);
|
|
161
176
|
}
|
|
162
177
|
|
|
178
|
+
// Mark as included. Check again if the URL is included.
|
|
179
|
+
if (globalIncludes[includeURL]) {
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
globalIncludes[includeURL] = true;
|
|
183
|
+
|
|
163
184
|
// Parse the include script
|
|
164
185
|
const includeScript = parseScript(includeText, 1, includeURL);
|
|
165
186
|
if (systemInclude) {
|