Skip to content

Instantly share code, notes, and snippets.

@IgnusG
Created November 24, 2020 10:38
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save IgnusG/8d0b6271a294a263b4185a959d8b77e0 to your computer and use it in GitHub Desktop.
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
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