core-decorators
Advanced tools
Comparing version 0.0.1-broken to 0.0.1
212
index.js
@@ -0,25 +1,95 @@ | ||
const GENERIC_FUNCTION_ERROR = '{child} does not properly override {parent}'; | ||
const FUNCTION_REGEXP = /^function ([_$a-zA-Z\xA0-\uFFFF][_$a-zA-Z0-9\xA0-\uFFFF]*)?(\([^\)]*\))[\s\S]+$/; | ||
class SyntaxErrorReporter { | ||
key: string; | ||
parent: Function; | ||
child: Function; | ||
// undefined values needed until this babel PR is released: | ||
// https://github.com/babel/babel/pull/1256 | ||
parentKlass = undefined; | ||
childKlass = undefined; | ||
parentDescriptor = undefined; | ||
childDescriptor = undefined; | ||
get key() { | ||
return this.childDescriptor.key; | ||
} | ||
get parentNotation() { | ||
return `${this.parent.constructor.name}#${this.key}`; | ||
return `${this.parentKlass.constructor.name}#${this.parentPropertySignature}`; | ||
} | ||
get childNotation() { | ||
return `${this.child.constructor.name}#${this.key}`; | ||
return `${this.childKlass.constructor.name}#${this.childPropertySignature}`; | ||
} | ||
get parentTopic() { | ||
return this._getTopic(this.parentDescriptor); | ||
} | ||
get childTopic() { | ||
return this._getTopic(this.childDescriptor); | ||
} | ||
_getTopic(descriptor) { | ||
if (descriptor === undefined) { | ||
return null; | ||
} | ||
if ('value' in descriptor) { | ||
return descriptor.value; | ||
} | ||
if ('get' in descriptor) { | ||
return descriptor.get; | ||
} | ||
if ('set' in descriptor) { | ||
return descriptor.set; | ||
} | ||
} | ||
get parentPropertySignature() { | ||
return this._extractTopicSignature(this.parentTopic); | ||
} | ||
get childPropertySignature() { | ||
return this._extractTopicSignature(this.childTopic); | ||
} | ||
_extractTopicSignature(topic) { | ||
switch (typeof topic) { | ||
case 'function': | ||
return this._extractFunctionSignature(topic); | ||
default: | ||
return this.key; | ||
} | ||
} | ||
_extractFunctionSignature(fn) { | ||
return fn | ||
.toString() | ||
.replace( | ||
FUNCTION_REGEXP, | ||
(match, name = this.key, params) => name + params | ||
); | ||
} | ||
constructor(propertyKey, parentKlass, childClass) { | ||
this.key = propertyKey; | ||
this.parent = parentKlass; | ||
this.child = childClass; | ||
constructor(parentKlass, childKlass, parentDescriptor, childDescriptor) { | ||
this.parentKlass = parentKlass; | ||
this.childKlass = childKlass; | ||
this.parentDescriptor = parentDescriptor; | ||
this.childDescriptor = childDescriptor; | ||
} | ||
assert(condition, msg = '') { | ||
if (condition !== true) { | ||
this.error(GENERIC_FUNCTION_ERROR + msg); | ||
} | ||
} | ||
error(msg) { | ||
msg = msg | ||
.replace('{parent}', this.parentNotation) | ||
.replace('{child}', this.childNotation); | ||
// Replace lazily, because they actually might not | ||
// be available in all cases | ||
.replace('{parent}', m => this.parentNotation) | ||
.replace('{child}', m => this.childNotation); | ||
throw new SyntaxError(msg); | ||
@@ -43,20 +113,74 @@ } | ||
function checkDataDescriptors(parent, child, reportor) { | ||
function checkFunctionSignatures(parent: Function, child: Function, reporter) { | ||
reporter.assert(parent.length === child.length); | ||
} | ||
function checkDataDescriptors(parent, child, reporter) { | ||
const parentValueType = typeof parent.value; | ||
const childValueType = typeof child.value; | ||
if (parentValueType === 'undefined' && childValueType === 'undefined') { | ||
// class properties can be any expression, which isn't ran until the | ||
// the instance is created, so we can't reliably get type information | ||
// for them yet (per spec). Perhaps when Babel includes flow-type info | ||
// in runtime? Tried regex solutions, but super hacky and only feasible | ||
// on primitives, which is confusing for usage... | ||
reporter.error(`descriptor values are both undefined. (class properties are are not currently supported)'`); | ||
} | ||
if (parentValueType !== childValueType) { | ||
reportor.error(`Descriptor value types do not match. {parent} is "${parentValueType}", {child} is "${childValueType}"`); | ||
const isFunctionOverUndefined = (childValueType === 'function' && parentValueType === undefined); | ||
// Even though we don't support class properties, this | ||
// will still handle more than just functions, just in case. | ||
// Shadowing an undefined value is an error if the inherited | ||
// value was undefined (usually a class property, not a method) | ||
if (isFunctionOverUndefined || parentValueType !== undefined) { | ||
reporter.error(`value types do not match. {parent} is "${parentValueType}", {child} is "${childValueType}"`); | ||
} | ||
} | ||
// Switch, in preparation for supporting more types | ||
switch (childValueType) { | ||
case 'function': | ||
checkFunctionSignatures(parent.value, child.value, reporter); | ||
break; | ||
default: | ||
reporter.error(`Unexpected error. Please file a bug with: {parent} is "${parentValueType}", {child} is "${childValueType}"`); | ||
break; | ||
} | ||
} | ||
function checkAccessorDescriptors(parent, child, reportor) { | ||
function checkAccessorDescriptors(parent, child, reporter) { | ||
const parentHasGetter = typeof parent.get === 'function'; | ||
const childHasGetter = typeof child.get === 'function'; | ||
const parentHasSetter = typeof parent.set === 'function'; | ||
const childHasSetter = typeof child.set === 'function'; | ||
if (parentHasGetter || childHasGetter) { | ||
if (!parentHasGetter && parentHasSetter) { | ||
reporter.error(`{parent} is setter but {child} is getter`); | ||
} | ||
if (!childHasGetter && childHasSetter) { | ||
reporter.error(`{parent} is getter but {child} is setter`); | ||
} | ||
checkFunctionSignatures(parent.get, child.get, reporter); | ||
} | ||
if (parentHasSetter || childHasSetter) { | ||
if (!parentHasSetter && parentHasGetter) { | ||
reporter.error(`{parent} is getter but {child} is setter`); | ||
} | ||
if (!childHasSetter && childHasGetter) { | ||
reporter.error(`{parent} is setter but {child} is getter`); | ||
} | ||
checkFunctionSignatures(parent.set, child.set, reporter); | ||
} | ||
} | ||
function checkDescriptors(parent, child, reportor) { | ||
if (parent === undefined) { | ||
reportor.error(`{child} marked as @override but {parent} was not defined (perhaps misspelled?)`); | ||
} | ||
function checkDescriptors(parent, child, reporter) { | ||
const parentType = getDescriptorType(parent); | ||
@@ -66,3 +190,3 @@ const childType = getDescriptorType(child); | ||
if (parentType !== childType) { | ||
reportor.error(`Descriptor types do not match. {parent} is "${parentType}", {child} is "${childType}"`); | ||
reporter.error(`descriptor types do not match. {parent} is "${parentType}", {child} is "${childType}"`); | ||
} | ||
@@ -72,7 +196,7 @@ | ||
case 'data': | ||
checkDataDescriptors(parent, child, reportor); | ||
checkDataDescriptors(parent, child, reporter); | ||
break; | ||
case 'accessor': | ||
checkAccessorDescriptors(parent, child, reportor); | ||
checkAccessorDescriptors(parent, child, reporter); | ||
break; | ||
@@ -82,10 +206,36 @@ } | ||
const suggestionTransforms = [ | ||
(key) => key.toLowerCase(), | ||
(key) => key.toUpperCase(), | ||
(key) => key + 's', | ||
(key) => key.slice(0, -1), | ||
(key) => key.slice(1, key.length), | ||
]; | ||
function findPossibleAlternatives(superKlass, key) { | ||
for (const fn of suggestionTransforms) { | ||
const suggestion = fn(key); | ||
if (suggestion in superKlass) { | ||
return suggestion; | ||
} | ||
} | ||
return null; | ||
} | ||
export function override(klass, key, descriptor) { | ||
descriptor.key = key; | ||
const superKlass = Object.getPrototypeOf(klass); | ||
const superDescriptor = Object.getOwnPropertyDescriptor(superKlass, key); | ||
const reportor = new SyntaxErrorReporter(key, superKlass, klass); | ||
checkDescriptors(superDescriptor, descriptor, reportor); | ||
const reporter = new SyntaxErrorReporter(superKlass, klass, superDescriptor, descriptor); | ||
if (superDescriptor === undefined) { | ||
const suggestedKey = findPossibleAlternatives(superKlass, key); | ||
const suggestion = suggestedKey ? `\n\n Did you mean "${suggestedKey}"?` : ''; | ||
reporter.error(`No descriptor matching {child} was found on the prototype chain.${suggestion}`); | ||
} | ||
checkDescriptors(superDescriptor, descriptor, reporter); | ||
return descriptor; | ||
} | ||
} |
{ | ||
"name": "core-decorators", | ||
"version": "0.0.1-broken", | ||
"version": "0.0.1", | ||
"description": "Library of ES7 decorators inspired by languages that come with built-ins like @override, @deprecated, etc", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
# core-decorators.js | ||
Library of ES7 decorators inspired by languages that come with built-ins like @override, @deprecated, etc | ||
Library of ES7 decorators inspired by languages that come with built-ins like @override, @deprecated, etc | ||
@@ -16,5 +16,14 @@ The idea is these decorators would be used to ensure code sanity, but would be removed in production builds via a Babel plugin. | ||
kickDog() {} | ||
// SyntaxError: Parent#kickDog has two parameters but Child#kickDog has none | ||
// > Parent#kickDog(first, second) | ||
// SyntaxError: Child#kickDog() does not properly override Parent#kickDog(first, second) | ||
} | ||
// or | ||
class Child extends Parent { | ||
@override | ||
kickDogs() {} | ||
// SyntaxError: No descriptor matching Child#kickDogs() was found on the prototype chain. | ||
// | ||
// Did you mean "kickDog"? | ||
} | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Deprecated
MaintenanceThe maintainer of the package marked it as deprecated. This could indicate that a single version should not be used, or that the package is no longer maintained and any new vulnerabilities will not be fixed.
Found 1 instance in 1 package
9659
193
29