Skip to content

Commit

Permalink
feat: add validateIdString() function, used to ensure that id, classe…
Browse files Browse the repository at this point in the history
…s and layers doesn't contain any invalid characters
  • Loading branch information
nytamin committed Sep 30, 2022
1 parent cd31f34 commit ecd0ae7
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 4 deletions.
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './api/enums'
export * from './api/api'
export { Resolver } from './resolver/resolver'
export { validateTimeline, validateObject, validateKeyframe } from './resolver/validate'
export { validateTimeline, validateObject, validateKeyframe, validateIdString } from './resolver/validate'
52 changes: 51 additions & 1 deletion src/resolver/__tests__/validate.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { validateObject, validateKeyframe, validateTimeline } from '../validate'
import { validateObject, validateKeyframe, validateTimeline, validateIdString } from '../validate'
import { TimelineObject, TimelineKeyframe, TimelineEnable } from '../../api/api'
import _ = require('underscore')

Expand Down Expand Up @@ -283,4 +283,54 @@ describe('validate', () => {
validateTimeline(tl, false)
}).toThrowError()
})
test('validateIdString', () => {
expect(() => validateIdString('')).not.toThrowError()
expect(() => validateIdString('test')).not.toThrowError()
expect(() => validateIdString('abcABC123_')).not.toThrowError()
expect(() => validateIdString('_¤"\'£€\\,;:¨~')).not.toThrowError()

expect(() => validateIdString('test-1')).toThrowError()
expect(() => validateIdString('test+1')).toThrowError()
expect(() => validateIdString('test/1')).toThrowError()
expect(() => validateIdString('test*1')).toThrowError()
expect(() => validateIdString('test%1')).toThrowError()
expect(() => validateIdString('test&1')).toThrowError()
expect(() => validateIdString('test|1')).toThrowError()
expect(() => validateIdString('test!')).toThrowError()
expect(() => validateIdString('test(')).toThrowError()
expect(() => validateIdString('test)')).toThrowError()
expect(() => validateIdString('#test')).toThrowError() // a reference to an object id
expect(() => validateIdString('.test')).toThrowError() // a reference to an object class
expect(() => validateIdString('$test')).toThrowError() // a reference to an object layer

// These aren't currently in use anywhere, but might be so in the future:
expect(() => validateIdString('test§', true)).toThrowError()
expect(() => validateIdString('test^', true)).toThrowError()
expect(() => validateIdString('test?', true)).toThrowError()
expect(() => validateIdString('test=', true)).toThrowError()
expect(() => validateIdString('test{', true)).toThrowError()
expect(() => validateIdString('test}', true)).toThrowError()
expect(() => validateIdString('test[', true)).toThrowError()
expect(() => validateIdString('test]', true)).toThrowError()
})
test('invalid id-strings', () => {
expect(() => {
const tl = _.clone(timeline)
tl[0].id = 'obj-1'
validateTimeline(tl, false)
}).toThrowError()

expect(() => {
const tl = _.clone(timeline)

tl[0].classes = ['class-1']
validateTimeline(tl, false)
}).toThrowError()
expect(() => {
const tl = _.clone(timeline)

tl[0].layer = 'layer-1'
validateTimeline(tl, false)
}).toThrowError()
})
})
4 changes: 2 additions & 2 deletions src/resolver/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { isNumeric, isConstant, cacheResult } from '../lib'

export const OPERATORS = ['&', '|', '+', '-', '*', '/', '%', '!']

const REGEXP_OPERATORS = _.map(OPERATORS, (o) => '\\' + o).join('')
export const REGEXP_OPERATORS = new RegExp('([' + _.map(OPERATORS, (o) => '\\' + o).join('') + '\\(\\)])', 'g')

export function interpretExpression(expression: null): null
export function interpretExpression(expression: number): number
Expand All @@ -18,7 +18,7 @@ export function interpretExpression(expression: Expression): Expression {
return cacheResult(
expressionString,
() => {
const expr = expressionString.replace(new RegExp('([' + REGEXP_OPERATORS + '\\(\\)])', 'g'), ' $1 ') // Make sure there's a space between every operator & operand
const expr = expressionString.replace(REGEXP_OPERATORS, ' $1 ') // Make sure there's a space between every operator & operand

const words: Array<string> = _.compact(expr.split(' '))

Expand Down
43 changes: 43 additions & 0 deletions src/resolver/validate.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* tslint:disable:strict-type-predicates */
import { TimelineObject, TimelineKeyframe, TimelineEnable } from '../api/api'
import _ = require('underscore')
import { REGEXP_OPERATORS } from './expression'

interface Ids {
[id: string]: true
Expand Down Expand Up @@ -143,3 +144,45 @@ export function validateObject(obj: TimelineObject, strict?: boolean): void {
export function validateKeyframe(keyframe: TimelineKeyframe, strict?: boolean): void {
validateKeyframe0(keyframe, strict)
}

/** These characters are reserved and cannot be used in ids, etc */
const RESERVED_CHARACTERS = /[#.$]/

/** These characters are reserved for possible future use and cannot be used in ids, etc */
const FUTURE_RESERVED_CHARACTERS = /[=?@{}[\]^§]/

/**
* Validates a string that is used in Timeline as a reference (an id, a class or layer)
* @param str The string to validate
* @param strict Set to true to enable some strict rules (rules that can possibly be ignored)
*/
export function validateIdString(str: string, strict?: boolean): void {
if (!str) return
{
const m = str.match(REGEXP_OPERATORS)
if (m) {
throw new Error(
`The string "${str}" contains a character ("${m[1]}") which isn't allowed in Timeline (is an operator)`
)
}
}
{
const m = str.match(RESERVED_CHARACTERS)
if (m) {
throw new Error(
`The string "${str}" contains a character ("${m[1]}") which isn't allowed in Timeline (is a reserved character)`
)
}
}
if (strict) {
// Also check a few characters that are technically allowed today, but *might* become used in future versions of Timeline:
{
const m = str.match(FUTURE_RESERVED_CHARACTERS)
if (m) {
throw new Error(
`The string "${str}" contains a character ("${m[0]}") which isn't allowed in Timeline (is an reserved character and might be used in the future)`
)
}
}
}
}

0 comments on commit ecd0ae7

Please sign in to comment.