Created
November 24, 2020 10:38
-
-
Save IgnusG/8d0b6271a294a263b4185a959d8b77e0 to your computer and use it in GitHub Desktop.
WARNING: This is an unofficial (unsupported) patch - stuff might break! - Credit goes to https://github.com/NicoCevallos
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/node_modules/eslint-plugin-svelte3/index.js b/node_modules/eslint-plugin-svelte3/index.js | |
index 86ec523..cba16d2 100644 | |
--- a/node_modules/eslint-plugin-svelte3/index.js | |
+++ b/node_modules/eslint-plugin-svelte3/index.js | |
@@ -62,6 +62,15 @@ const get_line_offsets = str => { | |
return offsets; | |
}; | |
+// find the index of the last element of an array matching a condition | |
+const find_last_index = (array, cond) => { | |
+ const idx = array.findIndex(item => !cond(item)); | |
+ return idx === -1 ? array.length - 1 : idx - 1; | |
+}; | |
+ | |
+// find the last element of an array matching a condition | |
+const find_last = (array, cond) => array[find_last_index(array, cond)]; | |
+ | |
// return a new block | |
const new_block = () => ({ transformed_code: '', line_offsets: null, translations: new Map() }); | |
@@ -100,6 +109,7 @@ Linter.prototype.verify = function(code, config, options) { | |
processor_options.ignore_styles = settings['svelte3/ignore-styles']; | |
processor_options.compiler_options = settings['svelte3/compiler-options']; | |
processor_options.named_blocks = settings['svelte3/named-blocks']; | |
+ processor_options.svelte_preprocess = settings['svelte3/preprocess']; | |
// call original Linter#verify | |
return verify.call(this, code, config, options); | |
}; | |
@@ -110,10 +120,94 @@ const reset = () => { | |
messages: null, | |
var_names: null, | |
blocks: new Map(), | |
+ pre_line_offsets: null, | |
+ post_line_offsets: null, | |
+ mappings: null, | |
}; | |
}; | |
reset(); | |
+var charToInteger = {}; | |
+var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; | |
+for (var i = 0; i < chars.length; i++) { | |
+ charToInteger[chars.charCodeAt(i)] = i; | |
+} | |
+function decode(mappings) { | |
+ var generatedCodeColumn = 0; // first field | |
+ var sourceFileIndex = 0; // second field | |
+ var sourceCodeLine = 0; // third field | |
+ var sourceCodeColumn = 0; // fourth field | |
+ var nameIndex = 0; // fifth field | |
+ var decoded = []; | |
+ var line = []; | |
+ var segment = []; | |
+ for (var i = 0, j = 0, shift = 0, value = 0, len = mappings.length; i < len; i++) { | |
+ var c = mappings.charCodeAt(i); | |
+ if (c === 44) { // "," | |
+ if (segment.length) | |
+ line.push(segment); | |
+ segment = []; | |
+ j = 0; | |
+ } | |
+ else if (c === 59) { // ";" | |
+ if (segment.length) | |
+ line.push(segment); | |
+ segment = []; | |
+ j = 0; | |
+ decoded.push(line); | |
+ line = []; | |
+ generatedCodeColumn = 0; | |
+ } | |
+ else { | |
+ var integer = charToInteger[c]; | |
+ if (integer === undefined) { | |
+ throw new Error('Invalid character (' + String.fromCharCode(c) + ')'); | |
+ } | |
+ var hasContinuationBit = integer & 32; | |
+ integer &= 31; | |
+ value += integer << shift; | |
+ if (hasContinuationBit) { | |
+ shift += 5; | |
+ } | |
+ else { | |
+ var shouldNegate = value & 1; | |
+ value >>>= 1; | |
+ if (shouldNegate) { | |
+ value = -value; | |
+ if (value === 0) | |
+ value = -0x80000000; | |
+ } | |
+ if (j == 0) { | |
+ generatedCodeColumn += value; | |
+ segment.push(generatedCodeColumn); | |
+ } | |
+ else if (j === 1) { | |
+ sourceFileIndex += value; | |
+ segment.push(sourceFileIndex); | |
+ } | |
+ else if (j === 2) { | |
+ sourceCodeLine += value; | |
+ segment.push(sourceCodeLine); | |
+ } | |
+ else if (j === 3) { | |
+ sourceCodeColumn += value; | |
+ segment.push(sourceCodeColumn); | |
+ } | |
+ else if (j === 4) { | |
+ nameIndex += value; | |
+ segment.push(nameIndex); | |
+ } | |
+ j++; | |
+ value = shift = 0; // reset | |
+ } | |
+ } | |
+ } | |
+ if (segment.length) | |
+ line.push(segment); | |
+ decoded.push(line); | |
+ return decoded; | |
+} | |
+ | |
let default_compiler; | |
// find the contextual name or names described by a particular node in the AST | |
@@ -135,7 +229,7 @@ const find_contextual_names = (compiler, node) => { | |
}; | |
// extract scripts to lint from component definition | |
-const preprocess = text => { | |
+const preprocess = (text, filename) => { | |
const compiler = processor_options.custom_compiler || default_compiler || (default_compiler = require('svelte/compiler')); | |
if (processor_options.ignore_styles) { | |
// wipe the appropriate <style> tags in the file | |
@@ -152,10 +246,112 @@ const preprocess = text => { | |
return processor_options.ignore_styles(attrs) ? match.replace(/\S/g, ' ') : match; | |
}); | |
} | |
- // get information about the component | |
let result; | |
+ let processedResult; | |
+ let processedModule; | |
+ let processedInstance; | |
+ let processedStyle; | |
+ let processedMarkup; | |
+ let moduleExt = 'js'; | |
+ let instanceExt = 'js'; | |
+ let moduleEndLine; | |
+ let processedModuleLineOffset; | |
+ let instanceEndLine; | |
+ let processedInstanceLineOffset; | |
try { | |
- result = compiler.compile(text, { generate: false, ...processor_options.compiler_options }); | |
+ // run preprocessor if present | |
+ if (processor_options.svelte_preprocess) { | |
+ const result = processor_options.svelte_preprocess(text, filename); | |
+ if (result) { | |
+ state.pre_line_offsets = get_line_offsets(text); | |
+ processedResult = result.code; | |
+ state.post_line_offsets = get_line_offsets(processedResult); | |
+ if (result.mappings) { | |
+ state.mappings = decode(result.mappings); | |
+ } | |
+ | |
+ if (result.module) { | |
+ processedModule = result.module; | |
+ moduleExt = result.module.ext; | |
+ } | |
+ if (result.instance) { | |
+ processedInstance = result.instance; | |
+ instanceExt = result.instance.ext; | |
+ } | |
+ | |
+ processedStyle = result.style; | |
+ | |
+ processedMarkup = result.markup; | |
+ | |
+ processor_options.named_blocks = true; | |
+ } | |
+ } | |
+ // get information about the component | |
+ result = compiler.compile(processedResult || text, { generate: false, ...processor_options.compiler_options }); | |
+ if (processedResult) { | |
+ const { html, css, instance, module } = result.ast; | |
+ | |
+ let moduleDiff = processedModule ? processedModule.diff : 0; | |
+ let instanceDiff = processedInstance ? processedInstance.diff : 0; | |
+ let styleDiff = processedStyle ? processedStyle.diff : 0; | |
+ let markupDiff = processedMarkup ? processedMarkup.diff : 0; | |
+ | |
+ let modulePreOffset = 0; | |
+ let modulePostOffset = 0; | |
+ if (module) { | |
+ if (module.start > html.start) { | |
+ modulePreOffset += markupDiff; | |
+ } | |
+ if (css && module.start > css.start) { | |
+ modulePreOffset += styleDiff; | |
+ } | |
+ if (instance && module.start > instance.start) { | |
+ modulePreOffset += instanceDiff; | |
+ } | |
+ | |
+ modulePostOffset = modulePreOffset + moduleDiff; | |
+ } | |
+ | |
+ let instancePreOffset = 0; | |
+ let instancePostOffset = 0; | |
+ if (instance) { | |
+ if (instance.start > html.start) { | |
+ instancePreOffset += markupDiff; | |
+ } | |
+ if (css && instance.start > css.start) { | |
+ instancePreOffset += styleDiff; | |
+ } | |
+ if (module && instance.start > module.start) { | |
+ instancePreOffset += moduleDiff; | |
+ } | |
+ | |
+ instancePostOffset = instancePreOffset + instanceDiff; | |
+ } | |
+ | |
+ if (module && processedModule) { | |
+ moduleEndLine = module.content.loc.end.line; | |
+ processedModuleLineOffset = processedModule.ast.loc.end.line - moduleEndLine; | |
+ module.content.body = processedModule.ast.body; | |
+ | |
+ module.start += modulePreOffset; | |
+ module.end += modulePostOffset; | |
+ | |
+ module.content.start += modulePreOffset; | |
+ module.content.end += modulePostOffset; | |
+ } | |
+ | |
+ if (instance && processedInstance) { | |
+ instanceEndLine = instance.content.loc.end.line; | |
+ processedInstanceLineOffset = processedInstance.ast.loc.end.line - instanceEndLine; | |
+ instance.content.body = processedInstance.ast.body; | |
+ | |
+ instance.start += instancePreOffset; | |
+ instance.end += instancePostOffset; | |
+ | |
+ instance.content.start += instancePreOffset; | |
+ instance.content.end += instancePostOffset; | |
+ } | |
+ } | |
} catch ({ name, message, start, end }) { | |
// convert the error to a linting message, store it, and return | |
state.messages = [ | |
@@ -176,27 +372,40 @@ const preprocess = text => { | |
state.var_names = new Set(vars.map(v => v.name)); | |
// convert warnings to linting messages | |
- state.messages = (processor_options.ignore_warnings ? warnings.filter(warning => !processor_options.ignore_warnings(warning)) : warnings).map(({ code, message, start, end }) => ({ | |
- ruleId: code, | |
- severity: 1, | |
- message, | |
- line: start && start.line, | |
- column: start && start.column + 1, | |
- endLine: end && end.line, | |
- endColumn: end && end.column + 1, | |
- })); | |
+ state.messages = (processor_options.ignore_warnings ? warnings.filter(warning => !processor_options.ignore_warnings(warning)) : warnings).map(({ code, message, start, end }) => { | |
+ let fixLine = 0; | |
+ | |
+ if (processedInstanceLineOffset && start && start.line > instanceEndLine ) { | |
+ fixLine += processedInstanceLineOffset; | |
+ } | |
+ | |
+ if (processedModuleLineOffset && start && start.line > moduleEndLine ) { | |
+ fixLine += processedModuleLineOffset; | |
+ } | |
+ return { | |
+ ruleId: code, | |
+ severity: 1, | |
+ message, | |
+ line: start && start.line + fixLine, | |
+ column: start && start.column + 1, | |
+ endLine: end && end.line + fixLine, | |
+ endColumn: end && end.column + 1, | |
+ } | |
+ }); | |
// build strings that we can send along to ESLint to get the remaining messages | |
if (ast.module) { | |
// block for <script context='module'> | |
const block = new_block(); | |
- state.blocks.set('module.js', block); | |
+ state.blocks.set(`module.${moduleExt}`, block); | |
get_translation(text, block, ast.module.content); | |
if (ast.instance) { | |
- block.transformed_code += text.slice(ast.instance.content.start, ast.instance.content.end); | |
+ block.transformed_code += processedInstance | |
+ ? processedInstance.original | |
+ : text.slice(ast.instance.content.start, ast.instance.content.end); | |
} | |
block.transformed_code += references_and_reassignments; | |
@@ -205,7 +414,7 @@ const preprocess = text => { | |
if (ast.instance) { | |
// block for <script context='instance'> | |
const block = new_block(); | |
- state.blocks.set('instance.js', block); | |
+ state.blocks.set(`instance.${instanceExt}`, block); | |
block.transformed_code = vars.filter(v => v.injected || v.module).map(v => `let ${v.name};`).join(''); | |
@@ -223,11 +432,13 @@ const preprocess = text => { | |
const nodes_with_contextual_scope = new WeakSet(); | |
let in_quoted_attribute = false; | |
+ const htmlText = processedResult || text; | |
+ | |
compiler.walk(ast.html, { | |
enter(node, parent, prop) { | |
if (prop === 'expression') { | |
return this.skip(); | |
- } else if (prop === 'attributes' && '\'"'.includes(text[node.end - 1])) { | |
+ } else if (prop === 'attributes' && '\'"'.includes(htmlText[node.end - 1])) { | |
in_quoted_attribute = true; | |
} | |
contextual_names.length = 0; | |
@@ -248,7 +459,7 @@ const preprocess = text => { | |
if (node.expression && typeof node.expression === 'object') { | |
// add the expression in question to the constructed string | |
block.transformed_code += '('; | |
- get_translation(text, block, node.expression, { template: true, in_quoted_attribute }); | |
+ get_translation(htmlText, block, node.expression, { template: true, in_quoted_attribute }); | |
block.transformed_code += ');'; | |
} | |
}, | |
@@ -268,6 +479,31 @@ const preprocess = text => { | |
return [...state.blocks].map(([filename, { transformed_code: text }]) => processor_options.named_blocks ? { text, filename } : text); | |
}; | |
+const unmap = message => { | |
+ for (let j = 0; j < 2; j++) { | |
+ if (message[j ? 'endLine' : 'line']) { | |
+ const mapping = find_last(state.mappings[message[j ? 'endLine' : 'line'] - 1], ([column]) => column < message[j ? 'endColumn' : 'column']); | |
+ if (!mapping || mapping[1] !== 0) { | |
+ return false; | |
+ } | |
+ message[j ? 'endLine' : 'line'] = mapping[2] + 1; | |
+ message[j ? 'endColumn' : 'column'] += mapping[3] - mapping[0]; | |
+ } | |
+ } | |
+ if (message.fix) { | |
+ for (let j = 0; j < 2; j++) { | |
+ const line = find_last_index(state.post_line_offsets, offset => offset < message.fix.range[j]); | |
+ const line_offset = state.post_line_offsets[line]; | |
+ const mapping = find_last(state.mappings[line], ([column]) => column < message.fix.range[j] - line_offset); | |
+ if (!mapping || mapping[1] !== 0) { | |
+ return false; | |
+ } | |
+ message.fix.range[j] += mapping[3] - mapping[0] + state.pre_line_offsets[mapping[2]] - line_offset; | |
+ } | |
+ } | |
+ return true; | |
+}; | |
+ | |
// transform a linting message according to the module/instance script info we've gathered | |
const transform_message = ({ transformed_code }, { unoffsets, dedent, offsets, range }, message) => { | |
// strip out the start and end of the fix if they are not actually changes | |
@@ -385,6 +621,9 @@ const postprocess = blocks_messages => { | |
} | |
} | |
} | |
+ if (state.mappings) { | |
+ state.messages = state.messages.filter(unmap); | |
+ } | |
// sort messages and return | |
const sorted_messages = state.messages.sort((a, b) => a.line - b.line || a.column - b.column); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment