@borgar/fx 4.13.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index-BMr6cTgc.d.cts +1444 -0
- package/dist/index-BMr6cTgc.d.ts +1444 -0
- package/dist/index.cjs +3054 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2984 -0
- package/dist/index.js.map +1 -0
- package/dist/xlsx/index.cjs +3120 -0
- package/dist/xlsx/index.cjs.map +1 -0
- package/dist/xlsx/index.d.cts +55 -0
- package/dist/xlsx/index.d.ts +55 -0
- package/dist/xlsx/index.js +3049 -0
- package/dist/xlsx/index.js.map +1 -0
- package/docs/API.md +2959 -718
- package/docs/AST_format.md +2 -2
- package/eslint.config.mjs +40 -0
- package/lib/a1.spec.ts +32 -0
- package/lib/a1.ts +26 -0
- package/lib/addA1RangeBounds.ts +50 -0
- package/lib/addTokenMeta.spec.ts +166 -0
- package/lib/{addTokenMeta.js → addTokenMeta.ts} +53 -33
- package/lib/astTypes.ts +211 -0
- package/lib/cloneToken.ts +29 -0
- package/lib/{constants.js → constants.ts} +6 -3
- package/lib/fixRanges.spec.ts +220 -0
- package/lib/fixRanges.ts +260 -0
- package/lib/fromCol.spec.ts +15 -0
- package/lib/{fromCol.js → fromCol.ts} +1 -1
- package/lib/index.spec.ts +119 -0
- package/lib/index.ts +76 -0
- package/lib/isNodeType.ts +151 -0
- package/lib/isType.spec.ts +208 -0
- package/lib/{isType.js → isType.ts} +26 -25
- package/lib/lexers/{advRangeOp.js → advRangeOp.ts} +1 -1
- package/lib/lexers/{canEndRange.js → canEndRange.ts} +2 -2
- package/lib/lexers/{lexBoolean.js → lexBoolean.ts} +25 -6
- package/lib/lexers/{lexContext.js → lexContext.ts} +14 -6
- package/lib/lexers/{lexError.js → lexError.ts} +3 -3
- package/lib/lexers/{lexFunction.js → lexFunction.ts} +3 -2
- package/lib/lexers/lexNameFuncCntx.ts +112 -0
- package/lib/lexers/{lexNamed.js → lexNamed.ts} +4 -4
- package/lib/lexers/{lexNewLine.js → lexNewLine.ts} +3 -2
- package/lib/lexers/{lexNumber.js → lexNumber.ts} +4 -3
- package/lib/lexers/{lexOperator.js → lexOperator.ts} +5 -4
- package/lib/lexers/lexRange.ts +15 -0
- package/lib/lexers/{lexRangeA1.js → lexRangeA1.ts} +11 -7
- package/lib/lexers/{lexRangeR1C1.js → lexRangeR1C1.ts} +10 -6
- package/lib/lexers/{lexRangeTrim.js → lexRangeTrim.ts} +3 -2
- package/lib/lexers/{lexRefOp.js → lexRefOp.ts} +4 -3
- package/lib/lexers/{lexString.js → lexString.ts} +3 -3
- package/lib/lexers/{lexStructured.js → lexStructured.ts} +5 -5
- package/lib/lexers/{lexWhitespace.js → lexWhitespace.ts} +3 -2
- package/lib/lexers/sets.ts +51 -0
- package/lib/mergeRefTokens.spec.ts +141 -0
- package/lib/{mergeRefTokens.js → mergeRefTokens.ts} +14 -9
- package/lib/nodeTypes.ts +54 -0
- package/lib/parse.spec.ts +1410 -0
- package/lib/{parser.js → parse.ts} +81 -63
- package/lib/parseA1Range.spec.ts +233 -0
- package/lib/parseA1Range.ts +206 -0
- package/lib/parseA1Ref.spec.ts +337 -0
- package/lib/parseA1Ref.ts +115 -0
- package/lib/parseR1C1Range.ts +191 -0
- package/lib/parseR1C1Ref.spec.ts +323 -0
- package/lib/parseR1C1Ref.ts +127 -0
- package/lib/parseRef.spec.ts +90 -0
- package/lib/parseRef.ts +240 -0
- package/lib/{parseSRange.js → parseSRange.ts} +15 -10
- package/lib/parseStructRef.spec.ts +168 -0
- package/lib/parseStructRef.ts +76 -0
- package/lib/stringifyA1Range.spec.ts +72 -0
- package/lib/stringifyA1Range.ts +72 -0
- package/lib/stringifyA1Ref.spec.ts +64 -0
- package/lib/stringifyA1Ref.ts +59 -0
- package/lib/{stringifyPrefix.js → stringifyPrefix.ts} +17 -2
- package/lib/stringifyR1C1Range.spec.ts +92 -0
- package/lib/stringifyR1C1Range.ts +73 -0
- package/lib/stringifyR1C1Ref.spec.ts +63 -0
- package/lib/stringifyR1C1Ref.ts +67 -0
- package/lib/stringifyStructRef.spec.ts +124 -0
- package/lib/stringifyStructRef.ts +113 -0
- package/lib/stringifyTokens.ts +15 -0
- package/lib/toCol.spec.ts +11 -0
- package/lib/{toCol.js → toCol.ts} +4 -4
- package/lib/tokenTypes.ts +76 -0
- package/lib/tokenize-srefs.spec.ts +429 -0
- package/lib/tokenize.spec.ts +2103 -0
- package/lib/tokenize.ts +346 -0
- package/lib/translate.spec.ts +35 -0
- package/lib/translateToA1.spec.ts +247 -0
- package/lib/translateToA1.ts +231 -0
- package/lib/translateToR1C1.spec.ts +227 -0
- package/lib/translateToR1C1.ts +145 -0
- package/lib/types.ts +179 -0
- package/lib/xlsx/index.spec.ts +27 -0
- package/lib/xlsx/index.ts +32 -0
- package/package.json +45 -31
- package/tsconfig.json +28 -0
- package/typedoc-ignore-links.ts +17 -0
- package/typedoc.json +41 -0
- package/.eslintrc +0 -22
- package/benchmark/benchmark.js +0 -48
- package/benchmark/formulas.json +0 -15677
- package/dist/fx.d.ts +0 -823
- package/dist/fx.js +0 -2
- package/dist/package.json +0 -1
- package/lib/a1.js +0 -348
- package/lib/a1.spec.js +0 -458
- package/lib/addTokenMeta.spec.js +0 -153
- package/lib/astTypes.js +0 -96
- package/lib/extraTypes.js +0 -74
- package/lib/fixRanges.js +0 -104
- package/lib/fixRanges.spec.js +0 -171
- package/lib/fromCol.spec.js +0 -11
- package/lib/index.js +0 -134
- package/lib/index.spec.js +0 -67
- package/lib/isType.spec.js +0 -168
- package/lib/lexer-srefs.spec.js +0 -324
- package/lib/lexer.js +0 -264
- package/lib/lexer.spec.js +0 -1953
- package/lib/lexers/lexRange.js +0 -8
- package/lib/lexers/sets.js +0 -38
- package/lib/mergeRefTokens.spec.js +0 -121
- package/lib/package.json +0 -1
- package/lib/parseRef.js +0 -157
- package/lib/parseRef.spec.js +0 -71
- package/lib/parseStructRef.js +0 -48
- package/lib/parseStructRef.spec.js +0 -164
- package/lib/parser.spec.js +0 -1208
- package/lib/rc.js +0 -341
- package/lib/rc.spec.js +0 -403
- package/lib/stringifyStructRef.js +0 -80
- package/lib/stringifyStructRef.spec.js +0 -182
- package/lib/toCol.spec.js +0 -11
- package/lib/translate-toA1.spec.js +0 -214
- package/lib/translate-toRC.spec.js +0 -197
- package/lib/translate.js +0 -239
- package/lib/translate.spec.js +0 -21
- package/rollup.config.mjs +0 -22
- package/tsd.json +0 -12
package/lib/astTypes.ts
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents the source location information of the node.
|
|
3
|
+
* If the node contains no information about the source location, the field is `null`;
|
|
4
|
+
* otherwise it is an array consisting of a two numbers: A start offset (the position of
|
|
5
|
+
* the first character of the parsed source region) and an end offset (the position of
|
|
6
|
+
* the first character after the parsed source region).
|
|
7
|
+
*/
|
|
8
|
+
export type SourceLocation = number[];
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* All AST nodes are represented by `Node` objects.
|
|
12
|
+
* They may have any prototype inheritance but implement the same basic interface.
|
|
13
|
+
*
|
|
14
|
+
* The `type` field is a string representing the AST variant type.
|
|
15
|
+
* Each subtype of Node is documented below with the specific string of its `type` field.
|
|
16
|
+
* You can use this field to determine which interface a node implements.
|
|
17
|
+
*/
|
|
18
|
+
export type Node = {
|
|
19
|
+
/** The type of this AST node. */
|
|
20
|
+
type: string;
|
|
21
|
+
/** The original source position of the node. */
|
|
22
|
+
loc?: SourceLocation;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* An identifier. These appear on `CallExpression`, `LambdaExpression`, and `LetExpression`
|
|
27
|
+
* and will always be a static string representing the name of a function call or parameter.
|
|
28
|
+
*/
|
|
29
|
+
export type Identifier = {
|
|
30
|
+
/** The type of this AST node. */
|
|
31
|
+
type: 'Identifier';
|
|
32
|
+
/** The original source position of the node. */
|
|
33
|
+
loc?: SourceLocation;
|
|
34
|
+
/** The identifying name. */
|
|
35
|
+
name: string;
|
|
36
|
+
} & Node;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* An identifier for a range or a name.
|
|
40
|
+
*/
|
|
41
|
+
export type ReferenceIdentifier = {
|
|
42
|
+
/** The type of this AST node. */
|
|
43
|
+
type: 'ReferenceIdentifier';
|
|
44
|
+
/** The original source position of the node. */
|
|
45
|
+
loc?: SourceLocation;
|
|
46
|
+
/** The untouched reference value. */
|
|
47
|
+
value: string;
|
|
48
|
+
/** The kind of reference the value holds. */
|
|
49
|
+
kind: 'name' | 'range' | 'beam' | 'table';
|
|
50
|
+
} & Node;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A literal token. Captures numbers, strings, and booleans.
|
|
54
|
+
* Literal errors have their own variant type.
|
|
55
|
+
*/
|
|
56
|
+
export type Literal = {
|
|
57
|
+
/** The type of this AST node. */
|
|
58
|
+
type: 'Literal';
|
|
59
|
+
/** The original source position of the node. */
|
|
60
|
+
loc?: SourceLocation;
|
|
61
|
+
/** The untouched literal source. */
|
|
62
|
+
raw: string;
|
|
63
|
+
/** The value of the literal. */
|
|
64
|
+
value: string | number | boolean;
|
|
65
|
+
} & Node;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* An Error expression.
|
|
69
|
+
*/
|
|
70
|
+
export type ErrorLiteral = {
|
|
71
|
+
/** The type of this AST node. */
|
|
72
|
+
type: 'ErrorLiteral';
|
|
73
|
+
/** The original source position of the node. */
|
|
74
|
+
loc?: SourceLocation;
|
|
75
|
+
/** The untouched literal source. */
|
|
76
|
+
raw: string;
|
|
77
|
+
/** The value of the error. */
|
|
78
|
+
value: string;
|
|
79
|
+
} & Node;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* A unary operator expression.
|
|
83
|
+
*/
|
|
84
|
+
export type UnaryExpression = {
|
|
85
|
+
/** The type of this AST node. */
|
|
86
|
+
type: 'UnaryExpression';
|
|
87
|
+
/** The original source position of the node. */
|
|
88
|
+
loc?: SourceLocation;
|
|
89
|
+
/** The expression's operator. */
|
|
90
|
+
operator: UnaryOperator;
|
|
91
|
+
/** The arguments for the operator. */
|
|
92
|
+
arguments: AstExpression[];
|
|
93
|
+
} & Node;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* A unary operator token.
|
|
97
|
+
*/
|
|
98
|
+
export type UnaryOperator = (
|
|
99
|
+
'+' | '-' | '%' | '#' | '@'
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* A binary operator expression.
|
|
104
|
+
*/
|
|
105
|
+
export type BinaryExpression = {
|
|
106
|
+
/** The type of this AST node. */
|
|
107
|
+
type: 'BinaryExpression';
|
|
108
|
+
/** The original source position of the node. */
|
|
109
|
+
loc?: SourceLocation;
|
|
110
|
+
/** The expression's operator. */
|
|
111
|
+
operator: BinaryOperator;
|
|
112
|
+
/** The arguments for the operator. */
|
|
113
|
+
arguments: AstExpression[];
|
|
114
|
+
} & Node;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A binary operator token.
|
|
118
|
+
*
|
|
119
|
+
* Note that Excels union operator is whitespace so a parser must take care to normalize this to
|
|
120
|
+
* a single space.
|
|
121
|
+
*/
|
|
122
|
+
export type BinaryOperator = (
|
|
123
|
+
'=' | '<' | '>' | '<=' | '>=' | '<>' |
|
|
124
|
+
'-' | '+' | '*' | '/' | '^' |
|
|
125
|
+
':' | ' ' | ',' |
|
|
126
|
+
'&'
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* A function call expression.
|
|
131
|
+
*/
|
|
132
|
+
export type CallExpression = {
|
|
133
|
+
/** The type of this AST node. */
|
|
134
|
+
type: 'CallExpression';
|
|
135
|
+
/** The original source position of the node. */
|
|
136
|
+
loc?: SourceLocation;
|
|
137
|
+
/** The function being called. */
|
|
138
|
+
callee: Identifier;
|
|
139
|
+
/** The arguments for the function. */
|
|
140
|
+
arguments: AstExpression[];
|
|
141
|
+
} & Node;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* An array expression. Excel does not have empty or sparse arrays and restricts array elements to
|
|
145
|
+
* literals. Google Sheets allows `ReferenceIdentifier`s and `CallExpression`s as elements of
|
|
146
|
+
* arrays, the fx parser has options for this but they are off by default.
|
|
147
|
+
*/
|
|
148
|
+
export type ArrayExpression = {
|
|
149
|
+
/** The type of this AST node. */
|
|
150
|
+
type: 'ArrayExpression';
|
|
151
|
+
/** The original source position of the node. */
|
|
152
|
+
loc?: SourceLocation;
|
|
153
|
+
/** The elements of the array. */
|
|
154
|
+
elements: (ReferenceIdentifier | Literal | ErrorLiteral | CallExpression)[][];
|
|
155
|
+
} & Node;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* A LAMBDA expression.
|
|
159
|
+
*/
|
|
160
|
+
export type LambdaExpression = {
|
|
161
|
+
/** The type of this AST node. */
|
|
162
|
+
type: 'LambdaExpression';
|
|
163
|
+
/** The original source position of the node. */
|
|
164
|
+
loc?: SourceLocation;
|
|
165
|
+
/** The LAMBDA's parameters. */
|
|
166
|
+
params: Identifier[];
|
|
167
|
+
/** The LAMBDA's expression. */
|
|
168
|
+
body: AstExpression | null;
|
|
169
|
+
} & Node;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* A LET expression.
|
|
173
|
+
*/
|
|
174
|
+
export type LetExpression = {
|
|
175
|
+
/** The type of this AST node. */
|
|
176
|
+
type: 'LetExpression';
|
|
177
|
+
/** The original source position of the node. */
|
|
178
|
+
loc?: SourceLocation;
|
|
179
|
+
/** The LET's variable declarations. */
|
|
180
|
+
declarations: LetDeclarator[];
|
|
181
|
+
/** The LET's scoped expression. */
|
|
182
|
+
body: AstExpression | null;
|
|
183
|
+
} & Node;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* A LET parameter declaration.
|
|
187
|
+
*/
|
|
188
|
+
export type LetDeclarator = {
|
|
189
|
+
/** The type of this AST node. */
|
|
190
|
+
type: 'LetDeclarator';
|
|
191
|
+
/** The original source position of the node. */
|
|
192
|
+
loc?: SourceLocation;
|
|
193
|
+
/** The name of the variable. */
|
|
194
|
+
id: Identifier;
|
|
195
|
+
/** The variable's initializing expression. */
|
|
196
|
+
init: AstExpression | null;
|
|
197
|
+
} & Node;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Represents an evaluate-able expression.
|
|
201
|
+
*/
|
|
202
|
+
export type AstExpression =
|
|
203
|
+
ReferenceIdentifier |
|
|
204
|
+
Literal |
|
|
205
|
+
ErrorLiteral |
|
|
206
|
+
UnaryExpression |
|
|
207
|
+
BinaryExpression |
|
|
208
|
+
CallExpression |
|
|
209
|
+
ArrayExpression |
|
|
210
|
+
LambdaExpression |
|
|
211
|
+
LetExpression;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Token, TokenEnhanced } from './types.ts';
|
|
2
|
+
|
|
3
|
+
export function cloneToken<T extends Token | TokenEnhanced> (token: T): T {
|
|
4
|
+
// Token
|
|
5
|
+
const newToken: Partial<TokenEnhanced> = {
|
|
6
|
+
type: token.type,
|
|
7
|
+
value: token.value
|
|
8
|
+
};
|
|
9
|
+
if (token.loc) {
|
|
10
|
+
newToken.loc = [ token.loc[0], token.loc[1] ];
|
|
11
|
+
}
|
|
12
|
+
if (token.unterminated != null) {
|
|
13
|
+
newToken.unterminated = token.unterminated;
|
|
14
|
+
}
|
|
15
|
+
// TokenEnhanced
|
|
16
|
+
if (typeof token.index === 'number') {
|
|
17
|
+
newToken.index = token.index;
|
|
18
|
+
if (typeof token.groupId === 'string') {
|
|
19
|
+
newToken.groupId = token.groupId;
|
|
20
|
+
}
|
|
21
|
+
if (typeof token.depth === 'number') {
|
|
22
|
+
newToken.depth = token.depth;
|
|
23
|
+
}
|
|
24
|
+
if (typeof token.error === 'boolean') {
|
|
25
|
+
newToken.error = token.error;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return newToken as T;
|
|
29
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const OPERATOR = 'operator';
|
|
2
|
-
export const OPERATOR_TRIM = 'operator-trim';
|
|
2
|
+
export const OPERATOR_TRIM = 'operator-trim'; // internal only
|
|
3
3
|
export const BOOLEAN = 'bool';
|
|
4
4
|
export const ERROR = 'error';
|
|
5
5
|
export const NUMBER = 'number';
|
|
@@ -29,5 +29,8 @@ export const ARRAY = 'ArrayExpression';
|
|
|
29
29
|
export const IDENTIFIER = 'Identifier';
|
|
30
30
|
export const LET_DECL = 'LetDeclarator';
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
export const
|
|
32
|
+
/** The maximum number of columns a spreadsheet reference may hold (16383). */
|
|
33
|
+
export const MAX_COLS = (2 ** 14) - 1;
|
|
34
|
+
|
|
35
|
+
/** The maximum number of rows a spreadsheet reference may hold (1048575). */
|
|
36
|
+
export const MAX_ROWS = (2 ** 20) - 1;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { describe, test, expect } from 'vitest';
|
|
2
|
+
import { tokenize } from './tokenize.ts';
|
|
3
|
+
import { addTokenMeta } from './addTokenMeta.ts';
|
|
4
|
+
import { fixFormulaRanges, fixFormulaRangesXlsx, fixTokenRanges, fixTokenRangesXlsx } from './fixRanges.ts';
|
|
5
|
+
import { FUNCTION, FX_PREFIX, OPERATOR, REF_RANGE, REF_STRUCT, REF_TERNARY } from './constants.ts';
|
|
6
|
+
|
|
7
|
+
function isFixed (expr: string, expected: string, options: any = {}) {
|
|
8
|
+
const result = options.xlsx
|
|
9
|
+
? fixFormulaRangesXlsx(expr, options)
|
|
10
|
+
: fixFormulaRanges(expr, options);
|
|
11
|
+
expect(result).toBe(expected);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('fixRanges basics', () => {
|
|
15
|
+
test('throws on non-string inputs', () => {
|
|
16
|
+
expect(() => fixTokenRanges(123 as any)).toThrow();
|
|
17
|
+
expect(() => fixTokenRanges(null as any)).toThrow();
|
|
18
|
+
expect(() => fixFormulaRanges(123 as any)).toThrow();
|
|
19
|
+
expect(() => fixFormulaRanges(null as any)).toThrow();
|
|
20
|
+
expect(() => fixTokenRangesXlsx(123 as any)).toThrow();
|
|
21
|
+
expect(() => fixTokenRangesXlsx(null as any)).toThrow();
|
|
22
|
+
expect(() => fixFormulaRangesXlsx(123 as any)).toThrow();
|
|
23
|
+
expect(() => fixFormulaRangesXlsx(null as any)).toThrow();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('emits new array instance and preserves meta', () => {
|
|
27
|
+
const fx = '=SUM([wb]Sheet1!B2:A1)';
|
|
28
|
+
const tokens = addTokenMeta(tokenize(fx, { mergeRefs: true }));
|
|
29
|
+
const fixedTokens = fixTokenRanges(tokens);
|
|
30
|
+
|
|
31
|
+
expect(tokens).not.toBe(fixedTokens);
|
|
32
|
+
expect(tokens[3]).not.toBe(fixedTokens[3]);
|
|
33
|
+
|
|
34
|
+
expect(tokens[3]).toEqual({
|
|
35
|
+
type: REF_RANGE,
|
|
36
|
+
value: '[wb]Sheet1!B2:A1',
|
|
37
|
+
index: 3,
|
|
38
|
+
depth: 1,
|
|
39
|
+
groupId: 'fxg1'
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(fixedTokens[3]).toEqual({
|
|
43
|
+
type: REF_RANGE,
|
|
44
|
+
value: '[wb]Sheet1!A1:B2',
|
|
45
|
+
index: 3,
|
|
46
|
+
depth: 1,
|
|
47
|
+
groupId: 'fxg1'
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('updates token source location information', () => {
|
|
52
|
+
const tokensWithRanges = tokenize(
|
|
53
|
+
'=SUM(B2:A,table[[#This Row],[Foo]])',
|
|
54
|
+
{ withLocation: true, mergeRefs: true, allowTernary: true }
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(fixTokenRanges(tokensWithRanges, { addBounds: true })).toEqual([
|
|
58
|
+
{ type: FX_PREFIX, value: '=', loc: [ 0, 1 ] },
|
|
59
|
+
{ type: FUNCTION, value: 'SUM', loc: [ 1, 4 ] },
|
|
60
|
+
{ type: OPERATOR, value: '(', loc: [ 4, 5 ] },
|
|
61
|
+
{ type: REF_TERNARY, value: 'A2:B1048576', loc: [ 5, 16 ] },
|
|
62
|
+
{ type: OPERATOR, value: ',', loc: [ 16, 17 ] },
|
|
63
|
+
{ type: REF_STRUCT, value: 'table[@Foo]', loc: [ 17, 28 ] },
|
|
64
|
+
{ type: OPERATOR, value: ')', loc: [ 28, 29 ] }
|
|
65
|
+
]);
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('fixRanges A1', () => {
|
|
70
|
+
const opt = { allowTernary: true };
|
|
71
|
+
|
|
72
|
+
test('doesn\'t mess with things that it doesn\'t have to', () => {
|
|
73
|
+
isFixed('=A1', '=A1', opt);
|
|
74
|
+
isFixed('=ZZ123', '=ZZ123', opt);
|
|
75
|
+
isFixed('=A1:B2', '=A1:B2', opt);
|
|
76
|
+
isFixed('=B3:OFFSET(A1,10,10)', '=B3:OFFSET(A1,10,10)', opt);
|
|
77
|
+
isFixed('=A:B', '=A:B', opt);
|
|
78
|
+
isFixed('=C:C', '=C:C', opt);
|
|
79
|
+
isFixed('=3:6', '=3:6', opt);
|
|
80
|
+
isFixed('=3:3', '=3:3', opt);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('handles redundancy', () => {
|
|
84
|
+
isFixed('=A1:$A$1', '=A1:$A$1', opt);
|
|
85
|
+
isFixed('=A1:A1', '=A1', opt);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('converts lowercase to uppercase', () => {
|
|
89
|
+
isFixed('=a1', '=A1', opt);
|
|
90
|
+
isFixed('=zz123', '=ZZ123', opt);
|
|
91
|
+
isFixed('=a1:b2', '=A1:B2', opt);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('fixes flipped rectangles', () => {
|
|
95
|
+
isFixed('=B2:A1', '=A1:B2', opt);
|
|
96
|
+
isFixed('=$B$2:$A$1', '=$A$1:$B$2', opt);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('fixes flipped beams', () => {
|
|
100
|
+
isFixed('=C:A', '=A:C', opt);
|
|
101
|
+
isFixed('=$D:B', '=B:$D', opt);
|
|
102
|
+
isFixed('=10:1', '=1:10', opt);
|
|
103
|
+
isFixed('=$5:3', '=3:$5', opt);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test('fixes flipped partials - bottom', () => {
|
|
107
|
+
isFixed('=A:A1', '=A1:A', opt);
|
|
108
|
+
isFixed('=A:A$1', '=A$1:A', opt);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('fixes flipped partials - right', () => {
|
|
112
|
+
isFixed('=1:A1', '=A1:1', opt);
|
|
113
|
+
// $1:$A1 is rather counter intuitive case:
|
|
114
|
+
// This range is parsed as { left=null, top=$1, right=$A, bottom=1 } but,
|
|
115
|
+
// because left is null, right and left are flipped around, making this
|
|
116
|
+
// end up as { left=$A, top=$1, right=null, bottom=1 } which serializes
|
|
117
|
+
// as $A$1:1
|
|
118
|
+
isFixed('=$1:$A1', '=$A$1:1', opt);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('fixRanges A1 addBounds', () => {
|
|
123
|
+
const opt = { allowTernary: true, addBounds: true };
|
|
124
|
+
|
|
125
|
+
test('handles functions and existing bounds', () => {
|
|
126
|
+
isFixed('=B3:OFFSET(A1,10,10)', '=B3:OFFSET(A1,10,10)', opt);
|
|
127
|
+
isFixed('=A:A', '=A:A', opt);
|
|
128
|
+
isFixed('=A:A1', '=A:A', opt);
|
|
129
|
+
isFixed('=A:A$1', '=A:A', opt);
|
|
130
|
+
isFixed('=A:$A$1', '=A:$A', opt);
|
|
131
|
+
isFixed('=A.:A', '=A.:A', opt);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test('adds bounds to partials - bottom', () => {
|
|
135
|
+
isFixed('=A1:A', '=A:A', opt);
|
|
136
|
+
isFixed('=A1:Z', '=A:Z', opt);
|
|
137
|
+
isFixed('=A:A1', '=A:A', opt);
|
|
138
|
+
isFixed('=$A1:A', '=$A:A', opt);
|
|
139
|
+
isFixed('=A$1:A', '=A:A', opt);
|
|
140
|
+
isFixed('=A1:$A', '=A:$A', opt);
|
|
141
|
+
isFixed('=A2:A', '=A2:A1048576', opt);
|
|
142
|
+
isFixed('=B2:B', '=B2:B1048576', opt);
|
|
143
|
+
isFixed('=A:A2', '=A2:A1048576', opt);
|
|
144
|
+
isFixed('=B:B2', '=B2:B1048576', opt);
|
|
145
|
+
isFixed('=B.:.B2', '=B2.:.B1048576', opt);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
test('adds bounds to flipped partials - bottom', () => {
|
|
149
|
+
isFixed('=A1:1', '=1:1', opt);
|
|
150
|
+
isFixed('=A1:4', '=1:4', opt);
|
|
151
|
+
isFixed('=1:A1', '=1:1', opt);
|
|
152
|
+
isFixed('=$A1:1', '=1:1', opt);
|
|
153
|
+
isFixed('=A$1:1', '=$1:1', opt);
|
|
154
|
+
isFixed('=A1:$1', '=1:$1', opt);
|
|
155
|
+
isFixed('=B1:1', '=B1:XFD1', opt);
|
|
156
|
+
isFixed('=1:B1', '=B1:XFD1', opt);
|
|
157
|
+
isFixed('=B2:20', '=B2:XFD20', opt);
|
|
158
|
+
isFixed('=2:B20', '=B2:XFD20', opt);
|
|
159
|
+
isFixed('=2:.B20', '=B2:.XFD20', opt);
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
describe('fixRanges structured references', () => {
|
|
164
|
+
test('fixes this row references', () => {
|
|
165
|
+
isFixed('=Table1[[#This Row],[Foo]]', '=Table1[@Foo]');
|
|
166
|
+
isFixed('=[[#This Row],[s:s]]', '=[@[s:s]]');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
test('fixes column ranges', () => {
|
|
170
|
+
isFixed('=Table1[[#Totals],col name:Foo]', '=Table1[[#Totals],[col name]:[Foo]]');
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test('fixes special identifiers order', () => {
|
|
174
|
+
isFixed('[[#data],[#headers]]', '[[#Headers],[#Data]]');
|
|
175
|
+
isFixed('[[#headers],[#data]]', '[[#Headers],[#Data]]');
|
|
176
|
+
isFixed('[[#totals],[#data]]', '[[#Data],[#Totals]]');
|
|
177
|
+
isFixed('[ [#totals], [#data] ]', '[[#Data],[#Totals]]');
|
|
178
|
+
isFixed('[[#data],[#totals]]', '[[#Data],[#Totals]]');
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test('fixes column references', () => {
|
|
182
|
+
isFixed('[[#all],foo:bar]', '[[#All],[foo]:[bar]]');
|
|
183
|
+
isFixed('[[#all],[#all],[#all],[#all],[ColumnName]]', '[[#All],[ColumnName]]');
|
|
184
|
+
isFixed('[@[foo]:bar]', '[@[foo]:[bar]]');
|
|
185
|
+
isFixed('[@foo bar]', '[@[foo bar]]');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
test('preserves whitespace in column headers', () => {
|
|
189
|
+
// Care must be taken with spaces in column headers.
|
|
190
|
+
// Excel considers refs only valid if they match the column name
|
|
191
|
+
// but the parser does not know the names, so it must preserve
|
|
192
|
+
// leading/trailing whitespace.
|
|
193
|
+
isFixed('[ @[foo bar] ]', '[@[foo bar]]');
|
|
194
|
+
isFixed('[ @[ foo bar ] ]', '[@[ foo bar ]]');
|
|
195
|
+
isFixed('[ @foo bar ]', '[@[foo bar ]]');
|
|
196
|
+
isFixed('[@ foo bar]', '[@[ foo bar]]');
|
|
197
|
+
isFixed('[ @ foo bar ]', '[@[ foo bar ]]');
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('fixRanges works with xlsx mode', () => {
|
|
202
|
+
test('should not mess with invalid ranges in normal mode', () => {
|
|
203
|
+
isFixed("='[Workbook]'!Table[Column]", "='[Workbook]'!Table[Column]");
|
|
204
|
+
isFixed('=[Workbook]!Table[Column]', '=[Workbook]!Table[Column]');
|
|
205
|
+
isFixed("='[Foo]'!A1", "='[Foo]'!A1");
|
|
206
|
+
isFixed('=[Foo]!A1', '=[Foo]!A1');
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test('should fix things in xlsx mode', () => {
|
|
210
|
+
const opts = { xlsx: true };
|
|
211
|
+
isFixed("='[Workbook]'!Table[Column]", '=[Workbook]!Table[Column]', opts);
|
|
212
|
+
isFixed('=[Workbook]!Table[Column]', '=[Workbook]!Table[Column]', opts);
|
|
213
|
+
isFixed('=[Lorem Ipsum]!Table[Column]', "='[Lorem Ipsum]'!Table[Column]", opts);
|
|
214
|
+
isFixed("='[Foo]'!A1", '=[Foo]!A1', opts);
|
|
215
|
+
isFixed('=[Foo]Bar!A1', '=[Foo]Bar!A1', opts);
|
|
216
|
+
isFixed('=[Foo Bar]Baz!A1', "='[Foo Bar]Baz'!A1", opts);
|
|
217
|
+
isFixed('=[Foo]!A1', '=[Foo]!A1', opts);
|
|
218
|
+
isFixed('=[Lorem Ipsum]!A1', "='[Lorem Ipsum]'!A1", opts);
|
|
219
|
+
});
|
|
220
|
+
});
|