aero-vscode 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.vscodeignore +13 -0
- package/LICENSE +21 -0
- package/README.md +69 -0
- package/aero-vscode-0.0.1.vsix +0 -0
- package/dist/extension.js +19 -0
- package/images/logo.png +0 -0
- package/package.json +98 -0
- package/src/__tests__/analyzer.test.ts +202 -0
- package/src/__tests__/diagnostics.test.ts +964 -0
- package/src/__tests__/providers.test.ts +292 -0
- package/src/__tests__/utils.test.ts +120 -0
- package/src/analyzer.ts +914 -0
- package/src/completionProvider.ts +328 -0
- package/src/constants.ts +35 -0
- package/src/definitionProvider.ts +371 -0
- package/src/diagnostics.ts +732 -0
- package/src/extension.ts +74 -0
- package/src/hoverProvider.ts +134 -0
- package/src/pathResolver.ts +171 -0
- package/src/positionAt.ts +509 -0
- package/src/scope.ts +116 -0
- package/src/utils.ts +56 -0
- package/syntaxes/aero-attributes.json +54 -0
- package/syntaxes/aero-expressions.json +26 -0
- package/syntaxes/aero-globals.json +22 -0
- package/tsconfig.json +19 -0
- package/vitest.config.ts +7 -0
package/.vscodeignore
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 James M. Wilson (Aerobuilt)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Aero VS Code Extension
|
|
2
|
+
|
|
3
|
+
[](https://marketplace.visualstudio.com/items?itemName=aerobuilt.aero-vscode)
|
|
4
|
+
[](https://marketplace.visualstudio.com/items?itemName=aerobuilt.aero-vscode)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
Language support for Aero templates in HTML files: syntax highlighting, completions, hovers, definitions, and diagnostics for Aero expressions and components.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Syntax highlighting**
|
|
12
|
+
- Aero expressions in text (`{ props.title }`) and attributes (`title="{ meta.title }"`).
|
|
13
|
+
- Code inside `<script is:build>` and client script blocks is left as standard JS/TS.
|
|
14
|
+
|
|
15
|
+
- **Completions**
|
|
16
|
+
- Aero component names, path aliases, and props in HTML files.
|
|
17
|
+
- Triggered on `<`, `/`, `@`, `"`, and `'`.
|
|
18
|
+
|
|
19
|
+
- **Hover**
|
|
20
|
+
- Info for Aero expressions, props, and component usage.
|
|
21
|
+
|
|
22
|
+
- **Definitions**
|
|
23
|
+
- Jump to component/layout/page definitions via path aliases and imports.
|
|
24
|
+
|
|
25
|
+
- **Diagnostics**
|
|
26
|
+
- Warnings for invalid Aero expressions, missing props, and template errors (including `pass:data` and script-type scopes).
|
|
27
|
+
|
|
28
|
+
- **Scope mode** (`aero.scopeMode`)
|
|
29
|
+
- `auto` (default) — Features run in detected Aero projects and HTML files with Aero markers.
|
|
30
|
+
- `strict` — Features run only in detected Aero projects.
|
|
31
|
+
- `always` — Features run in all HTML files.
|
|
32
|
+
- Project detection uses Aero path aliases in `tsconfig.json`, Aero deps in `package.json`, and Aero-related `vite.config.*`.
|
|
33
|
+
|
|
34
|
+
- **Cache invalidation**
|
|
35
|
+
- Caches cleared when `tsconfig.json` changes or `aero.scopeMode` is updated.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
1. Open VS Code or Cursor.
|
|
40
|
+
2. Search for **Aero** in the Extensions view (`Ctrl+Shift+X` or `Cmd+Shift+X`).
|
|
41
|
+
3. Click Install.
|
|
42
|
+
|
|
43
|
+
## Repository
|
|
44
|
+
|
|
45
|
+
The source code for this extension is part of the [Aero Monorepo](https://github.com/aerobuilt/aero). Please file any issues or feature requests on the [GitHub Issues](https://github.com/aerobuilt/aero/issues) page.
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
Open an Aero template (e.g. `client/pages/about.html`, `client/components/meta.html`).
|
|
50
|
+
|
|
51
|
+
- Expressions like `{ props.title }` and `{ meta.title }` are highlighted.
|
|
52
|
+
- Attributes like `title="{ meta.title }"` show JS highlighting inside quotes.
|
|
53
|
+
- Code in `<script is:build>` and client `<script>` blocks is left unchanged.
|
|
54
|
+
- Completions, hovers, and definitions work for Aero components and props.
|
|
55
|
+
- Diagnostics appear for invalid expressions or missing props.
|
|
56
|
+
|
|
57
|
+
Plain HTML files without Aero markers do not get Aero features unless `aero.scopeMode` is set to `always`.
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
In VS Code settings, search for **Aero**:
|
|
62
|
+
|
|
63
|
+
- **aero.scopeMode** — Where Aero features are enabled: `auto`, `strict`, or `always`.
|
|
64
|
+
|
|
65
|
+
## Development
|
|
66
|
+
|
|
67
|
+
- Entry: `src/extension.ts`
|
|
68
|
+
- Build: `pnpm run build` (tsup, CJS, `dist/`)
|
|
69
|
+
- Test: `pnpm test` (Vitest)
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";var et=Object.create;var te=Object.defineProperty;var tt=Object.getOwnPropertyDescriptor;var nt=Object.getOwnPropertyNames;var st=Object.getPrototypeOf,ot=Object.prototype.hasOwnProperty;var it=(o,e)=>{for(var t in e)te(o,t,{get:e[t],enumerable:!0})},_e=(o,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let s of nt(e))!ot.call(o,s)&&s!==t&&te(o,s,{get:()=>e[s],enumerable:!(n=tt(e,s))||n.enumerable});return o};var O=(o,e,t)=>(t=o!=null?et(st(o)):{},_e(e||!o||!o.__esModule?te(t,"default",{value:o,enumerable:!0}):t,o)),rt=o=>_e(te({},"__esModule",{value:!0}),o);var Kt={};it(Kt,{activate:()=>Gt,deactivate:()=>Ft});module.exports=rt(Kt);var U=O(require("vscode"));var j=O(require("vscode"));var z=O(require("vscode"));var Z=/-(component|layout)$/,M=/((?:^|[\r\n;])\s*)import\s+(?:(\w+)|\{([^}]+)\}|\*\s+as\s+(\w+))\s+from\s+(['"])(.+?)\5/g,Re=/{([\s\S]+?)}/g;var $={site:"@content/site",theme:"@content/theme"},ne={language:"html",scheme:"file"},Se=[".html",".ts",".js",".json"];function oe(o,e){let n=o.lineAt(e.line).text,s=e.character,i=ct(n,e.line,s);if(i)return i;let r=at(n,e.line,s);if(r)return r;let c=lt(o,e);if(c)return c;let a=pt(o,e);return a||null}function ct(o,e,t){M.lastIndex=0;let n;for(;(n=M.exec(o))!==null;){let s=n.index,i=s+n[0].length;if(t<s||t>i)continue;let r=n[6],c=n[5],a=n[0].lastIndexOf(c+r+c),l=s+a+1,p=l+r.length;if(t>=l&&t<=p)return{kind:"import-path",specifier:r,range:new z.Range(e,l,e,p)};let m=n[2],x=n[3],f=n[4];if(m){let u=o.indexOf(m,s),d=u+m.length;if(t>=u&&t<=d)return{kind:"import-name",name:m,specifier:r,range:new z.Range(e,u,e,d)}}if(f){let u=o.indexOf(f,s),d=u+f.length;if(t>=u&&t<=d)return{kind:"import-name",name:f,specifier:r,range:new z.Range(e,u,e,d)}}if(x){let u=x.split(",").map(d=>d.trim());for(let d of u){if(!d)continue;let g=d.split(/\s+as\s+/)[0].trim(),v=o.indexOf(g,s),h=v+g.length;if(t>=v&&t<=h)return{kind:"import-name",name:g,specifier:r,range:new z.Range(e,v,e,h)}}}}return null}var Me=/<script[^>]*?\bsrc\s*=\s*(['"])(.*?)\1/gi,Ne=/<link[^>]*?\bhref\s*=\s*(['"])(.*?)\1/gi;function at(o,e,t){Me.lastIndex=0;let n;for(;(n=Me.exec(o))!==null;){let s=n[2],i=n.index+n[0].lastIndexOf(n[1]+s+n[1])+1,r=i+s.length;if(t>=i&&t<=r)return{kind:"script-src",value:s,range:new z.Range(e,i,e,r)}}for(Ne.lastIndex=0;(n=Ne.exec(o))!==null;){let s=n[2],i=n.index+n[0].lastIndexOf(n[1]+s+n[1])+1,r=i+s.length;if(t>=i&&t<=r)return{kind:"link-href",value:s,range:new z.Range(e,i,e,r)}}return null}var Le=/<\/?([a-z][a-z0-9]*(?:-[a-z0-9]+)*)/gi;function lt(o,e){let t=o.lineAt(e.line).text,n=e.character;Le.lastIndex=0;let s;for(;(s=Le.exec(t))!==null;){let i=s[1],r=s.index+s[0].length-i.length,c=r+i.length;if(n>=r&&n<=c){let a=Z.exec(i);if(a){let l=a[1],p=i.replace(Z,"");return{kind:"component-tag",tagName:i,baseName:p,suffix:l,range:new z.Range(e.line,r,e.line,c)}}}}return null}function pt(o,e){let t=o.lineAt(e.line).text,n=e.character,s=dt(o,e,t,n);if(!s)return null;let i=mt(t,n);if(!i)return null;let r=i.start+i.text.length;if(i.start<s.start||r>s.end)return null;let c=i.text.split("."),a=c[0];if(a in $){let m=n-i.start,x=0,f=0;for(let g=0;g<c.length;g++){let v=x+c[g].length;if(m<=v){f=g;break}x=v+1}let u=c.slice(1,f+1),d=i.start;for(let g=0;g<=f;g++)d+=c[g].length,g<f&&(d+=1);return{kind:"content-global",identifier:a,alias:$[a],propertyPath:u,range:new z.Range(e.line,i.start,e.line,d)}}let l=ht(t,e.line,n);return l?{kind:"expression-identifier",identifier:t.slice(l.start.character,l.end.character),range:l}:null}function dt(o,e,t,n){if(ft(o,e))return{start:0,end:t.length};if(gt(t,n))return{start:0,end:t.length};let s=ut(t,n);return s||null}function ft(o,e){let t=o.getText(),n=o.offsetAt(e),s=/<script\b([^>]*)>([\s\S]*?)<\/script>/gi,i;for(;(i=s.exec(t))!==null;){let r=(i[1]||"").toLowerCase();if(/\bsrc\s*=/.test(r))continue;let c=i[2]||"",a=i.index+i[0].indexOf(c),l=a+c.length;if(n>=a&&n<=l)return!0}return!1}function ut(o,e){let t=/\b(?:data-if|if|data-else-if|else-if|data-each|each)\s*=\s*(['"])(.*?)\1/gi,n;for(;(n=t.exec(o))!==null;){let s=n[2],i=n.index+n[0].lastIndexOf(n[1]+s+n[1])+1,r=s.indexOf("{"),c=s.lastIndexOf("}");if(r===-1||c===-1||c<=r)continue;let a=i+r+1,l=i+c;if(e>=a&&e<=l)return{start:a,end:l}}return null}function gt(o,e){let t=0;for(let n=e-1;n>=0;n--){let s=o[n];if(s==="}"&&t++,s==="{"){if(t===0)return!0;t--}}return!1}function mt(o,e){let t=o[e],n=e>0?o[e-1]:"";if(!se(t)&&t!=="."&&!se(n))return null;let s=e;for(;s>0;){let l=o[s-1];if(se(l)||l===".")s--;else break}let i=e;for(;i<o.length;){let l=o[i];if(se(l)||l===".")i++;else break}let r=o.slice(s,i),c=r.replace(/^\.+|\.+$/g,"");if(!c||!c.includes(".")&&!ze(c[0])){if(c&&ze(c[0])){let l=s+r.indexOf(c);return{text:c,start:l}}return null}let a=s+r.indexOf(c);return{text:c,start:a}}function se(o){return o?/[a-zA-Z0-9_$]/.test(o):!1}function ze(o){return o?/[a-zA-Z_$]/.test(o):!1}function ht(o,e,t){let n=/[a-zA-Z_$][a-zA-Z0-9_$]*/g,s;for(;(s=n.exec(o))!==null;){let i=s.index,r=i+s[0].length;if(t>=i&&t<=r)return new z.Range(e,i,e,r)}return null}var Ge=O(require("vscode")),D=O(require("path")),N=O(require("fs"));function xt(o){let e=o,t=D.parse(e).root;for(;e!==t;){let n=D.join(e,"tsconfig.json");if(N.existsSync(n))try{let i=N.readFileSync(n,"utf-8").replace(/\/\/.*$/gm,""),c=JSON.parse(i).compilerOptions||{},a=c.paths||{},l=c.baseUrl||".",p=D.resolve(D.dirname(n),l);return{tsconfigPath:n,paths:a,baseDir:p}}catch{}e=D.dirname(e)}return null}function vt(o,e){let t=[];for(let[n,s]of Object.entries(o)){let i=s[0];if(typeof i!="string"||i.length===0)continue;let r=n.replace(/\/*$/,"").replace("/*",""),c=i.replace(/\/*$/,"").replace("/*",""),a=D.resolve(e,c);t.push({find:r,replacement:a})}return t}var Ee=new Map;function K(o){let e=D.dirname(o.uri.fsPath),t=xt(e);if(!t)return{root:Ge.workspace.getWorkspaceFolder(o.uri)?.uri.fsPath||e,resolve(c,a){return Ve(c,a||o.uri.fsPath,this.root)}};let n=D.dirname(t.tsconfigPath),s=Ee.get(n);if(s)return s;let i=vt(t.paths,t.baseDir),r={root:n,resolve(c,a){if(!/^(https?:|data:|#|\/\/)/.test(c)){for(let l of i)if(c===l.find||c.startsWith(`${l.find}/`)){let p=c.slice(l.find.length),m=D.join(l.replacement,p);return Fe(m)}return Ve(c,a||o.uri.fsPath,n)}}};return Ee.set(n,r),r}function J(){Ee.clear()}function Ve(o,e,t){let n;if(o.startsWith("./")||o.startsWith("../"))n=D.resolve(D.dirname(e),o);else if(o.startsWith("/"))n=D.resolve(t,o.slice(1));else return;return Fe(n)}function Fe(o){if(N.existsSync(o)&&N.statSync(o).isFile())return o;for(let e of Se){let t=o+e;if(N.existsSync(t)&&N.statSync(t).isFile())return t}for(let e of Se){let t=D.join(o,`index${e}`);if(N.existsSync(t)&&N.statSync(t).isFile())return t}return o}var Y=O(require("vscode")),L=O(require("path")),ie=O(require("fs")),bt="aero",Rt="scopeMode",St=[/<script\b[^>]*\bon:(?:build|client)\b/i,/<[a-z][a-z0-9]*(?:-[a-z0-9]+)*-(?:component|layout)\b/i,/\bdata-(?:if|else-if|else|each|props)\b/],Ae=[/@aerobuilt/,/@components\/\*/,/@layouts\/\*/,/on:(?:build|client)/],ye=new Map;function H(){ye.clear()}function Et(){let o=Y.workspace.getConfiguration(bt).get(Rt,"auto");return o==="strict"||o==="always"?o:"auto"}function B(o){if(o.languageId!=="html"||o.uri.scheme!=="file")return!1;let e=Et();return e==="always"||wt(o.uri.fsPath)?!0:e==="strict"?!1:At(o.getText())}function At(o){return St.some(e=>e.test(o))}function wt(o){let e=L.dirname(o),t=Y.workspace.getWorkspaceFolder(Y.Uri.file(o))?.uri.fsPath,n=t?`${t}::${e}`:e,s=ye.get(n);if(s!==void 0)return s;let i=yt(e,t);return ye.set(n,i),i}function yt(o,e){let t=o,n=L.parse(t).root,s=e?L.resolve(e):n;for(;;){if(Ke(t))return!0;if(t===s||t===n)break;t=L.dirname(t)}return!!(e&&e!==s&&Ke(e))}function Ke(o){for(let e of["vite.config.ts","vite.config.js","vite.config.mts"])if(we(L.join(o,e),Ae))return!0;return!!(we(L.join(o,"tsconfig.json"),Ae)||we(L.join(o,"package.json"),Ae))}function we(o,e){try{if(!ie.existsSync(o))return!1;let t=ie.readFileSync(o,"utf-8");return e.some(n=>n.test(t))}catch{return!1}}var C=O(require("vscode"));function Q(o){return o.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g,e=>" ".repeat(e.length))}function ke(o,e){let t=/<!--[\s\S]*?-->/g,n;for(t.lastIndex=0;(n=t.exec(o))!==null;)if(e>=n.index&&e<n.index+n[0].length)return!0;return!1}function ee(o,e){let t=new Map,n=[],s=(a,l)=>{let p=t.get(a);p&&n.push({name:a,kind1:p.kind,kind2:l.kind,range:l.range}),t.set(a,l)};M.lastIndex=0;let i;for(;(i=M.exec(e))!==null;){if(ke(e,i.index))continue;let a=i[2]?.trim(),l=i[3],p=i[4]?.trim(),m=i[6],x=i.index;if(a){let f=x+i[0].indexOf(a);s(a,{name:a,range:new C.Range(o.positionAt(f),o.positionAt(f+a.length)),kind:"import"})}if(p){let f=x+i[0].indexOf(p);s(p,{name:p,range:new C.Range(o.positionAt(f),o.positionAt(f+p.length)),kind:"import"})}if(l){let f=x+i[0].indexOf(l),u=l.split(","),d=0;for(let g of u){let v=g.trim();if(!v){d+=g.length+1;continue}let h=v.indexOf(" as "),b=v;h>-1&&(b=v.slice(h+4).trim());let S=g.indexOf(b),w=f+d+g.indexOf(v)+(h>-1?h+4:0)+(b===v?0:v.indexOf(b)-(h>-1?h+4:0)),R=l.indexOf(g,d),I=g.lastIndexOf(b),A=f+R+I;b&&s(b,{name:b,range:new C.Range(o.positionAt(A),o.positionAt(A+b.length)),kind:"import"}),d=R+g.length}}}let r=/<script\b([^>]*)>([\s\S]*?)<\/script>/gi,c;for(;(c=r.exec(e))!==null;){if(ke(e,c.index))continue;let a=(c[1]||"").toLowerCase();if(/\bsrc\s*=/.test(a)||!/\bis:build\b/.test(a)||/\bis:inline\b/.test(a)||/\bis:blocking\b/.test(a))continue;let l=c[2],p=c.index+c[0].indexOf(l),m=Q(l),x=/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(\{[\s\S]*?\})?/g,f;for(;(f=x.exec(m))!==null;){let d=f[1],g=f[2],v=p+f.index+f[0].indexOf(d),h={name:d,range:new C.Range(o.positionAt(v),o.positionAt(v+d.length)),kind:"declaration"};if(g){let b=new Set,S=/([A-Za-z_$][\w$]*)\s*:/g,w;for(;(w=S.exec(g))!==null;)b.add(w[1]);let R=/(?:\{|,)\s*([A-Za-z_$][\w$]*)\s*(?:,|\})/g;for(;(w=R.exec(g))!==null;)b.add(w[1]);b.size>0&&(h.properties=b)}s(d,h)}let u=/\b(?:const|let|var)\s+\{([^}]+)\}\s*=/g;for(;(f=u.exec(m))!==null;){let d=f[1],g=p+f.index+f[0].indexOf(d),v=d.split(","),h=0;for(let b of v){let S=b.trim();if(!S){h+=b.length+1;continue}let w=S.indexOf(":"),R=S;w>-1&&(R=S.slice(w+1).trim());let I=d.indexOf(b,h),A=b.lastIndexOf(R),k=g+I+A;R&&s(R,{name:R,range:new C.Range(o.positionAt(k),o.positionAt(k+R.length)),kind:"declaration"}),h=I+b.length}}}return[t,n]}function Be(o,e,t){let n=new Map,s=(l,p)=>{n.set(l,p)},r={build:/\bis:build\b/,bundled:/(?!)/,inline:/\bis:inline\b/,blocking:/\bis:blocking\b/}[t],c=/<script\b([^>]*)>([\s\S]*?)<\/script>/gi,a;for(;(a=c.exec(e))!==null;){if(ke(e,a.index))continue;let l=a[1]||"",p=l.toLowerCase();if(/\bsrc\s*=/.test(p))continue;let m=r.test(p);if(t==="bundled"&&!m&&(/\bis:(build|inline|blocking)\b/.test(p)||(m=!0)),!m)continue;let x=a[2],f=a.index+a[0].indexOf(x),u=Q(x);if(t==="bundled"){let h=/pass:data\s*=\s*(['"])\{\{[\s\S]*?\}\}\1/gi,b;for(;(b=h.exec(l))!==null;){let S=b[0],w=b.index,R=/\{\{\s*([\s\S]*?)\s*\}\}/.exec(S);if(R){let I=R[1].split(",").map(A=>A.trim());for(let A of I)if(/^[a-zA-Z_$][\w$]*$/.test(A)){let T=a.index+a[0].indexOf(l)+w+R.index+R[0].indexOf(A),_=T+A.length;s(A,{name:A,range:new C.Range(o.positionAt(T),o.positionAt(_)),kind:"reference"})}}}}if(t!=="inline"){let h=Q(x);M.lastIndex=0;let b;for(;(b=M.exec(h))!==null;){let S=b[2]?.trim(),w=b[3],R=b[4]?.trim(),I=f+b.index;if(S){let A=I+b[0].indexOf(S);s(S,{name:S,range:new C.Range(o.positionAt(A),o.positionAt(A+S.length)),kind:"import"})}if(R){let A=I+b[0].indexOf(R);s(R,{name:R,range:new C.Range(o.positionAt(A),o.positionAt(A+R.length)),kind:"import"})}if(w){let A=I+b[0].indexOf(w),k=w.split(","),T=0;for(let _ of k){let F=_.trim();if(!F){T+=_.length+1;continue}let Te=F.indexOf(" as "),W=F;Te>-1&&(W=F.slice(Te+4).trim());let $e=w.indexOf(_,T),Qe=_.lastIndexOf(W),Pe=A+$e+Qe;W&&s(W,{name:W,range:new C.Range(o.positionAt(Pe),o.positionAt(Pe+W.length)),kind:"import"}),T=$e+_.length}}}}let d=/\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(\{[\s\S]*?\})?/g,g;for(;(g=d.exec(u))!==null;){let h=g[1],b=g[2],S=f+g.index+g[0].indexOf(h),w={name:h,range:new C.Range(o.positionAt(S),o.positionAt(S+h.length)),kind:"declaration"};if(b){let R=new Set,I=/([A-Za-z_$][\w$]*)\s*:/g,A;for(;(A=I.exec(b))!==null;)R.add(A[1]);let k=/(?:\{|,)\s*([A-Za-z_$][\w$]*)\s*(?:,|\})/g;for(;(A=k.exec(b))!==null;)R.add(A[1]);R.size>0&&(w.properties=R)}s(h,w)}let v=/\b(?:const|let|var)\s+\{([^}]+)\}\s*=/g;for(;(g=v.exec(u))!==null;){let h=g[1],b=f+g.index+g[0].indexOf(h),S=h.split(","),w=0;for(let R of S){let I=R.trim();if(!I){w+=R.length+1;continue}let A=I.indexOf(":"),k=I;A>-1&&(k=I.slice(A+1).trim());let T=h.indexOf(R,w),_=R.lastIndexOf(k),F=b+T+_;k&&s(k,{name:k,range:new C.Range(o.positionAt(F),o.positionAt(F+k.length)),kind:"declaration"}),w=T+R.length}}}return n}function re(o,e){let t=[],n=[],s=/<\/?([a-z][a-z0-9-]*)\b([^>]*?)>/gi,i;for(;(i=s.exec(e))!==null;){let r=i[0],c=r.startsWith("</"),a=i[1],l=i[2]||"",p=i.index,m=p+r.length;if(c){for(let u=n.length-1;u>=0;u--)if(n[u].tagName===a){let d=n.splice(u,1)[0];d.each&&t.push({...d.each,endOffset:m});break}continue}let x=It(o,l,p,r);if(/\/\s*>$/.test(r)){x&&t.push({...x,startOffset:p,endOffset:m});continue}n.push({tagName:a,each:x?{...x,startOffset:p}:void 0})}for(let r of n)r.each&&t.push({...r.each,endOffset:e.length});return t}function It(o,e,t,n){let s=/\b(?:data-)?each\s*=\s*(['"])(.*?)\1/i.exec(e);if(!s)return null;let i=s[2]||"",r=i,c=0,a=/^\s*\{([\s\S]*)\}\s*$/.exec(i);a&&(r=a[1],c=i.indexOf(r));let l=r.trim(),p=/^([A-Za-z_$][\w$]*)\s+in\s+([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*)$/.exec(l);if(!p)return null;let m=p[1],x=p[2],f=x.split(".")[0],u=n.indexOf(e),d=t+(u>=0?u:0),g=s[0].indexOf(i),v=d+s.index+g,h=c+r.indexOf(l),b=v+h,S=b+l.indexOf(m),w=S+m.length,R=b+l.lastIndexOf(x),I=R+x.length;return{itemName:m,itemRange:new C.Range(o.positionAt(S),o.positionAt(w)),sourceExpr:x,sourceRoot:f,sourceRange:new C.Range(o.positionAt(R),o.positionAt(I))}}function Ce(o,e){let t=[],n=e.replace(/<(script|style)\b[^>]*>([\s\S]*?)<\/\1>/gi,(c,a,l)=>c.replace(l," ".repeat(l.length)));n=n.replace(/<!--[\s\S]*?-->/g,c=>" ".repeat(c.length));let s=/<([a-zA-Z][a-zA-Z0-9-]*)\b([^>]*?)>/gi,i;for(;(i=s.exec(n))!==null;){let c=i[1];if(c==="script"||c==="style")continue;if(c.endsWith("-component")||c.endsWith("-layout")){let u=c.endsWith("-component")?"-component":"-layout",d=c.slice(0,-u.length),g=d.replace(/-([a-z])/g,v=>v[1].toUpperCase());t.push({content:g,range:new C.Range(o.positionAt(i.index+1),o.positionAt(i.index+1+d.length)),offset:i.index+1,isAttribute:!1,isComponent:!0})}let a=i[2],p=i.index+i[0].indexOf(a),m=(u,d)=>{n=n.substring(0,u)+" ".repeat(d)+n.substring(u+d)},x=/(?:\s|^)([a-zA-Z0-9-:@.]+)(?:(\s*=\s*)(['"])([\s\S]*?)\3)?/gi,f;for(;(f=x.exec(a))!==null;){let u=f[0],d=f[1],g=!!f[3],v=f[4]||"",h=f.index,b=u.indexOf(d),S=p+h+b,w=d.startsWith(":")||d.startsWith("@")||d.startsWith("x-");if(g){let R=f[3],I=u.indexOf(R,b+d.length),A=p+h+I+1;if(w)Ie(v,A,o,t,!0,!0),m(A,v.length);else{let k=/{([\s\S]+?)}/g,T;for(;(T=k.exec(v))!==null;){let _=T[1],F=A+T.index+1;Ie(_,F,o,t,!0)}m(A,v.length)}}else/^[a-zA-Z_$][\w$]*$/.test(d)&&!/^(if|else|return|function|var|let|const|import|from|as|in|true|false|null|undefined)$/.test(d)&&t.push({content:d,range:new C.Range(o.positionAt(S),o.positionAt(S+d.length)),offset:S,isAttribute:!0})}}Re.lastIndex=0;let r;for(;(r=Re.exec(n))!==null;){let c=r[1],a=r.index+r[0].indexOf(c);Ie(c,a,o,t,!1)}return t}function Ie(o,e,t,n,s,i){let r=o.replace(/(['"])(?:(?=(\\?))\2.)*?\1/g,l=>" ".repeat(l.length)),c=/\b([a-zA-Z_$][\w$]*)\b/g,a;for(;(a=c.exec(r))!==null;){let l=a[1];if(/^(if|else|return|function|var|let|const|import|from|as|in|true|false|null|undefined)$/.test(l))continue;let p=a.index;if((p>0?o[p-1]:"")==="."&&!(p>=3&&o.slice(p-3,p)==="..."))continue;let x=o.slice(p+l.length);if(/^\s*:\s*/.test(x))continue;let f=[],u=[],d=r.slice(p+l.length),g=p+l.length,v=/^\s*\.\s*([a-zA-Z_$][\w$]*)/,h;for(;h=v.exec(d);){let w=h[1];f.push(w);let R=h.index,I=h[0].lastIndexOf(w),A=e+g+R+I;u.push(new C.Range(t.positionAt(A),t.positionAt(A+w.length)));let k=h[0].length;d=d.slice(k),g+=k}let b=e+a.index,S={content:l,range:new C.Range(t.positionAt(b),t.positionAt(b+l.length)),offset:b,isAttribute:s,isAlpine:i};f.length>0&&(S.propertyPath=f,S.propertyRanges=u),n.push(S)}}function ce(o){return o.replace(/-([a-z])/g,(e,t)=>t.toUpperCase())}function ae(o){let e=new Map;M.lastIndex=0;let t;for(;(t=M.exec(o))!==null;){let n=t[2]?.trim(),s=t[3],i=t[4]?.trim(),r=t[6];if(n&&e.set(n,r),i&&e.set(i,r),!!s)for(let c of s.split(",")){let a=c.trim();if(!a)continue;let l=a.split(/\s+as\s+/i).map(m=>m.trim()),p=l[1]||l[0];p&&e.set(p,r)}}return e}function le(o,e){let t=null;for(let n of o){if(e<n.startOffset||e>n.endOffset)continue;if(!t){t=n;continue}let s=t.endOffset-t.startOffset;n.endOffset-n.startOffset<=s&&(t=n)}return t}var fe=class{provideDefinition(e,t,n){if(!B(e))return null;let s=oe(e,t);if(!s)return null;let i=K(e);if(!i)return null;switch(s.kind){case"import-path":{let r=i.resolve(s.specifier,e.uri.fsPath);return r?[X(s.range,r)]:null}case"import-name":{let r=i.resolve(s.specifier,e.uri.fsPath);return r?[X(s.range,r)]:null}case"script-src":case"link-href":{let r=(s.kind==="script-src",s.value),c=i.resolve(r,e.uri.fsPath);return c?[X(s.range,c)]:null}case"component-tag":{let r=ae(e.getText()),c=ce(s.baseName),l=r.get(c)||(s.suffix==="component"?`@components/${s.baseName}`:`@layouts/${s.baseName}`),p=i.resolve(l,e.uri.fsPath);return p?[X(s.range,p)]:null}case"content-global":{let r=i.resolve(s.alias,e.uri.fsPath);if(!r)return null;let c=s.propertyPath?ue(r,s.propertyPath):0;return[X(s.range,r,c)]}case"expression-identifier":return kt(e,t,s.identifier,s.range,i)}}};function kt(o,e,t,n,s){let i=o.getText(),r=o.offsetAt(e),c=o.lineAt(e.line).text,a=Xe(c,e.character),[l]=ee(o,i),p=re(o,i),m=le(p,r);if(m){if(t===m.itemName)return[pe(n,o.uri,m.itemRange)];if(t===m.sourceRoot){let d=l.get(m.sourceRoot);return d?[pe(n,o.uri,d.range)]:[pe(n,o.uri,m.sourceRange)]}let u=Xe(o.lineAt(e.line).text,e.character);if(u&&u.segments.length>=2&&u.segments[0]===m.itemName){let d=Ot(m.sourceExpr,l);if(d){let g=s?.resolve(d.alias,o.uri.fsPath);if(g){let v=[...d.propertyPath,...u.segments.slice(1)],h=ue(g,v);return[X(n,g,h)]}}}}let x=l.get(t);if(x)return[pe(n,o.uri,x.range)];let f=Ct(o,e,n,s,l,a);return f||null}function Ct(o,e,t,n,s,i){if(!i||i.segments.length<2)return null;let r=Dt(i.segments,i.start,e.character);if(r<=0)return null;let c=i.segments[0],a=i.segments.slice(1,r+1);if(c in $){let f=n?.resolve($[c],o.uri.fsPath);if(!f)return null;let u=ue(f,a);return[X(t,f,u)]}let l=s.get(c);if(!l?.contentRef)return null;let p=n?.resolve(l.contentRef.alias,o.uri.fsPath);if(!p)return null;let m=[...l.contentRef.propertyPath,...a],x=ue(p,m);return[X(t,p,x)]}function Ot(o,e){let t=/^([A-Za-z_$][\w$]*)(?:\.([A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)*))?$/.exec(o.trim());if(!t)return null;let n=t[1],s=t[2]?t[2].split("."):[];if(n in $)return{alias:$[n],propertyPath:s};let i=e.get(n);return i?.contentRef?{alias:i.contentRef.alias,propertyPath:[...i.contentRef.propertyPath,...s]}:null}function pe(o,e,t){return{originSelectionRange:o,targetUri:e,targetRange:t,targetSelectionRange:t}}function Xe(o,e){let t=o[e],n=e>0?o[e-1]:"";if(!de(t)&&t!=="."&&!de(n))return null;let s=e;for(;s>0&&(de(o[s-1])||o[s-1]===".");)s--;let i=e;for(;i<o.length&&(de(o[i])||o[i]===".");)i++;let r=o.slice(s,i).replace(/^\.+|\.+$/g,"");if(!r)return null;let c=r.split(".").filter(Boolean);if(!c.length)return null;let a=s+o.slice(s,i).indexOf(r);return{segments:c,start:a,end:a+r.length}}function de(o){return o?/[a-zA-Z0-9_$]/.test(o):!1}function Dt(o,e,t){let n=e;for(let s=0;s<o.length;s++){let r=n+o[s].length;if(t<=r)return s;n=r+1}return o.length-1}function X(o,e,t=0){let n=new j.Position(t,0);return{originSelectionRange:o,targetUri:j.Uri.file(e),targetRange:new j.Range(n,n),targetSelectionRange:new j.Range(n,n)}}function ue(o,e){try{let t=require("fs");if(!t.existsSync(o))return 0;let s=t.readFileSync(o,"utf-8").split(`
|
|
2
|
+
`),i=0,r=0;for(let c of e){let a=Tt(s,c,i,r);if(!a)break;i=a.line,r=a.depth+1}return i}catch{return 0}}function Tt(o,e,t,n){let s=new RegExp(`(?:^|\\s|,)(?:${Oe(e)}|'${Oe(e)}'|"${Oe(e)}")\\s*:`);for(let i=t;i<o.length;i++){let r=$t(o,i);if(!(r<n)&&s.test(o[i]))return{line:i,depth:r}}return null}function $t(o,e){let t=0;for(let n=0;n<e;n++)for(let s of o[n])s==="{"&&t++,s==="}"&&(t=Math.max(0,t-1));return t}function Oe(o){return o.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}var y=O(require("vscode")),q=O(require("path")),V=O(require("fs"));var Pt=[{label:"is:build",detail:"Build-time script block (Aero)",kind:y.CompletionItemKind.Property},{label:"is:inline",detail:"Inline client script, no bundling (Aero)",kind:y.CompletionItemKind.Property},{label:"data-each",detail:"Loop over items (Aero)",snippet:'data-each="{ ${1:item} in ${2:items} }"',kind:y.CompletionItemKind.Keyword},{label:"data-if",detail:"Conditional rendering (Aero)",snippet:'data-if="{ ${1:condition} }"',kind:y.CompletionItemKind.Keyword},{label:"data-else-if",detail:"Chained conditional (Aero)",snippet:'data-else-if="{ ${1:condition} }"',kind:y.CompletionItemKind.Keyword},{label:"data-else",detail:"Fallback conditional (Aero)",kind:y.CompletionItemKind.Keyword},{label:"data-props",detail:"Spread props to component (Aero)",kind:y.CompletionItemKind.Property},{label:"each",detail:"Loop over items (Aero shorthand)",snippet:'each="{ ${1:item} in ${2:items} }"',kind:y.CompletionItemKind.Keyword},{label:"if",detail:"Conditional rendering (Aero shorthand)",snippet:'if="{ ${1:condition} }"',kind:y.CompletionItemKind.Keyword},{label:"else-if",detail:"Chained conditional (Aero shorthand)",snippet:'else-if="{ ${1:condition} }"',kind:y.CompletionItemKind.Keyword},{label:"else",detail:"Fallback conditional (Aero shorthand)",kind:y.CompletionItemKind.Keyword}],ge=class{provideCompletionItems(e,t,n,s){if(!B(e))return null;let i=e.lineAt(t.line).text,r=i.slice(0,t.character),c=r.match(/<([a-z][a-z0-9-]*)$/);if(c)return this.getComponentTagCompletions(e,c[1]);if(this.isInsideTag(i,t.character))return this.getAttributeCompletions(r);let a=r.match(/from\s+['"]([^'"]*?)$/);return a?this.getImportPathCompletions(e,a[1]):this.isInsideExpression(r)?this.getExpressionCompletions():null}getComponentTagCompletions(e,t){let n=K(e);if(!n)return[];let s=[],i=q.join(n.root,"client","components");s.push(...this.scanDirForTags(i,"component",t));let r=q.join(n.root,"client","layouts");return s.push(...this.scanDirForTags(r,"layout",t)),s}scanDirForTags(e,t,n){let s=[];if(!V.existsSync(e))return s;try{let i=V.readdirSync(e);for(let r of i){if(!r.endsWith(".html"))continue;let l=`${r.replace(/\.html$/,"").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}-${t}`;if(!l.startsWith(n))continue;let p=new y.CompletionItem(l,t==="component"?y.CompletionItemKind.Class:y.CompletionItemKind.Struct);p.detail=`${t==="component"?"Component":"Layout"}: ${r}`,p.insertText=new y.SnippetString(`${l} $1/>
|
|
3
|
+
`),s.push(p)}}catch{}return s}getAttributeCompletions(e){return!/\s$/.test(e)&&!/=["'][^"']*$/.test(e)&&e.match(/\s([a-z-:]*)$/)?.[1]===void 0?[]:Pt.map(t=>{let n=new y.CompletionItem(t.label,t.kind);return n.detail=t.detail,t.snippet&&(n.insertText=new y.SnippetString(t.snippet)),n})}getImportPathCompletions(e,t){let n=K(e);if(!n)return[];let s=[];if(!t||t==="@"){let r=["@components/","@layouts/","@content/","@pages/","@styles/","@scripts/","@images/","@client/","@server/","~/"];for(let c of r)if(c.startsWith(t)){let a=new y.CompletionItem(c,y.CompletionItemKind.Folder);a.detail="Aero path alias",s.push(a)}return s}let i=n.resolve(t);if(i){let r=V.existsSync(i)&&V.statSync(i).isDirectory()?i:q.dirname(i);if(V.existsSync(r))try{let c=V.readdirSync(r);for(let a of c){let l=q.join(r,a);if(V.statSync(l).isDirectory()){let m=new y.CompletionItem(a+"/",y.CompletionItemKind.Folder);s.push(m)}else{let m=a.replace(/\.(html|ts|js|json)$/,""),x=new y.CompletionItem(m,y.CompletionItemKind.File);x.detail=a,s.push(x)}}}catch{}}return s}getExpressionCompletions(){let e=[];for(let[n,s]of Object.entries($)){let i=new y.CompletionItem(n,y.CompletionItemKind.Variable);i.detail=`Content global (${s})`,e.push(i)}let t=new y.CompletionItem("Aero",y.CompletionItemKind.Module);return t.detail="Aero runtime context",e.push(t),e}isInsideTag(e,t){let n=-1,s=-1;for(let i=0;i<t;i++)e[i]==="<"&&e[i+1]!=="/"&&(n=i),e[i]===">"&&(s=i);return n>s}isInsideExpression(e){let t=0;for(let n=e.length-1;n>=0;n--)if(e[n]==="}"&&t++,e[n]==="{"){if(t===0)return!0;t--}return!1}};var P=O(require("vscode")),he=O(require("fs"));var me=class{provideHover(e,t,n){if(!B(e))return null;let s=oe(e,t);if(!s)return null;let i=K(e);if(!i)return null;switch(s.kind){case"import-path":{let r=i.resolve(s.specifier,e.uri.fsPath);return r?new P.Hover(new P.MarkdownString(`**Import**: \`${s.specifier}\`
|
|
4
|
+
|
|
5
|
+
Resolved to: \`${r}\``),s.range):null}case"import-name":{let r=i.resolve(s.specifier,e.uri.fsPath);return r?new P.Hover(new P.MarkdownString(`**${s.name}** imported from \`${s.specifier}\`
|
|
6
|
+
|
|
7
|
+
Resolved to: \`${r}\``),s.range):null}case"script-src":case"link-href":{let r=(s.kind==="script-src",s.value),c=i.resolve(r,e.uri.fsPath);if(!c)return null;let a=s.kind==="script-src"?"Script source":"Link href";return new P.Hover(new P.MarkdownString(`**${a}**: \`${r}\`
|
|
8
|
+
|
|
9
|
+
Resolved to: \`${c}\``),s.range)}case"component-tag":{let r=s.suffix==="component"?`@components/${s.baseName}`:`@layouts/${s.baseName}`,c=i.resolve(r,e.uri.fsPath);if(!c)return null;let a=s.suffix==="component"?"Component":"Layout",l=new P.MarkdownString;l.appendMarkdown(`**${a}**: \`${s.tagName}\`
|
|
10
|
+
|
|
11
|
+
`),l.appendMarkdown(`File: \`${c}\`
|
|
12
|
+
|
|
13
|
+
`);let p=je(c,8);return p&&l.appendCodeblock(p,"html"),new P.Hover(l,s.range)}case"content-global":{let r=i.resolve(s.alias,e.uri.fsPath);if(!r)return null;let c=new P.MarkdownString,a=s.propertyPath.length>0?`${s.identifier}.${s.propertyPath.join(".")}`:s.identifier;c.appendMarkdown(`**Content global**: \`${a}\`
|
|
14
|
+
|
|
15
|
+
`),c.appendMarkdown(`Source: \`${r}\`
|
|
16
|
+
|
|
17
|
+
`);let l=je(r,12);if(l){let p=r.endsWith(".ts")?"typescript":"javascript";c.appendCodeblock(l,p)}return new P.Hover(c,s.range)}case"expression-identifier":return null}}};function je(o,e){try{if(!he.existsSync(o))return null;let n=he.readFileSync(o,"utf-8").split(`
|
|
18
|
+
`),s=n.slice(0,e);return n.length>e&&s.push("// ..."),s.join(`
|
|
19
|
+
`)}catch{return null}}var E=O(require("vscode")),Ye=O(require("fs"));var G="aero",Ue=/<script\b([^>]*)>([\s\S]*?)<\/script>/gi,_t=/\bis:(build|inline|blocking)\b/,Mt=/\bsrc\s*=/,Nt=/\b(?:data-)?if(?:\s*=)/,De=/\b(?:data-)?else-if(?:\s*=)/,Lt=/\b(?:data-)?else\b/,We=/<([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\b([^>]*?)\/?>/gi,Ze=/<\/?([a-z][a-z0-9]*(?:-[a-z0-9]+)*)\b([^>]*?)\/?>/gi,zt=new Set(["area","base","br","col","embed","hr","img","input","link","meta","param","source","track","wbr"]),He=/\b(data-if|if|data-else-if|else-if|data-each|each|data-props|props)\s*=\s*(['"])(.*?)\2/gi,qe=/<!--[\s\S]*?-->/g,Je=/<([a-z][a-z0-9]*(?:-[a-z0-9]+)*-(?:component|layout))\b[^>]*\/?>/gi,be=class{constructor(e){this.disposables=[];this.collection=E.languages.createDiagnosticCollection("aero"),this.disposables.push(E.workspace.onDidOpenTextDocument(t=>this.updateDiagnostics(t)),E.workspace.onDidSaveTextDocument(t=>this.updateDiagnostics(t)),E.workspace.onDidChangeTextDocument(t=>this.updateDiagnostics(t.document)),E.workspace.onDidCloseTextDocument(t=>this.collection.delete(t.uri)));for(let t of E.workspace.textDocuments)this.updateDiagnostics(t)}dispose(){this.collection.dispose();for(let e of this.disposables)e.dispose()}updateDiagnostics(e){if(!B(e)){this.collection.delete(e.uri);return}let t=[],n=e.getText();this.checkScriptTags(e,n,t),this.checkConditionalChains(e,n,t),this.checkDirectiveExpressionBraces(e,n,t),this.checkComponentReferences(e,n,t),this.checkUndefinedVariables(e,n,t),this.checkUnusedVariables(e,n,t),this.checkDuplicateDeclarations(e,n,t),this.collection.set(e.uri,t)}checkDirectiveExpressionBraces(e,t,n){let s=xe(t);We.lastIndex=0;let i;for(;(i=We.exec(t))!==null;){let r=i.index;if(ve(r,s))continue;let c=i[2]||"";if(!c)continue;He.lastIndex=0;let a;for(;(a=He.exec(c))!==null;){let l=a[1],p=(a[3]||"").trim();if(!this.requiresBracedDirectiveValue(l)||p.startsWith("{")&&p.endsWith("}"))continue;let f=r+i[0].indexOf(c)+a.index,u=f+a[0].length,d=`${l}="{ expression }"`,g=new E.Diagnostic(new E.Range(e.positionAt(f),e.positionAt(u)),`Directive \`${l}\` must use a braced expression, e.g. ${d}`,E.DiagnosticSeverity.Error);g.source=G,n.push(g)}}}requiresBracedDirectiveValue(e){return["if","data-if","else-if","data-else-if","each","data-each","props","data-props"].includes(e)}isInHead(e,t){let n=e.slice(0,t),s=n.match(/<head(?:\s|>)/),i=n.match(/<\/head\s*>/),r=s?s.index:-1,c=i?i.index:-1;return r>c}checkScriptTags(e,t,n){let s=xe(t);Ue.lastIndex=0;let i;for(;(i=Ue.exec(t))!==null;){let r=i.index;if(ve(r,s))continue;let c=i[1],a=i[2];if(Mt.test(c))continue;let l=t.slice(0,r),p=l.match(/<head(?:\s|>)/),m=l.match(/<\/head\s*>/),x=p?p.index:-1,f=m?m.index:-1;if(x>f)continue;let u=/\bimport\b/.test(a),d=/\btype\s*=\s*["']?module["']?\b/.test(c);if(u&&!d&&/\bis:inline\b/.test(c)&&!this.isInHead(t,r)){let g=r+i[0].indexOf(a),v=/\bimport\b/.exec(a);if(v){let h=g+v.index,b=h+6,S=new E.Diagnostic(new E.Range(e.positionAt(h),e.positionAt(b)),'Imports in <script is:inline> require type="module" attribute.',E.DiagnosticSeverity.Error);S.source=G,n.push(S)}}_t.test(c)||/\bpass:data\b/.test(c)}}checkConditionalChains(e,t,n){let s=new Map,i=0,r=xe(t);Ze.lastIndex=0;let c,a=p=>s.get(p)??null,l=(p,m)=>{s.set(p,m)};for(;(c=Ze.exec(t))!==null;){let p=c.index;if(ve(p,r))continue;let m=c[0],x=(c[1]||"").toLowerCase(),f=m.startsWith("</"),u=/\/\s*>$/.test(m)||zt.has(x);if(f){i=Math.max(0,i-1);continue}let d=i,g=a(d),v=c[2]||"";if(!v){l(d,null),u||(i+=1);continue}if(Nt.test(v)&&!De.test(v)){l(d,"if"),u||(i+=1);continue}if(De.test(v)){if(g!=="if"&&g!=="else-if"){let h=v.match(/(?:data-)?else-if\b/);if(h&&h.index!==void 0){let S=p+c[0].indexOf(v)+h.index,w=S+h[0].length,R=new E.Diagnostic(new E.Range(e.positionAt(S),e.positionAt(w)),"else-if must follow an element with if or else-if",E.DiagnosticSeverity.Error);R.source=G,n.push(R)}}l(d,"else-if"),u||(i+=1);continue}if(Lt.test(v)&&!De.test(v)){if(g!=="if"&&g!=="else-if"){let h=v.match(/(?:data-)?else\b/);if(h&&h.index!==void 0){let S=p+c[0].indexOf(v)+h.index,w=S+h[0].length,R=new E.Diagnostic(new E.Range(e.positionAt(S),e.positionAt(w)),"else must follow an element with if or else-if",E.DiagnosticSeverity.Error);R.source=G,n.push(R)}}l(d,null),u||(i+=1);continue}l(d,null),u||(i+=1)}}checkComponentReferences(e,t,n){let s=K(e);if(!s)return;let i=ae(t),r=xe(t);Je.lastIndex=0;let c;for(;(c=Je.exec(t))!==null;){let a=c.index;if(ve(a,r))continue;let l=c[1],p=Z.exec(l);if(!p)continue;let m=p[1],x=l.replace(Z,""),f=ce(x),u=i.get(f);if(!u){let g=e.positionAt(c.index),v=e.positionAt(c.index+c[0].length),h=new E.Diagnostic(new E.Range(g,v),`Component '${x}' is not imported. Explicit imports are required.`,E.DiagnosticSeverity.Error);h.source=G,n.push(h);continue}let d=s.resolve(u,e.uri.fsPath);if(d&&!Ye.existsSync(d)){let g=e.positionAt(c.index),v=e.positionAt(c.index+c[0].length),h=new E.Diagnostic(new E.Range(g,v),`${m==="component"?"Component":"Layout"} file not found: ${x}.html`,E.DiagnosticSeverity.Warning);h.source=G,n.push(h)}}}checkUndefinedVariables(e,t,n){let[s]=ee(e,t),i=re(e,t),r=Ce(e,t),c=new Set([...Object.keys($),"Aero","console","Math","JSON","Object","Array","String","Number","Boolean","Date","RegExp","Map","Set","WeakMap","WeakSet","Promise","Error","NaN","Infinity","undefined","null","true",!1,"window","document","globalThis","$el","$event","$data","$dispatch","$refs","$watch","$root","$nextTick","$tick","$store","$persist","$restore","$abi","$target","$trigger","$triggerElement","$response"]);for(let a of r){if(c.has(a.content)||a.isAlpine)continue;let l=s.get(a.content);if(l){if(l.properties&&a.propertyPath&&a.propertyPath.length>0){let d=a.propertyPath[0];if(!l.properties.has(d)){let g=a.propertyRanges&&a.propertyRanges.length>0?a.propertyRanges[0]:a.range,v=new E.Diagnostic(g,`Property '${d}' does not exist on type '${a.content}'`,E.DiagnosticSeverity.Error);v.source=G,n.push(v)}}continue}let p=le(i,a.offset);if(p&&p.itemName===a.content)continue;let m=p,x=!1;for(;m;){if(m.itemName===a.content){x=!0;break}m=Vt(i,m)}if(x)continue;let f=a.isComponent?`Component '${a.content}' is not defined`:`Variable '${a.content}' is not defined`,u=new E.Diagnostic(a.range,f,E.DiagnosticSeverity.Error);u.source=G,n.push(u)}}checkUnusedVariables(e,t,n){let s=Ce(e,t),i=new Set;for(let r of s)i.add(r.content);this.checkUnusedInScope(e,t,"build",i,n),this.checkUnusedInScope(e,t,"bundled",i,n),this.checkUnusedInScope(e,t,"inline",i,n),this.checkUnusedInScope(e,t,"blocking",i,n)}checkUnusedInScope(e,t,n,s,i){let r=Be(e,t,n),c=this.getScriptContentByScope(t,n),a=Q(c).replace(/(['"])(?:(?=(\\?))\2.)*?\1/g,()=>" ".repeat(20));for(let[l,p]of r){if(n==="build"){if(s.has(l))continue;let x=l.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),f=new RegExp(`\\b${x}\\b`,"g"),u=a.match(f);if(u&&u.length>1)continue}else if(n==="bundled"||n==="blocking"){let x=l.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),f=new RegExp(`\\b${x}\\b`,"g"),u=a.match(f);if(p.kind==="reference"){if(u&&u.length>=1)continue}else if(u&&u.length>1)continue}else{let x=l.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),f=new RegExp(`\\b${x}\\b`,"g"),u=a.match(f);if(u&&u.length>1)continue}let m=new E.Diagnostic(p.range,`'${l}' is declared but its value is never read.`,E.DiagnosticSeverity.Hint);m.tags=[E.DiagnosticTag.Unnecessary],m.source=G,i.push(m)}}getScriptContentByScope(e,t){let n={build:/\bis:build\b/,bundled:/(?!)/,inline:/\bis:inline\b/,blocking:/\bis:blocking\b/},s="",i=/<script\b([^>]*)>([\s\S]*?)<\/script>/gi,r;for(;(r=i.exec(e))!==null;){let a=(r[1]||"").toLowerCase();if(/\bsrc\s*=/.test(a))continue;let l=n[t].test(a);t==="bundled"&&!l&&!/\bis:build\b/.test(a)&&!/\bis:inline\b/.test(a)&&!/\bis:blocking\b/.test(a)&&(l=!0),l&&(s+=" "+r[2])}return s}checkDuplicateDeclarations(e,t,n){let[,s]=ee(e,t);for(let i of s){let r=new E.Diagnostic(i.range,`'${i.name}' is declared multiple times (as '${i.kind1}' and '${i.kind2}').`,E.DiagnosticSeverity.Error);r.source=G,n.push(r)}}};function Vt(o,e){let t=null;for(let n of o)if(n!==e&&e.startOffset>=n.startOffset&&e.endOffset<=n.endOffset){if(!t){t=n;continue}let s=t.endOffset-t.startOffset;n.endOffset-n.startOffset<=s&&(t=n)}return t}function xe(o){let e=[];qe.lastIndex=0;let t;for(;(t=qe.exec(o))!==null;)e.push({start:t.index,end:t.index+t[0].length});let n=/<(script|style)\b[^>]*>([\s\S]*?)<\/\1>/gi,s;for(;(s=n.exec(o))!==null;){let r=`</${s[1]}>`.length,c=s[2].length,a=s.index+s[0].length-r-c,l=a+c;e.push({start:a,end:l})}return e}function ve(o,e){for(let t of e)if(o>=t.start&&o<t.end)return!0;return!1}function Gt(o){o.subscriptions.push(U.languages.registerDefinitionProvider(ne,new fe)),o.subscriptions.push(U.languages.registerCompletionItemProvider(ne,new ge,"<","/","@",'"',"'")),o.subscriptions.push(U.languages.registerHoverProvider(ne,new me));let e=new be(o);o.subscriptions.push(e);let t=U.workspace.createFileSystemWatcher("**/tsconfig.json");t.onDidChange(()=>{J(),H()}),t.onDidCreate(()=>{J(),H()}),t.onDidDelete(()=>{J(),H()}),o.subscriptions.push(t),o.subscriptions.push(U.workspace.onDidChangeConfiguration(n=>{n.affectsConfiguration("aero.scopeMode")&&H()}))}function Ft(){J(),H()}0&&(module.exports={activate,deactivate});
|
package/images/logo.png
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aero-vscode",
|
|
3
|
+
"displayName": "Aero",
|
|
4
|
+
"icon": "images/logo.png",
|
|
5
|
+
"description": "Language support for Aero templates in HTML",
|
|
6
|
+
"version": "0.0.2",
|
|
7
|
+
"publisher": "aerobuilt",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/aerobuilt/aero.git",
|
|
11
|
+
"directory": "packages/aero-vscode"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/aerobuilt/aero/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"engines": {
|
|
18
|
+
"vscode": "^1.74.0"
|
|
19
|
+
},
|
|
20
|
+
"categories": [
|
|
21
|
+
"Programming Languages"
|
|
22
|
+
],
|
|
23
|
+
"keywords": [
|
|
24
|
+
"aero",
|
|
25
|
+
"aerobuilt",
|
|
26
|
+
"html",
|
|
27
|
+
"template",
|
|
28
|
+
"framework",
|
|
29
|
+
"syntax",
|
|
30
|
+
"highlight",
|
|
31
|
+
"completions",
|
|
32
|
+
"snippets"
|
|
33
|
+
],
|
|
34
|
+
"main": "./dist/extension.js",
|
|
35
|
+
"activationEvents": [
|
|
36
|
+
"onLanguage:html"
|
|
37
|
+
],
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"@types/node": "^25.3.0",
|
|
40
|
+
"@types/vscode": "^1.74.0",
|
|
41
|
+
"tsup": "^8.5.1",
|
|
42
|
+
"typescript": "^5.9.3",
|
|
43
|
+
"vitest": "^4.0.18"
|
|
44
|
+
},
|
|
45
|
+
"contributes": {
|
|
46
|
+
"configuration": {
|
|
47
|
+
"title": "Aero",
|
|
48
|
+
"properties": {
|
|
49
|
+
"aero.scopeMode": {
|
|
50
|
+
"type": "string",
|
|
51
|
+
"enum": [
|
|
52
|
+
"auto",
|
|
53
|
+
"strict",
|
|
54
|
+
"always"
|
|
55
|
+
],
|
|
56
|
+
"default": "auto",
|
|
57
|
+
"markdownDescription": "Controls where Aero language features run for HTML files. `auto` enables features in Aero projects and files with Aero markers, `strict` only in detected Aero projects, and `always` in all HTML files."
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
"grammars": [
|
|
62
|
+
{
|
|
63
|
+
"path": "./syntaxes/aero-expressions.json",
|
|
64
|
+
"scopeName": "aero.expressions.injection",
|
|
65
|
+
"injectTo": [
|
|
66
|
+
"text.html.basic",
|
|
67
|
+
"text.html.derivative"
|
|
68
|
+
],
|
|
69
|
+
"embeddedLanguages": {
|
|
70
|
+
"meta.embedded.expression.aero source.ts": "typescript"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"path": "./syntaxes/aero-attributes.json",
|
|
75
|
+
"scopeName": "aero.attributes.injection",
|
|
76
|
+
"injectTo": [
|
|
77
|
+
"text.html.basic",
|
|
78
|
+
"text.html.derivative"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"path": "./syntaxes/aero-globals.json",
|
|
83
|
+
"scopeName": "aero.globals.injection",
|
|
84
|
+
"injectTo": [
|
|
85
|
+
"text.html.basic",
|
|
86
|
+
"text.html.derivative"
|
|
87
|
+
]
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
"scripts": {
|
|
92
|
+
"dev": "tsup src/extension.ts --format cjs --out-dir dist --external vscode --watch",
|
|
93
|
+
"build": "tsup src/extension.ts --format cjs --out-dir dist --external vscode --minify",
|
|
94
|
+
"typecheck": "tsc --noEmit",
|
|
95
|
+
"test": "vitest run",
|
|
96
|
+
"vscode:prepublish": "cp ../../LICENSE ."
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the Aero VS Code analyzer (analyzer.ts): template references (components,
|
|
3
|
+
* attributes), defined variables (imports), template scopes (data-each), and variables by scope
|
|
4
|
+
* (e.g. pass:data in client/bundled scope). Mocks vscode Range/Position for offset→position conversion.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, vi } from 'vitest'
|
|
8
|
+
import {
|
|
9
|
+
collectTemplateReferences,
|
|
10
|
+
collectDefinedVariables,
|
|
11
|
+
collectTemplateScopes,
|
|
12
|
+
collectVariablesByScope,
|
|
13
|
+
} from '../analyzer'
|
|
14
|
+
|
|
15
|
+
vi.mock('vscode', () => {
|
|
16
|
+
return {
|
|
17
|
+
Range: class {
|
|
18
|
+
start: any
|
|
19
|
+
end: any
|
|
20
|
+
constructor(start: any, end: any) {
|
|
21
|
+
this.start = start
|
|
22
|
+
this.end = end
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
Position: class {
|
|
26
|
+
line: any
|
|
27
|
+
character: any
|
|
28
|
+
constructor(line: any, character: any) {
|
|
29
|
+
this.line = line
|
|
30
|
+
this.character = character
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
/** Component/attribute references in template; must skip HTML comments and structural directives (data-if, data-else, etc.) when not on elements. */
|
|
37
|
+
describe('collectTemplateReferences', () => {
|
|
38
|
+
const mockDoc = {
|
|
39
|
+
positionAt: (offset: number) => ({ line: 0, character: offset }),
|
|
40
|
+
} as any
|
|
41
|
+
|
|
42
|
+
it('should ignore components inside HTML comments', () => {
|
|
43
|
+
const text = `
|
|
44
|
+
<!-- <form-component /> -->
|
|
45
|
+
<!--<header-component></header-component>-->
|
|
46
|
+
<div></div>
|
|
47
|
+
`
|
|
48
|
+
const refs = collectTemplateReferences(mockDoc, text)
|
|
49
|
+
|
|
50
|
+
// Should find NO components because they are all commented out
|
|
51
|
+
const components = refs.filter(r => r.isComponent)
|
|
52
|
+
expect(components).toHaveLength(0)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should still find components outside comments', () => {
|
|
56
|
+
const text = `
|
|
57
|
+
<!-- <form-component /> -->
|
|
58
|
+
<header-component />
|
|
59
|
+
`
|
|
60
|
+
const refs = collectTemplateReferences(mockDoc, text)
|
|
61
|
+
|
|
62
|
+
const components = refs.filter(r => r.isComponent)
|
|
63
|
+
expect(components).toHaveLength(1)
|
|
64
|
+
expect(components[0].content).toBe('header')
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should ignore structural directives as standalone attributes', () => {
|
|
68
|
+
const text = `
|
|
69
|
+
<div data-if="true"></div>
|
|
70
|
+
<div data-else></div>
|
|
71
|
+
<div else></div>
|
|
72
|
+
<div if="false"></div>
|
|
73
|
+
`
|
|
74
|
+
const refs = collectTemplateReferences(mockDoc, text)
|
|
75
|
+
|
|
76
|
+
const attrs = refs.filter(r => r.isAttribute)
|
|
77
|
+
expect(attrs).toHaveLength(0)
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
/** Build-scope defined variables (imports, etc.) and pass:data variable positions in client (bundled) scope. */
|
|
82
|
+
describe('collectDefinedVariables', () => {
|
|
83
|
+
const mockDoc = {
|
|
84
|
+
positionAt: (offset: number) => ({ line: 0, character: offset }),
|
|
85
|
+
} as any
|
|
86
|
+
|
|
87
|
+
it('should correctly parse named imports in build scope', () => {
|
|
88
|
+
const text = `import { foo, bar } from 'pkg'`
|
|
89
|
+
const [buildScopeVars] = collectDefinedVariables(mockDoc, text)
|
|
90
|
+
|
|
91
|
+
expect(buildScopeVars.has('foo')).toBe(true)
|
|
92
|
+
expect(buildScopeVars.get('foo')?.kind).toBe('import')
|
|
93
|
+
expect(buildScopeVars.has('bar')).toBe(true)
|
|
94
|
+
expect(buildScopeVars.get('bar')?.kind).toBe('import')
|
|
95
|
+
|
|
96
|
+
expect(buildScopeVars.has('foo, bar')).toBe(false)
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('returns only build-scope variables (is:build), not client/bundled script vars', () => {
|
|
100
|
+
const text = `<script is:build>
|
|
101
|
+
import { buildOnly } from 'pkg'
|
|
102
|
+
const buildVar = 1
|
|
103
|
+
</script>
|
|
104
|
+
<script>
|
|
105
|
+
const clientVar = 2
|
|
106
|
+
</script>`
|
|
107
|
+
const [buildScopeVars] = collectDefinedVariables(mockDoc, text)
|
|
108
|
+
|
|
109
|
+
expect(buildScopeVars.has('buildOnly')).toBe(true)
|
|
110
|
+
expect(buildScopeVars.has('buildVar')).toBe(true)
|
|
111
|
+
expect(buildScopeVars.has('clientVar')).toBe(false)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('should calculate correct position for pass:data variables', () => {
|
|
115
|
+
const text = `<script pass:data="{{ isHomepage }}">
|
|
116
|
+
import { debug } from '@scripts/utils/debug'
|
|
117
|
+
</script>`
|
|
118
|
+
const vars = collectVariablesByScope(mockDoc, text, 'bundled')
|
|
119
|
+
|
|
120
|
+
expect(vars.has('isHomepage')).toBe(true)
|
|
121
|
+
const varInfo = vars.get('isHomepage')
|
|
122
|
+
expect(varInfo?.kind).toBe('reference')
|
|
123
|
+
expect(varInfo?.range.start.character).toBe(22) // position of 'i' in "isHomepage"
|
|
124
|
+
expect(varInfo?.range.end.character).toBe(32) // end of "isHomepage" (22 + 10)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should calculate correct position for pass:data with no spaces', () => {
|
|
128
|
+
const text = `<script pass:data="{{isHomepage}}"></script>`
|
|
129
|
+
const vars = collectVariablesByScope(mockDoc, text, 'bundled')
|
|
130
|
+
|
|
131
|
+
expect(vars.has('isHomepage')).toBe(true)
|
|
132
|
+
const varInfo = vars.get('isHomepage')
|
|
133
|
+
expect(varInfo?.range.start.character).toBe(21) // position of 'i' in "isHomepage" (no spaces)
|
|
134
|
+
expect(varInfo?.range.end.character).toBe(31)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
it('should calculate correct position for multiple pass:data variables', () => {
|
|
138
|
+
const text = `<script pass:data="{{ foo, bar }}"></script>`
|
|
139
|
+
const vars = collectVariablesByScope(mockDoc, text, 'bundled')
|
|
140
|
+
|
|
141
|
+
expect(vars.has('foo')).toBe(true)
|
|
142
|
+
expect(vars.has('bar')).toBe(true)
|
|
143
|
+
expect(vars.get('foo')?.range.start.character).toBe(22) // position of 'f' in "foo"
|
|
144
|
+
expect(vars.get('bar')?.range.start.character).toBe(27) // position of 'b' in "bar"
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
/** data-each / each scopes: item name and source expression; nested loops return inner-first. */
|
|
149
|
+
describe('collectTemplateScopes', () => {
|
|
150
|
+
const mockDoc = {
|
|
151
|
+
positionAt: (offset: number) => ({ line: 0, character: offset }),
|
|
152
|
+
} as any
|
|
153
|
+
|
|
154
|
+
it('should parse data-each attribute', () => {
|
|
155
|
+
const text = `
|
|
156
|
+
<ul>
|
|
157
|
+
<li data-each="{ item in items }">{item.name}</li>
|
|
158
|
+
</ul>
|
|
159
|
+
`
|
|
160
|
+
const scopes = collectTemplateScopes(mockDoc, text)
|
|
161
|
+
|
|
162
|
+
expect(scopes).toHaveLength(1)
|
|
163
|
+
expect(scopes[0].itemName).toBe('item')
|
|
164
|
+
expect(scopes[0].sourceExpr).toBe('items')
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('should parse shorthand each attribute', () => {
|
|
168
|
+
const text = `
|
|
169
|
+
<ul>
|
|
170
|
+
<li each="{ user in users }">{user.name}</li>
|
|
171
|
+
</ul>
|
|
172
|
+
`
|
|
173
|
+
const scopes = collectTemplateScopes(mockDoc, text)
|
|
174
|
+
|
|
175
|
+
expect(scopes).toHaveLength(1)
|
|
176
|
+
expect(scopes[0].itemName).toBe('user')
|
|
177
|
+
expect(scopes[0].sourceExpr).toBe('users')
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('should handle nested data-each scopes', () => {
|
|
181
|
+
const text = `
|
|
182
|
+
<div data-each="{ category in categories }">
|
|
183
|
+
<span data-each="{ item in category.items }">{item.name}</span>
|
|
184
|
+
</div>
|
|
185
|
+
`
|
|
186
|
+
const scopes = collectTemplateScopes(mockDoc, text)
|
|
187
|
+
|
|
188
|
+
expect(scopes).toHaveLength(2)
|
|
189
|
+
// Scopes are returned in closing order (inner first, then outer)
|
|
190
|
+
expect(scopes[0].itemName).toBe('item')
|
|
191
|
+
expect(scopes[1].itemName).toBe('category')
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should return empty array for no data-each', () => {
|
|
195
|
+
const text = `
|
|
196
|
+
<div>No loop here</div>
|
|
197
|
+
`
|
|
198
|
+
const scopes = collectTemplateScopes(mockDoc, text)
|
|
199
|
+
|
|
200
|
+
expect(scopes).toHaveLength(0)
|
|
201
|
+
})
|
|
202
|
+
})
|