nexa-compiler 0.7.2 → 0.7.3

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.
@@ -10,11 +10,12 @@ export declare function generateComponentCode(sfc: {
10
10
  setup?: boolean;
11
11
  scoped: boolean;
12
12
  hmrId?: string;
13
- }): string;
13
+ }, filename?: string): string;
14
14
  export declare function generateComponentCodeWithSourceMap(sfc: {
15
15
  template: string | null;
16
16
  script: string | null;
17
17
  style: string | null;
18
+ setup?: boolean;
18
19
  scoped: boolean;
19
20
  hmrId?: string;
20
21
  }, options?: GenerateOptions): {
@@ -2,7 +2,8 @@ import { transformStyle } from '../transform/style.js';
2
2
  import { generateSourceMap } from './sourcemap.js';
3
3
  import { parseDefineProps, parseDefineEmits, stripMacroLines, extractBindings, extractImportBindings } from './macros.js';
4
4
  import { generateRenderCode } from './template.js';
5
- export function generateComponentCode(sfc) {
5
+ import { validateTemplateBindings } from './validate.js';
6
+ export function generateComponentCode(sfc, filename) {
6
7
  const lines = [];
7
8
  const scriptContent = sfc.script || '';
8
9
  const importRegex = /import\s+[\s\S]*?\s+from\s+['"][^'"]+['"]|import\s+['"][^'"]+['"]/g;
@@ -163,6 +164,8 @@ export function generateComponentCode(sfc) {
163
164
  lines.push('');
164
165
  lines.push(`const _sfc_main = ${componentDefinition}`);
165
166
  if (sfc.template) {
167
+ const renderBindings = [...new Set([...allBindings, ...propsKeys])];
168
+ validateTemplateBindings(sfc.template, renderBindings, filename);
166
169
  lines.push('// Injected render function');
167
170
  lines.push('_sfc_main.render = function(ctx) {');
168
171
  const usedBuiltIns = ['Fragment'];
@@ -170,7 +173,6 @@ export function generateComponentCode(sfc) {
170
173
  usedBuiltIns.push('Teleport');
171
174
  if (sfc.template.includes('<Transition'))
172
175
  usedBuiltIns.push('Transition');
173
- const renderBindings = [...new Set([...allBindings, ...propsKeys])];
174
176
  const componentBindings = renderBindings.map(b => /^[A-Z]/.test(b) ? `${b}: _ntc_${b}` : b);
175
177
  const allRenderBindings = [...componentBindings, ...usedBuiltIns.map(b => `${b}: _ntc_${b}`)];
176
178
  if (allRenderBindings.length > 0) {
@@ -195,11 +197,10 @@ export function generateComponentCode(sfc) {
195
197
  return lines.join('\n');
196
198
  }
197
199
  export function generateComponentCodeWithSourceMap(sfc, options = {}) {
198
- const code = generateComponentCode(sfc);
200
+ const code = generateComponentCode(sfc, options.filename);
199
201
  if (!options.sourceMap || !options.filename || !options.source) {
200
202
  return { code, map: null };
201
203
  }
202
- const scriptLines = (options.source.split('\n<script')[0].match(/\n/g) || []).length + 2;
203
- const map = generateSourceMap(options.filename, code, options.source, scriptLines);
204
+ const map = generateSourceMap(options.filename, code, options.source);
204
205
  return { code, map };
205
206
  }
@@ -1 +1 @@
1
- export declare function generateSourceMap(filename: string, code: string, source: string, scriptLineCount: number): string;
1
+ export declare function generateSourceMap(filename: string, code: string, source: string): string | null;
@@ -17,7 +17,7 @@ function encodeVLQ(value) {
17
17
  }
18
18
  return result;
19
19
  }
20
- function encodeMappings(generatedLines, scriptLines, _templateLineStart) {
20
+ function encodeMappings(generatedLines, lineMap) {
21
21
  let result = '';
22
22
  let lastGenCol = 0;
23
23
  let lastSrcIdx = 0;
@@ -26,36 +26,74 @@ function encodeMappings(generatedLines, scriptLines, _templateLineStart) {
26
26
  for (let genLine = 0; genLine < generatedLines; genLine++) {
27
27
  if (genLine > 0)
28
28
  result += ';';
29
- if (genLine < scriptLines) {
30
- const srcLine = genLine;
31
- const genCol = 0;
32
- const srcCol = 0;
33
- const srcIdx = 0;
34
- const dGenCol = genCol - lastGenCol;
35
- const dSrcIdx = srcIdx - lastSrcIdx;
36
- const dSrcLine = srcLine - lastSrcLine;
37
- const dSrcCol = srcCol - lastSrcCol;
38
- result += encodeVLQ(dGenCol);
39
- result += encodeVLQ(dSrcIdx);
40
- result += encodeVLQ(dSrcLine);
41
- result += encodeVLQ(dSrcCol);
42
- lastGenCol = genCol;
43
- lastSrcIdx = srcIdx;
44
- lastSrcLine = srcLine;
45
- lastSrcCol = srcCol;
46
- }
29
+ const srcLine = lineMap[genLine];
30
+ const dGenCol = 0 - lastGenCol;
31
+ const dSrcIdx = 0 - lastSrcIdx;
32
+ const dSrcLine = srcLine - lastSrcLine;
33
+ const dSrcCol = 0 - lastSrcCol;
34
+ result += encodeVLQ(dGenCol);
35
+ result += encodeVLQ(dSrcIdx);
36
+ result += encodeVLQ(dSrcLine);
37
+ result += encodeVLQ(dSrcCol);
38
+ lastGenCol = 0;
39
+ lastSrcIdx = 0;
40
+ lastSrcLine = srcLine;
41
+ lastSrcCol = 0;
47
42
  }
48
43
  return result;
49
44
  }
50
- export function generateSourceMap(filename, code, source, scriptLineCount) {
51
- const generatedLines = code.split('\n').length;
45
+ export function generateSourceMap(filename, code, source) {
46
+ if (!code || !source)
47
+ return null;
48
+ const genLines = code.split('\n');
49
+ const srcLines = source.split('\n');
50
+ const generatedLines = genLines.length;
51
+ const templateOpen = srcLines.findIndex(l => l.trim().startsWith('<template'));
52
+ const templateClose = srcLines.findIndex(l => l.trim().startsWith('</template'));
53
+ const templateContentStart = templateOpen >= 0 ? templateOpen + 1 : 0;
54
+ const templateContentEnd = templateClose >= 0 ? templateClose - 1 : srcLines.length - 1;
55
+ const templateContentCount = Math.max(1, templateContentEnd - templateContentStart + 1);
56
+ const renderFuncLine = genLines.findIndex(l => /_sfc_main\.render\s*=/.test(l));
57
+ const lineMap = [];
58
+ if (renderFuncLine < 0) {
59
+ for (let i = 0; i < generatedLines; i++) {
60
+ lineMap.push(Math.min(i, srcLines.length - 1));
61
+ }
62
+ }
63
+ else {
64
+ let renderBodyStart = renderFuncLine + 1;
65
+ let renderDepth = 0;
66
+ let renderBodyEnd = genLines.length;
67
+ if (genLines[renderFuncLine].includes('{')) {
68
+ renderDepth = (genLines[renderFuncLine].match(/\{/g) || []).length;
69
+ renderDepth -= (genLines[renderFuncLine].match(/\}/g) || []).length;
70
+ }
71
+ for (let i = renderFuncLine + 1; i < genLines.length; i++) {
72
+ const opens = (genLines[i].match(/\{/g) || []).length;
73
+ const closes = (genLines[i].match(/\}/g) || []).length;
74
+ renderDepth += opens - closes;
75
+ if (renderDepth <= 0) {
76
+ renderBodyEnd = i;
77
+ break;
78
+ }
79
+ }
80
+ for (let genLine = 0; genLine < generatedLines; genLine++) {
81
+ if (genLine >= renderBodyStart && genLine < renderBodyEnd) {
82
+ const relativeLine = genLine - renderBodyStart;
83
+ lineMap.push(templateContentStart + (relativeLine % templateContentCount));
84
+ }
85
+ else {
86
+ lineMap.push(0);
87
+ }
88
+ }
89
+ }
52
90
  const map = {
53
91
  version: 3,
54
92
  file: filename.replace(/\.nexa$/, '.js'),
55
93
  sources: [filename],
56
94
  sourcesContent: [source],
57
95
  names: [],
58
- mappings: encodeMappings(generatedLines, scriptLineCount, 0),
96
+ mappings: encodeMappings(generatedLines, lineMap),
59
97
  };
60
98
  return JSON.stringify(map);
61
99
  }
@@ -0,0 +1 @@
1
+ export declare function validateTemplateBindings(template: string, knownBindings: string[], filename?: string): void;
@@ -0,0 +1,138 @@
1
+ const JS_GLOBALS = new Set([
2
+ 'Math', 'JSON', 'console', 'Object', 'Array', 'Number', 'String',
3
+ 'Boolean', 'Date', 'RegExp', 'Map', 'Set', 'Promise', 'Symbol',
4
+ 'parseInt', 'parseFloat', 'isNaN', 'isFinite', 'undefined', 'null',
5
+ 'true', 'false', 'this', 'NaN', 'Infinity', 'globalThis',
6
+ 'Error', 'TypeError', 'RangeError', 'SyntaxError', 'ReferenceError',
7
+ 'Intl', 'URL', 'URLSearchParams', 'AbortController', 'AbortSignal',
8
+ 'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
9
+ 'requestAnimationFrame', 'cancelAnimationFrame',
10
+ 'window', 'document', 'localStorage', 'sessionStorage',
11
+ 'fetch', 'navigator', 'history', 'location', 'location',
12
+ '$event', 'event',
13
+ 'h', 'hText', 'Fragment', 'Teleport', 'Transition',
14
+ 'in', 'of', 'typeof', 'instanceof', 'delete', 'void',
15
+ ]);
16
+ const PROPERTY_NAMES = new Set([
17
+ 'length', 'name', 'value', 'id', 'key', 'title', 'type', 'size',
18
+ 'x', 'y', 'w', 'h', 'top', 'bottom', 'left', 'right',
19
+ 'slice', 'charAt', 'charCodeAt', 'toUpperCase', 'toLowerCase', 'trim',
20
+ 'toString', 'toFixed', 'toPrecision', 'toExponential',
21
+ 'map', 'filter', 'reduce', 'reduceRight', 'forEach', 'find', 'findIndex',
22
+ 'push', 'pop', 'shift', 'unshift', 'splice', 'concat', 'join', 'split',
23
+ 'indexOf', 'lastIndexOf', 'includes', 'has', 'every', 'some', 'sort',
24
+ 'keys', 'values', 'entries', 'from', 'of',
25
+ 'prototype', 'constructor', 'call', 'apply', 'bind',
26
+ 'column', 'columns', 'data', 'row', 'rows', 'index', 'idx',
27
+ 'option', 'options', 'item', 'items', 'group', 'groups',
28
+ 'field', 'fields', 'label', 'labels', 'icon', 'icons',
29
+ 'footer', 'header', 'slot', 'scopedSlots',
30
+ 'message', 'messages', 'toast', 'toasts', 'tab', 'tabs',
31
+ 'step', 'page', 'pages', 'mode', 'theme',
32
+ ]);
33
+ export function validateTemplateBindings(template, knownBindings, filename) {
34
+ if (!template)
35
+ return;
36
+ const known = new Set(knownBindings);
37
+ const used = new Set();
38
+ const forLoopVars = new Set();
39
+ const mustacheRe = /\{\{(.*?)\}\}/g;
40
+ let m;
41
+ while ((m = mustacheRe.exec(template)) !== null) {
42
+ extractUsedIds(m[1].trim(), used, forLoopVars);
43
+ }
44
+ const attrRe = /([\w.-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g;
45
+ while ((m = attrRe.exec(template)) !== null) {
46
+ const attrName = m[1];
47
+ const value = m[2] ?? m[3];
48
+ if (!value)
49
+ continue;
50
+ if (attrName === 'v-for') {
51
+ const forParts = value.match(/^\s*(?:\(?\s*(\w+)\s*(?:,\s*(\w+))?\s*\)?\s+)?(?:in|of)\s+([\s\S]+)$/);
52
+ if (forParts) {
53
+ if (forParts[1])
54
+ forLoopVars.add(forParts[1]);
55
+ if (forParts[2])
56
+ forLoopVars.add(forParts[2]);
57
+ const listExpr = forParts[3].trim();
58
+ if (!/^\d+$/.test(listExpr)) {
59
+ extractUsedIds(listExpr, used, forLoopVars);
60
+ }
61
+ }
62
+ }
63
+ else if (attrName.startsWith(':') || attrName.startsWith('@') || attrName.startsWith('v-')) {
64
+ extractUsedIds(value, used, forLoopVars);
65
+ }
66
+ }
67
+ const missing = [];
68
+ for (const id of used) {
69
+ if (JS_GLOBALS.has(id))
70
+ continue;
71
+ if (PROPERTY_NAMES.has(id))
72
+ continue;
73
+ if (forLoopVars.has(id))
74
+ continue;
75
+ if (known.has(id))
76
+ continue;
77
+ missing.push(id);
78
+ }
79
+ if (missing.length > 0) {
80
+ const source = filename ? ` in ${filename}` : '';
81
+ const listed = [...new Set(missing)].sort().join(', ');
82
+ console.warn(`[nexa-compiler]${source} template references undefined binding(s): ${listed}. ` +
83
+ `Make sure these are declared in <script setup> or passed as props.`);
84
+ }
85
+ }
86
+ function extractUsedIds(expr, used, loopVars) {
87
+ let inString = null;
88
+ let prevDot = false;
89
+ let braceDepth = 0;
90
+ for (let i = 0; i < expr.length; i++) {
91
+ const c = expr[i];
92
+ if ((c === "'" || c === '"' || c === '`') && expr[i - 1] !== '\\') {
93
+ if (!inString)
94
+ inString = c;
95
+ else if (inString === c)
96
+ inString = null;
97
+ continue;
98
+ }
99
+ if (inString)
100
+ continue;
101
+ if (c === '{') {
102
+ braceDepth++;
103
+ continue;
104
+ }
105
+ if (c === '}') {
106
+ braceDepth = Math.max(0, braceDepth - 1);
107
+ prevDot = false;
108
+ continue;
109
+ }
110
+ if (c === '.') {
111
+ prevDot = true;
112
+ continue;
113
+ }
114
+ if (/[a-zA-Z_$]/.test(c)) {
115
+ const start = i;
116
+ while (i < expr.length && /[\w$]/.test(expr[i])) {
117
+ i++;
118
+ }
119
+ const ident = expr.slice(start, i);
120
+ if (ident && !prevDot) {
121
+ const isObjectKey = braceDepth > 0 && skipSpace(expr, i) === ':';
122
+ if (!isObjectKey) {
123
+ used.add(ident);
124
+ }
125
+ }
126
+ prevDot = false;
127
+ i--;
128
+ continue;
129
+ }
130
+ prevDot = false;
131
+ }
132
+ }
133
+ function skipSpace(s, i) {
134
+ while (i < s.length && (s[i] === ' ' || s[i] === '\t' || s[i] === '\n' || s[i] === '\r')) {
135
+ i++;
136
+ }
137
+ return s[i];
138
+ }
@@ -75,10 +75,19 @@ export function getErrorHelp(code) {
75
75
  const helpUrls = {
76
76
  E001: 'https://nexajs.dev/docs/compiler/errors#e001',
77
77
  E002: 'https://nexajs.dev/docs/compiler/errors#e002',
78
+ E003: 'https://nexajs.dev/docs/compiler/errors#e003',
79
+ E004: 'https://nexajs.dev/docs/compiler/errors#e004',
80
+ E005: 'https://nexajs.dev/docs/compiler/errors#e005',
81
+ E006: 'https://nexajs.dev/docs/compiler/errors#e006',
78
82
  E007: 'https://nexajs.dev/docs/compiler/errors#e007',
79
83
  E008: 'https://nexajs.dev/docs/compiler/errors#e008',
80
84
  E009: 'https://nexajs.dev/docs/compiler/errors#e009',
81
85
  E010: 'https://nexajs.dev/docs/compiler/errors#e010',
86
+ E011: 'https://nexajs.dev/docs/compiler/errors#e011',
87
+ E012: 'https://nexajs.dev/docs/compiler/errors#e012',
88
+ E013: 'https://nexajs.dev/docs/compiler/errors#e013',
89
+ E014: 'https://nexajs.dev/docs/compiler/errors#e014',
90
+ E015: 'https://nexajs.dev/docs/compiler/errors#e015',
82
91
  };
83
92
  return helpUrls[code];
84
93
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nexa-compiler",
3
- "version": "0.7.2",
3
+ "version": "0.7.3",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",