Skip to content
Permalink
ffd96b38fb
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
Robert Brignull replace jest with ava
Latest commit 0347b72 May 4, 2020 History
0 contributors

Users who have contributed to this file

222 lines (190 sloc) 5.99 KB
'use strict'
const semver = require('semver')
const pkg = require('../package.json')
const constants = require('./constants')
const object = require('./complexValues/object')
const lineBuilder = require('./lineBuilder')
const formatUtils = require('./formatUtils')
const itemDescriptor = require('./metaDescriptors/item')
const propertyDescriptor = require('./metaDescriptors/property')
const stringDescriptor = require('./primitiveValues/string')
const recursorUtils = require('./recursorUtils')
const themeUtils = require('./themeUtils')
const API_VERSION = 1
const CONCORDANCE_VERSION = pkg.version
const descriptorRegistry = new Map()
const registry = new Map()
class PluginError extends Error {
constructor (message, plugin) {
super(message)
this.name = 'PluginError'
this.plugin = plugin
}
}
class PluginTypeError extends TypeError {
constructor (message, plugin) {
super(message)
this.name = 'PluginTypeError'
this.plugin = plugin
}
}
class UnsupportedApiError extends PluginError {
constructor (plugin) {
super('Plugin requires an unsupported API version', plugin)
this.name = 'UnsupportedApiError'
}
}
class UnsupportedError extends PluginError {
constructor (plugin) {
super('Plugin does not support this version of Concordance', plugin)
this.name = 'UnsupportedError'
}
}
class DuplicateDescriptorTagError extends PluginError {
constructor (tag, plugin) {
super(`Could not add descriptor: tag ${String(tag)} has already been registered`, plugin)
this.name = 'DuplicateDescriptorTagError'
this.tag = tag
}
}
class DuplicateDescriptorIdError extends PluginError {
constructor (id, plugin) {
const printed = typeof id === 'number'
? `0x${id.toString(16).toUpperCase()}`
: String(id)
super(`Could not add descriptor: id ${printed} has already been registered`, plugin)
this.name = 'DuplicateDescriptorIdError'
this.id = id
}
}
function verify (plugin) {
if (typeof plugin.name !== 'string' || !plugin.name) {
throw new PluginTypeError('Plugin must have a `name`', plugin)
}
if (plugin.apiVersion !== API_VERSION) {
throw new UnsupportedApiError(plugin)
}
if ('minimalConcordanceVersion' in plugin) {
if (!semver.valid(plugin.minimalConcordanceVersion)) {
throw new PluginTypeError('If specified, `minimalConcordanceVersion` must be a valid SemVer version', plugin)
}
const range = `>=${plugin.minimalConcordanceVersion}`
if (!semver.satisfies(CONCORDANCE_VERSION, range)) {
throw new UnsupportedError(plugin)
}
}
}
// Selectively expose descriptor tags.
const publicDescriptorTags = Object.freeze({
complexItem: itemDescriptor.complexTag,
primitiveItem: itemDescriptor.primitiveTag,
primitiveProperty: propertyDescriptor.primitiveTag,
string: stringDescriptor.tag
})
// Don't expose `setDefaultGutter()`.
const publicLineBuilder = Object.freeze({
buffer: lineBuilder.buffer,
first: lineBuilder.first,
last: lineBuilder.last,
line: lineBuilder.line,
single: lineBuilder.single,
actual: Object.freeze({
buffer: lineBuilder.actual.buffer,
first: lineBuilder.actual.first,
last: lineBuilder.actual.last,
line: lineBuilder.actual.line,
single: lineBuilder.actual.single
}),
expected: Object.freeze({
buffer: lineBuilder.expected.buffer,
first: lineBuilder.expected.first,
last: lineBuilder.expected.last,
line: lineBuilder.expected.line,
single: lineBuilder.expected.single
})
})
function modifyTheme (descriptor, modifier) {
themeUtils.addModifier(descriptor, modifier)
return descriptor
}
function add (plugin) {
verify(plugin)
const name = plugin.name
if (registry.has(name)) return registry.get(name)
const id2deserialize = new Map()
const tag2id = new Map()
const addDescriptor = (id, tag, deserialize) => {
if (id2deserialize.has(id)) throw new DuplicateDescriptorIdError(id, plugin)
if (descriptorRegistry.has(tag) || tag2id.has(tag)) throw new DuplicateDescriptorTagError(tag, plugin)
id2deserialize.set(id, deserialize)
tag2id.set(tag, id)
}
const tryDescribeValue = plugin.register({
// Concordance makes assumptions about when AMBIGUOUS occurs. Do not expose
// it to plugins.
UNEQUAL: constants.UNEQUAL,
SHALLOW_EQUAL: constants.SHALLOW_EQUAL,
DEEP_EQUAL: constants.DEEP_EQUAL,
ObjectValue: object.ObjectValue,
DescribedMixin: object.DescribedMixin,
DeserializedMixin: object.DeserializedMixin,
addDescriptor,
applyThemeModifiers: themeUtils.applyModifiers,
descriptorTags: publicDescriptorTags,
lineBuilder: publicLineBuilder,
mapRecursor: recursorUtils.map,
modifyTheme,
wrapFromTheme: formatUtils.wrap
})
const registered = {
id2deserialize,
serializerVersion: plugin.serializerVersion,
name,
tag2id,
theme: plugin.theme || {},
tryDescribeValue
}
registry.set(name, registered)
for (const tag of tag2id.keys()) {
descriptorRegistry.set(tag, registered)
}
return registered
}
exports.add = add
function getDeserializers (plugins) {
return plugins.map(plugin => {
const registered = add(plugin)
return {
id2deserialize: registered.id2deserialize,
name: registered.name,
serializerVersion: registered.serializerVersion
}
})
}
exports.getDeserializers = getDeserializers
function getThemes (plugins) {
return plugins.map(plugin => {
const registered = add(plugin)
return {
name: registered.name,
theme: registered.theme
}
})
}
exports.getThemes = getThemes
function getTryDescribeValues (plugins) {
return plugins.map(plugin => add(plugin).tryDescribeValue)
}
exports.getTryDescribeValues = getTryDescribeValues
function resolveDescriptorRef (tag) {
if (!descriptorRegistry.has(tag)) return null
const registered = descriptorRegistry.get(tag)
return {
id: registered.tag2id.get(tag),
name: registered.name,
serialization: {
serializerVersion: registered.serializerVersion
}
}
}
exports.resolveDescriptorRef = resolveDescriptorRef