diff --git a/src/utils/isValidIdentifier.ts b/src/utils/isValidIdentifier.ts new file mode 100644 index 0000000000..d129e1bd9b --- /dev/null +++ b/src/utils/isValidIdentifier.ts @@ -0,0 +1,11 @@ +import { isIdentifierStart, isIdentifierChar } from 'acorn'; + +export default function isValidIdentifier(str) { + if (!isIdentifierStart(str.charCodeAt(0), true)) return false; + + for (let i = 0; i < str.length; i += 1) { + if (!isIdentifierChar(str.charCodeAt(i), true)) return false; + } + + return true; +} \ No newline at end of file diff --git a/src/validate/js/propValidators/computed.ts b/src/validate/js/propValidators/computed.ts index 9ee4ed5ebe..94a7212881 100644 --- a/src/validate/js/propValidators/computed.ts +++ b/src/validate/js/propValidators/computed.ts @@ -1,6 +1,8 @@ import checkForDupes from '../utils/checkForDupes'; import checkForComputedKeys from '../utils/checkForComputedKeys'; -import checkForValidIdentifiers from '../utils/checkForValidIdentifiers'; +import getName from '../../../utils/getName'; +import isValidIdentifier from '../../../utils/isValidIdentifier'; +import reservedNames from '../../../utils/reservedNames'; import { Validator } from '../../'; import { Node } from '../../../interfaces'; import walkThroughTopFunctionScope from '../../../utils/walkThroughTopFunctionScope'; @@ -21,9 +23,25 @@ export default function computed(validator: Validator, prop: Node) { checkForDupes(validator, prop.value.properties); checkForComputedKeys(validator, prop.value.properties); - checkForValidIdentifiers(validator, prop.value.properties); prop.value.properties.forEach((computation: Node) => { + const name = getName(computation.key); + + if (!isValidIdentifier(name)) { + const suggestion = name.replace(/[^_$a-z0-9]/ig, '_').replace(/^\d/, '_$&'); + validator.error( + `Computed property name '${name}' is invalid — must be a valid identifier such as ${suggestion}`, + computation.start + ); + } + + if (reservedNames.has(name)) { + validator.error( + `Computed property name '${name}' is invalid — cannot be a JavaScript reserved word`, + computation.start + ); + } + if (!isFunctionExpression.has(computation.value.type)) { validator.error( `Computed properties can be function expressions or arrow function expressions`, diff --git a/src/validate/js/utils/checkForValidIdentifiers.ts b/src/validate/js/utils/checkForValidIdentifiers.ts deleted file mode 100644 index 3b9968f902..0000000000 --- a/src/validate/js/utils/checkForValidIdentifiers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Validator } from '../../'; -import { Node } from '../../../interfaces'; -import getName from '../../../utils/getName'; -import { parse } from 'acorn'; - -export default function checkForValidIdentifiers( - validator: Validator, - properties: Node[] -) { - properties.forEach(prop => { - const name = getName(prop.key); - const functionDefinitionString = `function ${name}() {}`; - try { - parse(functionDefinitionString); - } catch (exception) { - const invalidCharacter = functionDefinitionString[exception.pos]; - validator.error( - `Computed property name "${name}" is invalid. Character '${ - invalidCharacter - }' at position ${exception.pos} is illegal in function identifiers`, - prop.start - ); - } - }); -}