UNPKG

3.73 kBJavaScriptView Raw
1/**
2 * @license
3 * Copyright 2018 Google LLC
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18'use strict';
19
20const {
21 memoizedTagFunction,
22 trimCommonWhitespaceFromLines,
23} = require('template-tag-common');
24
25const LITERAL_BACKTICK_FIXUP_PATTERN = /((?:[^\\]|\\[^`])+)|\\(`)(?!`)/g;
26
27/**
28 * Trims common whitespace and converts escaped backticks
29 * to backticks as appropriate.
30 *
31 * @param {!Array.<string>} strings a valid TemplateObject.
32 * @return {!Array.<string>} the adjusted raw strings.
33 */
34function prepareStrings(strings) {
35 const raw = trimCommonWhitespaceFromLines(strings).raw.slice();
36 for (let i = 0, len = raw.length; i < len; ++i) {
37 // Convert \` to ` but leave \\` alone.
38 raw[i] = raw[i].replace(LITERAL_BACKTICK_FIXUP_PATTERN, '$1$2');
39 }
40 return raw;
41}
42
43/**
44 * Returns a template tag function that contextually autoescapes values
45 * producing a SqlFragment.
46 */
47function makeSqlTagFunction(
48 { makeLexer },
49 escape,
50 escapeDelimitedValue,
51 fixupBackticks,
52 decorateOutput) {
53 /**
54 * Analyzes the static parts of the tag content.
55 *
56 * @param {!Array.<string>} strings a valid TemplateObject.
57 * @return { !{
58 * delimiters : !Array.<string>,
59 * chunks: !Array.<string>
60 * } }
61 * A record like { delimiters, chunks }
62 * where delimiter is a contextual cue and chunk is
63 * the adjusted raw text.
64 */
65 function computeStatic(strings) {
66 const chunks = fixupBackticks ? prepareStrings(strings) : strings.raw;
67 const lexer = makeLexer();
68
69 const delimiters = [];
70 for (let i = 0, len = chunks.length; i < len; ++i) {
71 const chunk = String(chunks[i]);
72 delimiters.push(lexer(chunk));
73 }
74
75 // Signal end of input.
76 lexer(null);
77
78 return { delimiters, chunks };
79 }
80
81 function defangMergeHazard(before, escaped, after) {
82 const escapedLast = escaped[escaped.length - 1];
83 if ('"\'`'.indexOf(escapedLast) < 0) {
84 // Not a merge hazard.
85 return escaped;
86 }
87
88 let escapedSetOff = escaped;
89 const lastBefore = before[before.length - 1];
90 if (escapedLast === escaped[0] && escapedLast === lastBefore) {
91 escapedSetOff = ` ${ escapedSetOff }`;
92 }
93 if (escapedLast === after[0]) {
94 escapedSetOff += ' ';
95 }
96 return escapedSetOff;
97 }
98
99 function interpolateSqlIntoFragment(
100 { stringifyObjects, timeZone, forbidQualified },
101 { delimiters, chunks },
102 strings, values) {
103 // A buffer to accumulate output.
104 let [ result ] = chunks;
105 for (let i = 1, len = chunks.length; i < len; ++i) {
106 const chunk = chunks[i];
107 // The count of values must be 1 less than the surrounding
108 // chunks of literal text.
109 const delimiter = delimiters[i - 1];
110 const value = values[i - 1];
111
112 const escaped = delimiter ?
113 escapeDelimitedValue(value, delimiter, timeZone, forbidQualified) :
114 defangMergeHazard(
115 result,
116 escape(value, stringifyObjects, timeZone),
117 chunk);
118
119 result += escaped + chunk;
120 }
121
122 return decorateOutput(result);
123 }
124
125 return memoizedTagFunction(computeStatic, interpolateSqlIntoFragment);
126}
127
128module.exports.makeSqlTagFunction = makeSqlTagFunction;