Closed IanVS closed 1 year ago
Note this also fixes the reproduction in https://github.com/trivago/prettier-plugin-sort-imports/issues/199, since we're not processing the entire contents of the file anymore (I don't think imports can happen outside of a script or setup script block). But I think there's some more we can do to address that issue as well, in a separate PR.
code.replace
is not safe to use here.
The code
here is the whole SFC source code. Despite the fact that up to one <script>
and one <script setup>
block can exist in a single SFC, other 3rd blocks may exist in that SFC (like vue-i18n
adds a new <i18n>
block). In rare cases String.prototype.replace
may replace the wrong place producing tricky issues and it is slower than merging by block offsets in the original fix.
@Tanimodori that sounds like a serious bug! Would you mind filing a PR to at least give us examples, if not fix it?
I’m not a Vue user, so I only sort of understand your situation here. I’m happy to review any PR that fixes this.
@fbartho Surely. Give me some time to implement that.
@fbartho The fix is blocked by #133. I've opened PR #134 to fix that. But it seems to be blocked by #128 and #129 again...
Let me introduce some backgrounds and give the examples.
Vue Single File Component, or SFC, is a file that contains blocks which describes various aspects of a reusable component similar to Web Component. Here's a typical structure of SFC:
<template>
<div class="my-text">
Here's the template block that
describes how vue render this component
into HTML DOM
</div>
</template>
<script setup lang="ts">
console.log("Here's the script block that");
console.log("contains the actual code of this component");
</script>
<style lang="less">
// Here is the style block that
// contains the style used in template
.my-text {
color: red;
}
</style>
You can tell that different blocks use different languages and the order of blocks does not matter. This prettier plugin only cares about <script>
and <script setup>
which contains JS/TS code so we use parse
from @vue/compiler-sfc
to split the blocks.
Here is the problematic example:
<template>
<code>
import z from 'z';
import threeLevelRelativePath from "../../../threeLevelRelativePath";
import sameLevelRelativePath from "./sameLevelRelativePath";
import thirdParty from "third-party";
import oneLevelRelativePath from "../oneLevelRelativePath";
import otherthing from "@core/otherthing";
import { defineComponent } from 'vue'
function add(a,b) {
return a + b;
}
import z from 'z';
import threeLevelRelativePath from "../../../threeLevelRelativePath";
import sameLevelRelativePath from "./sameLevelRelativePath";
import thirdParty from "third-party";
import oneLevelRelativePath from "../oneLevelRelativePath";
import otherthing from "@core/otherthing";
import { defineComponent } from 'vue'
function add(a,b) {
return a + b;
}
</code>
</template>
<script>
import z from 'z';
import threeLevelRelativePath from "../../../threeLevelRelativePath";
import sameLevelRelativePath from "./sameLevelRelativePath";
import thirdParty from "third-party";
import oneLevelRelativePath from "../oneLevelRelativePath";
import otherthing from "@core/otherthing";
import { defineComponent } from 'vue'
function add(a,b) {
return a + b;
}
</script>
<script setup>
import z from 'z';
import threeLevelRelativePath from "../../../threeLevelRelativePath";
import sameLevelRelativePath from "./sameLevelRelativePath";
import thirdParty from "third-party";
import oneLevelRelativePath from "../oneLevelRelativePath";
import otherthing from "@core/otherthing";
import { defineComponent } from 'vue'
function add(a,b) {
return a + b;
}
</script>
The <template>
block contains the same "code text" in <script>
and <script setup>
here! Well the "code text" in <template>
block only render into HTML DOM which does not serve as actual JS/TS code. When we want to show the actual code in a component to demonstrate a specific use case of it in a component library, this example may actually exist. Note that 3rd plugins may also add their custom blocks into SFC.
So now you can tell the problem. As String.prototype.replace
only replace the first occurrence and the order of blocks does not matter (some programmers prefer writing <template>
first actually), code.replace
will replace the content in <template>
rather than <script>
and <script setup>
! Here's the outcome:
<template>
<code>
import thirdParty from "third-party"; import { defineComponent } from
'vue'; import z from 'z'; import otherthing from "@core/otherthing";
import threeLevelRelativePath from "../../../threeLevelRelativePath";
import oneLevelRelativePath from "../oneLevelRelativePath"; import
sameLevelRelativePath from "./sameLevelRelativePath"; function add(a,b)
{ return a + b; } import thirdParty from "third-party"; import {
defineComponent } from 'vue'; import z from 'z'; import otherthing from
"@core/otherthing"; import threeLevelRelativePath from
"../../../threeLevelRelativePath"; import oneLevelRelativePath from
"../oneLevelRelativePath"; import sameLevelRelativePath from
"./sameLevelRelativePath"; function add(a,b) { return a + b; }
</code>
</template>
<script>
import z from "z";
import threeLevelRelativePath from "../../../threeLevelRelativePath";
import sameLevelRelativePath from "./sameLevelRelativePath";
import thirdParty from "third-party";
import oneLevelRelativePath from "../oneLevelRelativePath";
import otherthing from "@core/otherthing";
import { defineComponent } from "vue";
function add(a, b) {
return a + b;
}
</script>
<script setup>
import z from "z";
import threeLevelRelativePath from "../../../threeLevelRelativePath";
import sameLevelRelativePath from "./sameLevelRelativePath";
import thirdParty from "third-party";
import oneLevelRelativePath from "../oneLevelRelativePath";
import otherthing from "@core/otherthing";
import { defineComponent } from "vue";
function add(a, b) {
return a + b;
}
</script>
Although the <code>
is compressed a bit due to the HTML Whitespace Sensitivity settings of prettier
in this repo's config, you can tell that the wrong place has been replaced.
As the parse
tells where a block starts and ends in SFC, the correct and faster way is using that offset to merge blocks like the original fix. The algorithm here is simple here.
<script>
and one <script setup>
here. We may have [0,2]
blocks actually.[b1, e1)
and [b2, e2)
. Blocks may never overlaps.c1
and c2
.[0, b1)
from the original, then c1
, then [e1, b2)
from the original, then c2
and finally [e2,code.length)
@fbartho I've submitted PR #135 to fix the problem without touching dependencies and shrimpwarp files anyway. Should be able to merge without conflicts.
Fixes https://github.com/trivago/prettier-plugin-sort-imports/issues/218