Added Swagger
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
A guide to custom patterns used in the JSON Schema validation documents in this folder...
|
||||
|
||||
### Pivot key existential switch
|
||||
|
||||
Applies a schema to an object based on whether a key exists in an object.
|
||||
|
||||
> "If key A exists on the object, apply schema X. Else, apply schema Y."
|
||||
|
||||
```yaml
|
||||
switch:
|
||||
- if:
|
||||
required: [a]
|
||||
then:
|
||||
description: schema X; within `then` can be any JSON Schema content
|
||||
- then:
|
||||
description: schema Y; within `then` can be any JSON Schema content
|
||||
```
|
||||
|
||||
### Pivot key value switch
|
||||
|
||||
Applies a schema to an object based on the value of a specific, always-required key (the "pivot key").
|
||||
|
||||
> "If key A is foo, apply schema X. Else, if key A is bar, apply schema Y. Else, tell the user that key A must be foo or bar."
|
||||
|
||||
- The pivot key must be `required` in each `if` block, otherwise the switch may generate a false positive for the entire object when the key isn't provided at all.
|
||||
- The default case (the last one, with `then` but no `if`) must always require the pivot key's presence and report all possible values back as an enum, otherwise a misleading error message may be shown to the user.
|
||||
|
||||
```yaml
|
||||
switch:
|
||||
- if:
|
||||
required: [a]
|
||||
properties: { a: { enum: [foo] } }
|
||||
then:
|
||||
description: schema X; within `then` can be any JSON Schema content
|
||||
- if:
|
||||
required: [a]
|
||||
properties: { a: { enum: [bar] } }
|
||||
then:
|
||||
description: schema Y; within `then` can be any JSON Schema content
|
||||
- then:
|
||||
description: fallback schema; ensures the user is told the pivot key is needed and should have one of the enumerated values
|
||||
required: [a]
|
||||
properties: { a: { enum: [foo, bar] } }
|
||||
```
|
||||
@@ -0,0 +1,176 @@
|
||||
// JSON-Schema ( draf04 ) validator
|
||||
import JsonSchemaWebWorker from "./validator.worker.js"
|
||||
import YAML from "js-yaml"
|
||||
import PromiseWorker from "promise-worker"
|
||||
import debounce from "lodash/debounce"
|
||||
import swagger2SchemaYaml from "./swagger2-schema.yaml"
|
||||
import oas3SchemaYaml from "./oas3-schema.yaml"
|
||||
|
||||
const swagger2Schema = YAML.safeLoad(swagger2SchemaYaml)
|
||||
const oas3Schema = YAML.safeLoad(oas3SchemaYaml)
|
||||
|
||||
// Lazily created promise worker
|
||||
let _promiseWorker
|
||||
const promiseWorker = () => {
|
||||
if (!_promiseWorker)
|
||||
_promiseWorker = new PromiseWorker(new JsonSchemaWebWorker())
|
||||
return _promiseWorker
|
||||
}
|
||||
|
||||
export const addSchema = (schema, schemaPath = []) => () => {
|
||||
promiseWorker().postMessage({
|
||||
type: "add-schema",
|
||||
payload: {
|
||||
schemaPath,
|
||||
schema
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Figure out what schema we need to use ( we're making provision to be able to do sub-schema validation later on)
|
||||
// ...for now we just pick which base schema to use (eg: openapi-2-0, openapi-3.0, etc)
|
||||
export const getSchemaBasePath = () => ({ specSelectors }) => {
|
||||
// Eg: [openapi-3.0] or [openapi-2-0]
|
||||
// later on... ["openapi-2.0", "paths", "get"]
|
||||
const isOAS3 = specSelectors.isOAS3 ? specSelectors.isOAS3() : false
|
||||
const isSwagger2 = specSelectors.isSwagger2
|
||||
? specSelectors.isSwagger2()
|
||||
: false
|
||||
const isAmbiguousVersion = isOAS3 && isSwagger2
|
||||
|
||||
// Refuse to handle ambiguity
|
||||
if (isAmbiguousVersion) return []
|
||||
|
||||
if (isSwagger2) return ["openapi-2.0"]
|
||||
|
||||
if (isOAS3) return ["openapi-3.0"]
|
||||
}
|
||||
|
||||
export const setup = () => ({ jsonSchemaValidatorActions }) => {
|
||||
// Add schemas , once off
|
||||
jsonSchemaValidatorActions.addSchema(swagger2Schema, ["openapi-2.0"])
|
||||
jsonSchemaValidatorActions.addSchema(oas3Schema, ["openapi-3.0"])
|
||||
}
|
||||
|
||||
export const validate = ({ spec, path = [], ...rest }) => system => {
|
||||
// stagger clearing errors, in case there is another debounced validation
|
||||
// run happening, which can occur when the user's typing cadence matches
|
||||
// the latency of validation
|
||||
// TODO: instead of using a timeout, be aware of any pending validation
|
||||
// promises, and use them to schedule error clearing.
|
||||
setTimeout(() => {
|
||||
system.errActions.clear({
|
||||
source: system.jsonSchemaValidatorSelectors.errSource()
|
||||
})
|
||||
}, 50)
|
||||
system.jsonSchemaValidatorActions.validateDebounced({ spec, path, ...rest })
|
||||
}
|
||||
|
||||
// Create a debounced validate, that is lazy
|
||||
let _debValidate
|
||||
export const validateDebounced = (...args) => system => {
|
||||
// Lazily create one...
|
||||
if (!_debValidate) {
|
||||
_debValidate = debounce((...args) => {
|
||||
system.jsonSchemaValidatorActions.validateImmediate(...args)
|
||||
}, 200)
|
||||
}
|
||||
return _debValidate(...args)
|
||||
}
|
||||
|
||||
export const validateImmediate = ({ spec, path = [] }) => system => {
|
||||
// schemaPath refers to type of schema, and later might refer to sub-schema
|
||||
const baseSchemaPath = system.jsonSchemaValidatorSelectors.getSchemaBasePath()
|
||||
|
||||
// No base path? Then we're unable to do anything...
|
||||
if (!baseSchemaPath.length)
|
||||
throw new Error("Ambiguous schema path, unable to run validation")
|
||||
|
||||
return system.jsonSchemaValidatorActions.validateWithBaseSchema({
|
||||
spec,
|
||||
path: [...baseSchemaPath, ...path]
|
||||
})
|
||||
}
|
||||
|
||||
export const validateWithBaseSchema = ({ spec, path = [] }) => system => {
|
||||
const errSource = system.jsonSchemaValidatorSelectors.errSource()
|
||||
|
||||
return promiseWorker()
|
||||
.postMessage({
|
||||
type: "validate",
|
||||
payload: {
|
||||
jsSpec: spec,
|
||||
specStr: system.specSelectors.specStr(),
|
||||
schemaPath: path,
|
||||
source: errSource
|
||||
}
|
||||
})
|
||||
.then(
|
||||
({ results, path }) => {
|
||||
system.jsonSchemaValidatorActions.handleResults(null, {
|
||||
results,
|
||||
path
|
||||
})
|
||||
},
|
||||
err => {
|
||||
system.jsonSchemaValidatorActions.handleResults(err, {})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
export const handleResults = (err, { results }) => system => {
|
||||
if (err) {
|
||||
// Something bad happened with validation.
|
||||
throw err
|
||||
}
|
||||
|
||||
system.errActions.clear({
|
||||
source: system.jsonSchemaValidatorSelectors.errSource()
|
||||
})
|
||||
|
||||
if (!Array.isArray(results)) {
|
||||
results = [results]
|
||||
}
|
||||
|
||||
// Filter out anything funky
|
||||
results = results.filter(val => typeof val === "object" && val !== null)
|
||||
|
||||
if (results.length) {
|
||||
system.errActions.newSpecErrBatch(results)
|
||||
}
|
||||
}
|
||||
|
||||
export default function() {
|
||||
return {
|
||||
afterLoad: system => system.jsonSchemaValidatorActions.setup(),
|
||||
statePlugins: {
|
||||
jsonSchemaValidator: {
|
||||
actions: {
|
||||
addSchema,
|
||||
validate,
|
||||
handleResults,
|
||||
validateDebounced,
|
||||
validateImmediate,
|
||||
validateWithBaseSchema,
|
||||
setup
|
||||
},
|
||||
selectors: {
|
||||
getSchemaBasePath,
|
||||
errSource() {
|
||||
// Used to identify the errors generated by this plugin
|
||||
return "structural"
|
||||
}
|
||||
}
|
||||
},
|
||||
spec: {
|
||||
wrapActions: {
|
||||
validateSpec: (ori, system) => (...args) => {
|
||||
ori(...args)
|
||||
const [spec, path] = args
|
||||
system.jsonSchemaValidatorActions.validate({ spec, path })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,25 @@
|
||||
import "src/polyfills"
|
||||
import registerPromiseWorker from "promise-worker/register"
|
||||
import Validator from "./validator"
|
||||
|
||||
const validator = new Validator()
|
||||
|
||||
registerPromiseWorker(({ type, payload }) => {
|
||||
if (type == "add-schema") {
|
||||
const { schema, schemaPath } = payload
|
||||
validator.addSchema(schema, schemaPath)
|
||||
return
|
||||
}
|
||||
|
||||
if (type == "validate") {
|
||||
const { jsSpec, specStr, schemaPath, source } = payload
|
||||
let validationResults = validator.validate({
|
||||
jsSpec,
|
||||
specStr,
|
||||
schemaPath,
|
||||
source
|
||||
})
|
||||
|
||||
return { results: validationResults }
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,118 @@
|
||||
// Error condenser!
|
||||
//
|
||||
// 1. group all errors by path
|
||||
// 2. score them by message frequency
|
||||
// 3. select the most frequent messages (ties retain all equally-frequent messages)
|
||||
// 4. concatenate the params of each occurrence of the most frequent message
|
||||
// 5. create one condensed error for the path
|
||||
// 6. return all condensed errors as an array
|
||||
|
||||
export function condenseErrors(errors) {
|
||||
if (!Array.isArray(errors)) {
|
||||
return []
|
||||
}
|
||||
|
||||
const tree = {}
|
||||
|
||||
function countFor(dataPath, message) {
|
||||
return tree[dataPath][message].length
|
||||
}
|
||||
|
||||
errors.forEach(err => {
|
||||
const { dataPath, message } = err
|
||||
|
||||
if (tree[dataPath] && tree[dataPath][message]) {
|
||||
tree[dataPath][message].push(err)
|
||||
} else if (tree[dataPath]) {
|
||||
tree[dataPath][message] = [err]
|
||||
} else {
|
||||
tree[dataPath] = {
|
||||
[message]: [err]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const dataPaths = Object.keys(tree)
|
||||
|
||||
return dataPaths.reduce((res, path) => {
|
||||
const messages = Object.keys(tree[path])
|
||||
|
||||
const mostFrequentMessageNames = messages.reduce(
|
||||
(obj, msg) => {
|
||||
const count = countFor(path, msg)
|
||||
|
||||
if (count > obj.max) {
|
||||
return {
|
||||
messages: [msg],
|
||||
max: count
|
||||
}
|
||||
} else if (count === obj.max) {
|
||||
obj.messages.push(msg)
|
||||
return obj
|
||||
} else {
|
||||
return obj
|
||||
}
|
||||
},
|
||||
{ max: 0, messages: [] }
|
||||
).messages
|
||||
|
||||
const mostFrequentMessages = mostFrequentMessageNames.map(
|
||||
name => tree[path][name]
|
||||
)
|
||||
|
||||
const condensedErrors = mostFrequentMessages.map(messages => {
|
||||
return messages.reduce((prev, err) => {
|
||||
const obj = Object.assign({}, prev, {
|
||||
params: mergeParams(prev.params, err.params)
|
||||
})
|
||||
|
||||
if (!prev.params && !err.params) {
|
||||
delete obj.params
|
||||
}
|
||||
return obj
|
||||
})
|
||||
})
|
||||
|
||||
return res.concat(condensedErrors)
|
||||
}, [])
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
function mergeParams(objA = {}, objB = {}) {
|
||||
if (!objA && !objB) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const res = {}
|
||||
|
||||
for (let k in objA) {
|
||||
if (Object.prototype.hasOwnProperty.call(objA, k)) {
|
||||
res[k] = arrayify(objA[k])
|
||||
}
|
||||
}
|
||||
|
||||
for (let k in objB) {
|
||||
if (Object.prototype.hasOwnProperty.call(objB, k)) {
|
||||
if (res[k]) {
|
||||
const curr = res[k]
|
||||
res[k] = curr.concat(arrayify(objB[k]))
|
||||
} else {
|
||||
res[k] = arrayify(objB[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
function arrayify(thing) {
|
||||
if (thing === undefined || thing === null) {
|
||||
return thing
|
||||
}
|
||||
if (Array.isArray(thing)) {
|
||||
return thing
|
||||
} else {
|
||||
return [thing]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import Ajv from "ajv"
|
||||
import AjvErrors from "ajv-errors"
|
||||
import AjvKeywords from "ajv-keywords"
|
||||
import { getLineNumberForPath } from "./shared.js"
|
||||
import { condenseErrors } from "./condense-errors.js"
|
||||
import jsonSchema from "./jsonSchema"
|
||||
const IGNORED_AJV_PARAMS = ["type", "errors"]
|
||||
|
||||
export default class JSONSchemaValidator {
|
||||
constructor() {
|
||||
this.ajv = new Ajv({
|
||||
allErrors: true,
|
||||
jsonPointers: true,
|
||||
})
|
||||
|
||||
AjvKeywords(this.ajv, "switch")
|
||||
AjvErrors(this.ajv)
|
||||
|
||||
this.addSchema(jsonSchema)
|
||||
}
|
||||
|
||||
addSchema(schema, key) {
|
||||
this.ajv.addSchema(schema, normalizeKey(key))
|
||||
}
|
||||
|
||||
validate({ jsSpec, specStr, schemaPath, source }) {
|
||||
this.ajv.validate(normalizeKey(schemaPath), jsSpec)
|
||||
|
||||
if (!this.ajv.errors || !this.ajv.errors.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const condensedErrors = condenseErrors(this.ajv.errors)
|
||||
try {
|
||||
const boundGetLineNumber = getLineNumberForPath.bind(null, specStr)
|
||||
|
||||
return condensedErrors.map(err => {
|
||||
let preparedMessage = err.message
|
||||
if (err.params) {
|
||||
preparedMessage += "\n"
|
||||
for (var k in err.params) {
|
||||
if (IGNORED_AJV_PARAMS.indexOf(k) === -1) {
|
||||
const ori = err.params[k]
|
||||
const value = Array.isArray(ori) ? dedupe(ori).join(", ") : ori
|
||||
preparedMessage += `${k}: ${value}\n`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const errorPathArray = jsonPointerStringToArray(err.dataPath)
|
||||
|
||||
return {
|
||||
level: "error",
|
||||
line: boundGetLineNumber(errorPathArray || []),
|
||||
path: errorPathArray,
|
||||
message: preparedMessage.trim(),
|
||||
source,
|
||||
original: err
|
||||
}
|
||||
})
|
||||
}
|
||||
catch (err) {
|
||||
return {
|
||||
level: "error",
|
||||
line: err.problem_mark && err.problem_mark.line + 1 || 0,
|
||||
message: err.problem,
|
||||
source: "parser",
|
||||
original: err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dedupe(arr) {
|
||||
return arr.filter((val, i) => {
|
||||
return arr.indexOf(val) === i
|
||||
})
|
||||
}
|
||||
|
||||
function pathToJSONPointer(arr) {
|
||||
return arr.map(a => (a + "").replace("~", "~0").replace("/", "~1")).join("/")
|
||||
}
|
||||
|
||||
function jsonPointerStringToArray(str) {
|
||||
return str.split("/")
|
||||
.map(part => (part + "").replace(/~0/g, "~").replace(/~1/g, "/"))
|
||||
.filter(str => str.length > 0)
|
||||
}
|
||||
|
||||
// Convert arrays into a string. Safely, by using the JSONPath spec
|
||||
function normalizeKey(key) {
|
||||
if (!Array.isArray(key)) key = [key]
|
||||
return pathToJSONPointer(key)
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
export default {
|
||||
id: "http://json-schema.org/draft-04/schema#",
|
||||
$schema: "http://json-schema.org/draft-04/schema#",
|
||||
description: "Core schema meta-schema",
|
||||
definitions: {
|
||||
schemaArray: {
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
items: { $ref: "#" }
|
||||
},
|
||||
positiveInteger: {
|
||||
type: "integer",
|
||||
minimum: 0
|
||||
},
|
||||
positiveIntegerDefault0: {
|
||||
allOf: [{ $ref: "#/definitions/positiveInteger" }, { default: 0 }]
|
||||
},
|
||||
simpleTypes: {
|
||||
enum: [
|
||||
"array",
|
||||
"boolean",
|
||||
"integer",
|
||||
/* "null", */ // removed per https://github.com/swagger-api/swagger-editor/issues/1832#issuecomment-483717197
|
||||
"number",
|
||||
"object",
|
||||
"string"
|
||||
]
|
||||
},
|
||||
stringArray: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
minItems: 1,
|
||||
uniqueItems: true
|
||||
}
|
||||
},
|
||||
type: "object",
|
||||
properties: {
|
||||
id: {
|
||||
type: "string",
|
||||
format: "uri"
|
||||
},
|
||||
$schema: {
|
||||
type: "string",
|
||||
format: "uri"
|
||||
},
|
||||
title: {
|
||||
type: "string"
|
||||
},
|
||||
description: {
|
||||
type: "string"
|
||||
},
|
||||
default: {},
|
||||
multipleOf: {
|
||||
type: "number",
|
||||
minimum: 0,
|
||||
exclusiveMinimum: true
|
||||
},
|
||||
maximum: {
|
||||
type: "number"
|
||||
},
|
||||
exclusiveMaximum: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
},
|
||||
minimum: {
|
||||
type: "number"
|
||||
},
|
||||
exclusiveMinimum: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
},
|
||||
maxLength: { $ref: "#/definitions/positiveInteger" },
|
||||
minLength: { $ref: "#/definitions/positiveIntegerDefault0" },
|
||||
pattern: {
|
||||
type: "string",
|
||||
format: "regex"
|
||||
},
|
||||
additionalItems: {
|
||||
anyOf: [{ type: "boolean" }, { $ref: "#" }],
|
||||
default: {}
|
||||
},
|
||||
items: {
|
||||
anyOf: [{ $ref: "#" }, { $ref: "#/definitions/schemaArray" }],
|
||||
default: {}
|
||||
},
|
||||
maxItems: { $ref: "#/definitions/positiveInteger" },
|
||||
minItems: { $ref: "#/definitions/positiveIntegerDefault0" },
|
||||
uniqueItems: {
|
||||
type: "boolean",
|
||||
default: false
|
||||
},
|
||||
maxProperties: { $ref: "#/definitions/positiveInteger" },
|
||||
minProperties: { $ref: "#/definitions/positiveIntegerDefault0" },
|
||||
required: { $ref: "#/definitions/stringArray" },
|
||||
additionalProperties: {
|
||||
anyOf: [{ type: "boolean" }, { $ref: "#" }],
|
||||
default: {}
|
||||
},
|
||||
definitions: {
|
||||
type: "object",
|
||||
additionalProperties: { $ref: "#" },
|
||||
default: {}
|
||||
},
|
||||
properties: {
|
||||
type: "object",
|
||||
additionalProperties: { $ref: "#" },
|
||||
default: {}
|
||||
},
|
||||
patternProperties: {
|
||||
type: "object",
|
||||
additionalProperties: { $ref: "#" },
|
||||
default: {}
|
||||
},
|
||||
dependencies: {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
anyOf: [{ $ref: "#" }, { $ref: "#/definitions/stringArray" }]
|
||||
}
|
||||
},
|
||||
enum: {
|
||||
type: "array",
|
||||
minItems: 1,
|
||||
uniqueItems: true
|
||||
},
|
||||
type: {
|
||||
$ref: "#/definitions/simpleTypes"
|
||||
// anyOf: [
|
||||
// { $ref: "#/definitions/simpleTypes" },
|
||||
// {
|
||||
// type: "array",
|
||||
// items: { $ref: "#/definitions/simpleTypes" },
|
||||
// minItems: 1,
|
||||
// uniqueItems: true
|
||||
// }
|
||||
// ]
|
||||
},
|
||||
allOf: { $ref: "#/definitions/schemaArray" },
|
||||
anyOf: { $ref: "#/definitions/schemaArray" },
|
||||
oneOf: { $ref: "#/definitions/schemaArray" },
|
||||
not: { $ref: "#" }
|
||||
},
|
||||
dependencies: {
|
||||
exclusiveMaximum: ["maximum"],
|
||||
exclusiveMinimum: ["minimum"]
|
||||
},
|
||||
default: {}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import get from "lodash/get"
|
||||
|
||||
export function transformPathToArray(property, jsSpec) {
|
||||
if (property.slice(0, 9) === "instance.") {
|
||||
var str = property.slice(9)
|
||||
} else {
|
||||
// eslint-disable-next-line no-redeclare
|
||||
var str = property
|
||||
}
|
||||
|
||||
var pathArr = []
|
||||
|
||||
// replace '.', '["', '"]' separators with pipes
|
||||
str = str.replace(/\.(?![^["]*"\])|(\[\")|(\"\]\.?)/g, "|")
|
||||
|
||||
// handle single quotes as well
|
||||
str = str.replace(/\[\'/g, "|")
|
||||
str = str.replace(/\'\]/g, "|")
|
||||
|
||||
// split on our new delimiter, pipe
|
||||
str = str.split("|")
|
||||
|
||||
str
|
||||
.map(item => {
|
||||
// "key[0]" becomes ["key", "0"]
|
||||
if (item.indexOf("[") > -1) {
|
||||
let index = parseInt(item.match(/\[(.*)\]/)[1])
|
||||
let keyName = item.slice(0, item.indexOf("["))
|
||||
return [keyName, index.toString()]
|
||||
} else {
|
||||
return item
|
||||
}
|
||||
})
|
||||
.reduce(function(a, b) {
|
||||
// flatten!
|
||||
return a.concat(b)
|
||||
}, [])
|
||||
.concat([""]) // add an empty item into the array, so we don't get stuck with something in our buffer below
|
||||
.reduce((buffer, curr) => {
|
||||
let obj = pathArr.length ? get(jsSpec, pathArr) : jsSpec
|
||||
|
||||
if (get(obj, makeAccessArray(buffer, curr))) {
|
||||
if (buffer.length) {
|
||||
pathArr.push(buffer)
|
||||
}
|
||||
if (curr.length) {
|
||||
pathArr.push(curr)
|
||||
}
|
||||
return ""
|
||||
} else {
|
||||
// attach key to buffer
|
||||
return `${buffer}${buffer.length ? "." : ""}${curr}`
|
||||
}
|
||||
}, "")
|
||||
|
||||
if (typeof get(jsSpec, pathArr) !== "undefined") {
|
||||
return pathArr
|
||||
} else {
|
||||
// if our path is not correct (there is no value at the path),
|
||||
// return null
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function makeAccessArray(buffer, curr) {
|
||||
let arr = []
|
||||
|
||||
if (buffer.length) {
|
||||
arr.push(buffer)
|
||||
}
|
||||
|
||||
if (curr.length) {
|
||||
arr.push(curr)
|
||||
}
|
||||
|
||||
return arr
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// export * from './ast.js'
|
||||
// These import/exports are shared code between worker and main bundle.
|
||||
// Putting them here keeps the distiction clear
|
||||
export { getLineNumberForPath } from "../../ast/ast.js"
|
||||
Reference in New Issue
Block a user