diff --git a/src/filters/array.ts b/src/filters/array.ts index 4be965486..8547fb80e 100644 --- a/src/filters/array.ts +++ b/src/filters/array.ts @@ -1,4 +1,4 @@ -import { toArray, argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast } from '../util' +import { toArray, argumentsToValue, toValue, stringify, caseInsensitiveCompare, isArray, isNil, last as arrayLast, isArrayLike } from '../util' import { arrayIncludes, equals, evalToken, isTruthy } from '../render' import { Value, FilterImpl } from '../template' import { Tokenizer } from '../parser' @@ -12,8 +12,8 @@ export const join = argumentsToValue(function (this: FilterImpl, v: any[], arg: this.context.memoryLimit.use(complexity) return array.join(sep) }) -export const last = argumentsToValue((v: any) => isArray(v) ? arrayLast(v) : '') -export const first = argumentsToValue((v: any) => isArray(v) ? v[0] : '') +export const last = argumentsToValue((v: any) => isArrayLike(v) ? arrayLast(v) : '') +export const first = argumentsToValue((v: any) => isArrayLike(v) ? v[0] : '') export const reverse = argumentsToValue(function (this: FilterImpl, v: any[]) { const array = toArray(v) this.context.memoryLimit.use(array.length) diff --git a/src/util/underscore.ts b/src/util/underscore.ts index eb5c75ce8..29c96d982 100644 --- a/src/util/underscore.ts +++ b/src/util/underscore.ts @@ -93,6 +93,10 @@ export function isArray (value: any): value is any[] { return toString.call(value) === '[object Array]' } +export function isArrayLike (value: any): value is any[] { + return value && isNumber(value.length) +} + export function isIterable (value: any): value is Iterable { return isObject(value) && Symbol.iterator in value } diff --git a/test/integration/filters/array.spec.ts b/test/integration/filters/array.spec.ts index ed1e9a44e..f633dc931 100644 --- a/test/integration/filters/array.spec.ts +++ b/test/integration/filters/array.spec.ts @@ -27,13 +27,6 @@ describe('filters/array', function () { return expect(render(src)).rejects.toThrow('expected ":" after filter name, line:1, col:83') }) }) - describe('last', () => { - it('should support last', function () { - const src = '{{ arr | last }}' - const scope = { arr: ['zebra', 'octopus', 'giraffe', 'tiger'] } - return test(src, scope, 'tiger') - }) - }) describe('split', () => { it('should support split', function () { const src = '{% assign my_array = "zebra, octopus, giraffe, tiger" | split: ", " %}' + @@ -263,6 +256,7 @@ describe('filters/array', function () { it('should return 0 for false', () => test('{{ false | size }}', '0')) it('should return 0 for nil', () => test('{{ nil | size }}', '0')) it('should return 0 for undefined', () => test('{{ foo | size }}', '0')) + it('should work for string', () => test('{{ "foo" | size }}', {}, '3')) }) describe('first', function () { it('should support first', () => test( @@ -273,7 +267,7 @@ describe('filters/array', function () { it('should return empty for nil', () => test('{{nil | first}}', '')) it('should return empty for undefined', () => test('{{foo | first}}', '')) it('should return empty for false', () => test('{{false | first}}', '')) - it('should return empty for string', () => test('{{"zebra" | first}}', '')) + it('should work for string', () => test('{{ "foo" | first }}', 'f')) }) describe('last', function () { it('should support last', () => test( @@ -284,7 +278,7 @@ describe('filters/array', function () { it('should return empty for nil', () => test('{{nil | last}}', '')) it('should return empty for undefined', () => test('{{foo | last}}', '')) it('should return empty for false', () => test('{{false | last}}', '')) - it('should return empty for string', () => test('{{"zebra" | last}}', '')) + it('should work for string', () => test('{{ "foo" | last }}', {}, 'o')) }) describe('slice', function () { it('should slice first char by 0', () => test('{{ "Liquid" | slice: 0 }}', 'L'))