74 行
2.8 KiB
TypeScript
74 行
2.8 KiB
TypeScript
// 已读取AGENTS.md
|
||
import * as fs from 'fs';
|
||
import * as path from 'path';
|
||
|
||
/**
|
||
* 第九十七批:小程序 Vue 单文件不得混入 TypeScript 语法。
|
||
* 背景:hall.vue 的 `<script setup>` 未声明 lang="ts",却写了 `(cls: string)`,
|
||
* 导致 HBuilderX/Vite 编译直接失败,只能在人工编译时发现。
|
||
*/
|
||
describe('第九十七批小程序脚本语法闸门', () => {
|
||
const root = path.resolve(__dirname, '../../pwuniapp');
|
||
|
||
const walk = (dir: string, out: string[]): string[] => {
|
||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||
if (entry.name === 'node_modules' || entry.name === 'unpackage' || entry.name.startsWith('.')) continue;
|
||
const full = path.join(dir, entry.name);
|
||
if (entry.isDirectory()) walk(full, out);
|
||
else if (entry.name.endsWith('.vue')) out.push(full);
|
||
}
|
||
return out;
|
||
};
|
||
|
||
it('未声明 lang="ts" 的 <script> 中不得出现 TS 类型标注', () => {
|
||
const problems: string[] = [];
|
||
const typeAnno = /\(\s*[A-Za-z_$][\w$]*\s*:\s*(string|number|boolean|any|unknown|void|Record|Array)\b/;
|
||
for (const file of walk(root, [])) {
|
||
const src = fs.readFileSync(file, 'utf-8');
|
||
const scriptMatch = src.match(/<script([^>]*)>([\s\S]*?)<\/script>/);
|
||
if (!scriptMatch) continue;
|
||
const attrs = scriptMatch[1] || '';
|
||
if (/lang\s*=\s*["']ts["']/.test(attrs)) continue;
|
||
if (typeAnno.test(scriptMatch[2])) {
|
||
problems.push(path.relative(root, file));
|
||
}
|
||
}
|
||
expect(problems).toEqual([]);
|
||
});
|
||
|
||
it('未声明 lang="scss" 的 <style> 中不得出现嵌套选择器,杜绝微信 WXSS unexpected { 编译报错', () => {
|
||
const problems: string[] = [];
|
||
for (const file of walk(root, [])) {
|
||
const src = fs.readFileSync(file, 'utf-8');
|
||
const styleMatches = src.matchAll(/<style([^>]*)>([\s\S]*?)<\/style>/g);
|
||
for (const m of styleMatches) {
|
||
const attrs = m[1] || '';
|
||
if (/lang\s*=\s*["']scss["']/.test(attrs)) continue;
|
||
const body = m[2];
|
||
const lines = body.split('\n');
|
||
let depth = 0;
|
||
let inAtRule = false;
|
||
for (let i = 0; i < lines.length; i++) {
|
||
const line = lines[i].trim();
|
||
if (line.startsWith('@keyframes') || line.startsWith('@media')) {
|
||
inAtRule = true;
|
||
}
|
||
for (const ch of line) {
|
||
if (ch === '{') {
|
||
depth++;
|
||
if (depth > 1 && !inAtRule) {
|
||
problems.push(`${path.relative(root, file)}: line ${i + 1}`);
|
||
break;
|
||
}
|
||
} else if (ch === '}') {
|
||
depth = Math.max(0, depth - 1);
|
||
if (depth === 0) inAtRule = false;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
expect(problems).toEqual([]);
|
||
});
|
||
});
|