From 34f24ac9cfdca3662010c375efab7cf20e8f842b Mon Sep 17 00:00:00 2001 From: Jason Kurian Date: Sun, 13 Jun 2021 17:13:11 -0400 Subject: [PATCH] GraphQL: Added more detailed tokens (#2939) Co-authored-by: Michael Schmidt --- components/prism-graphql.js | 174 +++++++++++++++++- components/prism-graphql.min.js | 2 +- .../languages/graphql/attr-name_feature.test | 14 +- tests/languages/graphql/constant_feature.test | 4 +- tests/languages/graphql/fragment_feature.test | 11 +- .../graphql_inclusion.test | 8 +- 6 files changed, 194 insertions(+), 19 deletions(-) diff --git a/components/prism-graphql.js b/components/prism-graphql.js index c676a6d806..ad5cd5d18b 100644 --- a/components/prism-graphql.js +++ b/components/prism-graphql.js @@ -24,11 +24,17 @@ Prism.languages.graphql = { alias: 'function' }, 'attr-name': { - pattern: /\b[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i, + pattern: /[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i, greedy: true }, + 'atom-input': { + pattern: /[A-Z]\w*Input(?=!?.*$)/m, + alias: 'class-name' + }, + 'scalar': /\b(?:Boolean|Float|ID|Int|String)\b/, + 'constant': /\b[A-Z][A-Z_\d]*\b/, 'class-name': { - pattern: /(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*)[a-zA-Z_]\w*/, + pattern: /(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*|:\s*|\[)[A-Z_]\w*/, lookbehind: true }, 'fragment': { @@ -36,8 +42,170 @@ Prism.languages.graphql = { lookbehind: true, alias: 'function' }, + 'definition-mutation': { + pattern: /(\bmutation\s+|\.{3}\s*)[a-zA-Z_]\w*/, + lookbehind: true, + alias: 'function' + }, + 'definition-query': { + pattern: /(\bquery\s+|\.{3}\s*)[a-zA-Z_]\w*/, + lookbehind: true, + alias: 'function' + }, 'keyword': /\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/, 'operator': /[!=|&]|\.{3}/, + 'property-query': /\w+(?=\s*\()/, + 'object': /\w+(?=\s*{)/, 'punctuation': /[!(){}\[\]:=,]/, - 'constant': /\b(?!ID\b)[A-Z][A-Z_\d]*\b/ + 'property': /\w+/ }; + +Prism.hooks.add('after-tokenize', function afterTokenizeGraphql(env) { + if (env.language !== 'graphql') { + return; + } + + /** + * get the graphql token stream that we want to customize + * + * @typedef {InstanceType} Token + * @type {Token[]} + */ + var validTokens = env.tokens.filter(function (token) { + return typeof token !== 'string' && token.type !== 'comment' && token.type !== 'scalar'; + }); + + var currentIndex = 0; + + /** + * Returns whether the token relative to the current index has the given type. + * + * @param {number} offset + * @returns {Token | undefined} + */ + function getToken(offset) { + return validTokens[currentIndex + offset]; + } + + /** + * Returns whether the token relative to the current index has the given type. + * + * @param {readonly string[]} types + * @param {number} [offset=0] + * @returns {boolean} + */ + function isTokenType(types, offset) { + offset = offset || 0; + for (var i = 0; i < types.length; i++) { + var token = getToken(i + offset); + if (!token || token.type !== types[i]) { + return false; + } + } + return true; + } + + /** + * Returns the index of the closing bracket to an opening bracket. + * + * It is assumed that `token[currentIndex - 1]` is an opening bracket. + * + * If no closing bracket could be found, `-1` will be returned. + * + * @param {RegExp} open + * @param {RegExp} close + * @returns {number} + */ + function findClosingBracket(open, close) { + var stackHeight = 1; + + for (var i = currentIndex; i < validTokens.length; i++) { + var token = validTokens[i]; + var content = token.content; + + if (token.type === 'punctuation' && typeof content === 'string') { + if (open.test(content)) { + stackHeight++; + } else if (close.test(content)) { + stackHeight--; + + if (stackHeight === 0) { + return i; + } + } + } + } + + return -1; + } + + /** + * Adds an alias to the given token. + * + * @param {Token} token + * @param {string} alias + * @returns {void} + */ + function addAlias(token, alias) { + var aliases = token.alias; + if (!aliases) { + token.alias = aliases = []; + } else if (!Array.isArray(aliases)) { + token.alias = aliases = [aliases]; + } + aliases.push(alias); + } + + for (; currentIndex < validTokens.length;) { + var startToken = validTokens[currentIndex++]; + + // add special aliases for mutation tokens + if (startToken.type === 'keyword' && startToken.content === 'mutation') { + // any array of the names of all input variables (if any) + var inputVariables = []; + + if (isTokenType(['definition-mutation', 'punctuation']) && getToken(1).content === '(') { + // definition + + currentIndex += 2; // skip 'definition-mutation' and 'punctuation' + + var definitionEnd = findClosingBracket(/^\($/, /^\)$/); + if (definitionEnd === -1) { + continue; + } + + // find all input variables + for (; currentIndex < definitionEnd; currentIndex++) { + var t = getToken(0); + if (t.type === 'variable') { + addAlias(t, 'variable-input'); + inputVariables.push(t.content); + } + } + + currentIndex = definitionEnd + 1; + } + + if (isTokenType(['punctuation', 'property-query']) && getToken(0).content === '{') { + currentIndex++; // skip opening bracket + + addAlias(getToken(0), 'property-mutation'); + + if (inputVariables.length > 0) { + var mutationEnd = findClosingBracket(/^{$/, /^}$/); + if (mutationEnd === -1) { + continue; + } + + // give references to input variables a special alias + for (var i = currentIndex; i < mutationEnd; i++) { + var varToken = validTokens[i]; + if (varToken.type === 'variable' && inputVariables.indexOf(varToken.content) >= 0) { + addAlias(varToken, 'variable-input'); + } + } + } + } + } + } +}); diff --git a/components/prism-graphql.min.js b/components/prism-graphql.min.js index d97f3c37ed..2ac0fa46ce 100644 --- a/components/prism-graphql.min.js +++ b/components/prism-graphql.min.js @@ -1 +1 @@ -Prism.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:Prism.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:true|false)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/\b[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*)[a-zA-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/,operator:/[!=|&]|\.{3}/,punctuation:/[!(){}\[\]:=,]/,constant:/\b(?!ID\b)[A-Z][A-Z_\d]*\b/}; \ No newline at end of file +Prism.languages.graphql={comment:/#.*/,description:{pattern:/(?:"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*")(?=\s*[a-z_])/i,greedy:!0,alias:"string",inside:{"language-markdown":{pattern:/(^"(?:"")?)(?!\1)[\s\S]+(?=\1$)/,lookbehind:!0,inside:Prism.languages.markdown}}},string:{pattern:/"""(?:[^"]|(?!""")")*"""|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},number:/(?:\B-|\b)\d+(?:\.\d+)?(?:e[+-]?\d+)?\b/i,boolean:/\b(?:true|false)\b/,variable:/\$[a-z_]\w*/i,directive:{pattern:/@[a-z_]\w*/i,alias:"function"},"attr-name":{pattern:/[a-z_]\w*(?=\s*(?:\((?:[^()"]|"(?:\\.|[^\\"\r\n])*")*\))?:)/i,greedy:!0},"atom-input":{pattern:/[A-Z]\w*Input(?=!?.*$)/m,alias:"class-name"},scalar:/\b(?:Boolean|Float|ID|Int|String)\b/,constant:/\b[A-Z][A-Z_\d]*\b/,"class-name":{pattern:/(\b(?:enum|implements|interface|on|scalar|type|union)\s+|&\s*|:\s*|\[)[A-Z_]\w*/,lookbehind:!0},fragment:{pattern:/(\bfragment\s+|\.{3}\s*(?!on\b))[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-mutation":{pattern:/(\bmutation\s+|\.{3}\s*)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},"definition-query":{pattern:/(\bquery\s+|\.{3}\s*)[a-zA-Z_]\w*/,lookbehind:!0,alias:"function"},keyword:/\b(?:directive|enum|extend|fragment|implements|input|interface|mutation|on|query|repeatable|scalar|schema|subscription|type|union)\b/,operator:/[!=|&]|\.{3}/,"property-query":/\w+(?=\s*\()/,object:/\w+(?=\s*{)/,punctuation:/[!(){}\[\]:=,]/,property:/\w+/},Prism.hooks.add("after-tokenize",function(n){if("graphql"===n.language)for(var o=n.tokens.filter(function(n){return"string"!=typeof n&&"comment"!==n.type&&"scalar"!==n.type}),s=0;s