save code

This commit is contained in:
xsl
2026-01-26 08:04:53 +08:00
parent 0895398138
commit e4b59c5ee4
1238 changed files with 95107 additions and 1068 deletions
+16
View File
@@ -0,0 +1,16 @@
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
-v
--strict-markers
--cov=src
--cov-report=term-missing
--cov-report=html
asyncio_mode = auto
markers =
unit: 单元测试
integration: 集成测试
slow: 慢速测试
+7
View File
@@ -0,0 +1,7 @@
# 测试依赖
pytest==7.4.3
pytest-asyncio==0.21.1
pytest-cov==4.1.0
pytest-mock==3.12.0
httpx==0.25.2
aiosqlite==0.19.0
+339
View File
@@ -0,0 +1,339 @@
# 后端测试文档
## 1. 测试概述
### 1.1 测试目标
- 确保API接口功能正确
- 验证业务逻辑准确性
- 保证代码质量和稳定性
- 测试覆盖率达到80%以上
### 1.2 测试范围
- 单元测试:测试各个模块的独立功能
- 集成测试:测试API接口的端到端功能
- 性能测试:测试API响应时间(可选)
### 1.3 测试技术栈
| 技术 | 说明 |
|------|------|
| 测试框架 | pytest |
| 异步测试 | pytest-asyncio |
| HTTP测试 | httpx / TestClient |
| 数据库测试 | pytest-postgresql(可选) |
| 覆盖率 | pytest-cov |
| Mock | pytest-mock |
### 1.4 测试目录结构
```
backend/tests/
├── __init__.py
├── conftest.py # pytest配置和fixture
├── test_auth.py # 认证模块测试
├── test_users.py # 用户管理模块测试
├── test_projects.py # 项目管理模块测试
├── test_statistics.py # 统计功能测试
└── fixtures/ # 测试数据fixture
├── __init__.py
└── test_data.py
```
## 2. 测试环境配置
### 2.1 依赖安装
```bash
pip install pytest pytest-asyncio pytest-cov pytest-mock httpx
```
### 2.2 测试配置文件 pytest.ini
```ini
[pytest]
testpaths = tests
python_files = test_*.py
python_classes = Test*
python_functions = test_*
addopts =
-v
--strict-markers
--cov=src
--cov-report=term-missing
--cov-report=html
asyncio_mode = auto
markers =
unit: 单元测试
integration: 集成测试
slow: 慢速测试
```
### 2.3 环境变量
测试环境使用独立数据库配置:
```env
TEST_DB_HOST=localhost
TEST_DB_NAME=project_manager_test
TEST_DB_USER=test_user
TEST_DB_PASSWORD=test_password
```
## 3. 测试策略
### 3.1 单元测试
- 测试各个Service层方法
- 测试工具函数
- 不涉及数据库和网络调用
- 使用Mock隔离外部依赖
### 3.2 集成测试
- 测试完整的API端点
- 使用测试数据库
- 验证请求/响应格式
- 测试权限控制
### 3.3 测试数据管理
- 使用pytest fixture创建测试数据
- 每个测试独立运行,互不影响
- 测试后清理数据
## 4. 测试用例设计
### 4.1 认证模块测试 (test_auth.py)
#### 4.1.1 用户登录测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_login_success | 正确的用户名和密码 | 返回token和用户信息 |
| test_login_wrong_username | 错误的用户名 | 返回401错误 |
| test_login_wrong_password | 错误的密码 | 返回401错误 |
| test_login_missing_fields | 缺少必填字段 | 返回400错误 |
#### 4.1.2 Token验证测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_get_current_user_success | 有效token | 返回用户信息 |
| test_get_current_user_invalid_token | 无效token | 返回401错误 |
| test_get_current_user_expired_token | 过期token | 返回401错误 |
| test_logout_success | 有效token登出 | 返回成功 |
### 4.2 用户管理模块测试 (test_users.py)
#### 4.2.1 创建用户测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_create_user_admin_success | 管理员创建用户 | 返回创建的用户 |
| test_create_user_market_forbidden | 非管理员创建用户 | 返回403错误 |
| test_create_user_duplicate_username | 重复的用户名 | 返回409错误 |
| test_create_user_invalid_role | 无效的角色 | 返回400错误 |
#### 4.2.2 获取用户列表测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_get_users_admin_success | 管理员获取用户列表 | 返回用户列表 |
| test_get_users_market_forbidden | 非管理员获取用户列表 | 返回403错误 |
| test_get_users_with_pagination | 测试分页 | 返回正确的分页数据 |
| test_get_users_with_filter | 测试筛选 | 返回筛选后的数据 |
#### 4.2.3 更新用户测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_update_user_admin_success | 管理员更新用户 | 返回更新后的用户 |
| test_update_user_market_forbidden | 非管理员更新用户 | 返回403错误 |
| test_update_user_not_found | 更新不存在的用户 | 返回404错误 |
#### 4.2.4 删除用户测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_delete_user_admin_success | 管理员删除用户 | 返回成功 |
| test_delete_user_market_forbidden | 非管理员删除用户 | 返回403错误 |
| test_delete_user_not_found | 删除不存在的用户 | 返回404错误 |
#### 4.2.5 重置密码测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_reset_password_admin_success | 管理员重置密码 | 返回成功 |
| test_reset_password_market_forbidden | 非管理员重置密码 | 返回403错误 |
| test_reset_password_invalid_password | 无效的密码格式 | 返回400错误 |
### 4.3 项目管理模块测试 (test_projects.py)
#### 4.3.1 获取项目列表测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_get_projects_all_users_success | 所有用户获取项目列表 | 返回项目列表 |
| test_get_projects_with_pagination | 测试分页 | 返回正确的分页数据 |
| test_get_projects_with_filters | 测试组合筛选 | 返回筛选后的数据 |
| test_get_projects_sort_by_contract_amount | 按合同金额排序 | 返回排序后的数据 |
#### 4.3.2 创建项目测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_create_project_admin_success | 管理员创建项目 | 返回创建的项目 |
| test_create_project_market_success | 市场部用户创建项目 | 返回创建的项目 |
| test_create_project_other_forbidden | 其他部门用户创建项目 | 返回403错误 |
| test_create_project_duplicate_project_no | 重复的项目编号 | 返回409错误 |
| test_create_project_missing_required_fields | 缺少必填字段 | 返回400错误 |
#### 4.3.3 更新项目测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_update_project_admin_success | 管理员更新项目 | 返回更新后的项目 |
| test_update_project_market_own_project | 市场部更新自己的项目 | 返回更新后的项目 |
| test_update_project_market_other_project | 市场部更新其他人的项目 | 返回403错误 |
| test_update_project_other_financial_info | 其他部门更新财务信息 | 返回更新后的项目 |
| test_update_project_other_basic_info | 其他部门更新基础信息 | 返回403错误 |
| test_update_project_not_found | 更新不存在的项目 | 返回404错误 |
#### 4.3.4 删除项目测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_delete_project_admin_success | 管理员删除项目 | 返回成功 |
| test_delete_project_market_own_project | 市场部删除自己的项目 | 返回成功 |
| test_delete_project_market_other_project | 市场部删除其他人的项目 | 返回403错误 |
| test_delete_project_other_forbidden | 其他部门删除项目 | 返回403错误 |
| test_delete_project_not_found | 删除不存在的项目 | 返回404错误 |
### 4.4 统计功能测试 (test_statistics.py)
#### 4.4.1 基础统计测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_get_statistics_all_users_success | 所有用户获取统计 | 返回统计数据 |
| test_get_statistics_with_filters | 测试筛选条件 | 返回筛选后的统计 |
| test_get_statistics_empty_data | 无数据时统计 | 返回零值统计 |
#### 4.4.2 分组统计测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_get_group_statistics_by_engineering_type | 按工程类别分组 | 返回分组统计 |
| test_get_group_statistics_by_department | 按项目部分组 | 返回分组统计 |
| test_get_group_statistics_with_date_filter | 带日期筛选的分组统计 | 返回分组统计 |
#### 4.4.3 时间维度统计测试
| 测试用例 | 描述 | 预期结果 |
|---------|------|---------|
| test_get_timeline_statistics_by_month | 按月统计 | 返回时间维度统计 |
| test_get_timeline_statistics_by_year | 按年统计 | 返回时间维度统计 |
| test_get_timeline_statistics_with_filter | 带筛选的时间统计 | 返回时间维度统计 |
## 5. 测试Fixture
### 5.1 数据库Fixture
```python
@pytest.fixture
async def db_session():
# 创建测试数据库会话
# 测试结束后回滚
yield session
# 清理数据
```
### 5.2 用户Fixture
```python
@pytest.fixture
async def admin_user(db_session):
# 创建管理员用户
return user
@pytest.fixture
async def market_user(db_session):
# 创建市场部用户
return user
@pytest.fixture
async def other_user(db_session):
# 创建其他部门用户
return user
```
### 5.3 项目Fixture
```python
@pytest.fixture
async def test_project(db_session, admin_user):
# 创建测试项目
return project
```
### 5.4 Token Fixture
```python
@pytest.fixture
async def admin_token(client, admin_user):
# 获取管理员token
return token
```
## 6. 运行测试
### 6.1 运行所有测试
```bash
cd backend
pytest tests/
```
### 6.2 运行特定测试文件
```bash
pytest tests/test_auth.py
```
### 6.3 运行特定测试用例
```bash
pytest tests/test_auth.py::test_login_success
```
### 6.4 生成覆盖率报告
```bash
pytest tests/ --cov=src --cov-report=html
```
### 6.5 运行集成测试
```bash
pytest tests/ -m integration
```
### 6.6 运行单元测试
```bash
pytest tests/ -m unit
```
## 7. 持续集成
### 7.1 测试流程
1. 提交代码前运行单元测试
2. CI/CD流水线运行完整测试套件
3. 生成覆盖率报告
4. 测试通过才能合并代码
### 7.2 测试覆盖率要求
- 单元测试覆盖率 > 80%
- 关键业务逻辑覆盖率 > 90%
- API接口覆盖率 100%
## 8. 测试最佳实践
### 8.1 测试命名规范
- 测试文件: `test_<模块名>.py`
- 测试类: `Test<功能名>`
- 测试方法: `test_<功能>_<场景>`
### 8.2 测试编写原则
- 每个测试只验证一个功能点
- 测试之间相互独立
- 使用描述性的测试名称
- 遵循AAA模式:Arrange, Act, Assert
### 8.3 Mock使用
- Mock外部依赖(数据库、网络等)
- 不要Mock被测试的代码
- 保持Mock的行为真实
## 9. 常见问题
### 9.1 数据库连接失败
检查测试数据库配置,确保测试数据库已创建。
### 9.2 测试数据冲突
使用pytest fixture和scope控制测试数据的生命周期。
### 9.3 异步测试失败
使用`pytest-asyncio`,确保测试函数使用`async def`定义。
---
**文档维护**: 后端程序员
**更新时间**: 2026-01-25
+1
View File
@@ -0,0 +1 @@
# Backend Tests Package
+165
View File
@@ -0,0 +1,165 @@
import pytest
import asyncio
from typing import AsyncGenerator, Generator
from httpx import AsyncClient
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from sqlalchemy.pool import StaticPool
from src.main import app
from src.config.database import Base, get_db
from src.models.user import User
from src.utils.password import hash_password
# 测试数据库配置
TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:"
# 创建测试引擎
engine = create_async_engine(
TEST_DATABASE_URL,
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
# 创建测试会话工厂
TestingSessionLocal = async_sessionmaker(
engine, expire_on_commit=False, class_=AsyncSession
)
@pytest.fixture(scope="function")
async def db_session() -> AsyncGenerator[AsyncSession, None]:
"""创建测试数据库会话"""
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async with TestingSessionLocal() as session:
yield session
await session.rollback()
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
@pytest.fixture(scope="function")
async def client(db_session: AsyncSession) -> AsyncGenerator[AsyncClient, None]:
"""创建测试客户端"""
async def override_get_db():
yield db_session
app.dependency_overrides[get_db] = override_get_db
async with AsyncClient(app=app, base_url="http://test") as ac:
yield ac
app.dependency_overrides.clear()
@pytest.fixture
async def admin_user(db_session: AsyncSession) -> User:
"""创建管理员用户"""
user = User(
username="admin",
password_hash=hash_password("admin123"),
real_name="系统管理员",
department="管理部",
role="admin",
email="admin@test.com",
phone="13800000001",
is_active=True,
)
db_session.add(user)
await db_session.commit()
await db_session.refresh(user)
return user
@pytest.fixture
async def market_user(db_session: AsyncSession) -> User:
"""创建市场部用户"""
user = User(
username="market",
password_hash=hash_password("market123"),
real_name="市场部用户",
department="市场部",
role="market",
email="market@test.com",
phone="13800000002",
is_active=True,
)
db_session.add(user)
await db_session.commit()
await db_session.refresh(user)
return user
@pytest.fixture
async def other_user(db_session: AsyncSession) -> User:
"""创建其他部门用户"""
user = User(
username="other",
password_hash=hash_password("other123"),
real_name="其他部门用户",
department="技术部",
role="other",
email="other@test.com",
phone="13800000003",
is_active=True,
)
db_session.add(user)
await db_session.commit()
await db_session.refresh(user)
return user
@pytest.fixture
async def inactive_user(db_session: AsyncSession) -> User:
"""创建未激活用户"""
user = User(
username="inactive",
password_hash=hash_password("inactive123"),
real_name="未激活用户",
department="市场部",
role="market",
email="inactive@test.com",
phone="13800000004",
is_active=False,
)
db_session.add(user)
await db_session.commit()
await db_session.refresh(user)
return user
@pytest.fixture
async def admin_token(client: AsyncClient, admin_user: User) -> str:
"""获取管理员token"""
response = await client.post(
"/api/v1/auth/login",
json={"username": "admin", "password": "admin123"},
)
data = response.json()
return data["data"]["token"]
@pytest.fixture
async def market_token(client: AsyncClient, market_user: User) -> str:
"""获取市场部用户token"""
response = await client.post(
"/api/v1/auth/login",
json={"username": "market", "password": "market123"},
)
data = response.json()
return data["data"]["token"]
@pytest.fixture
async def other_token(client: AsyncClient, other_user: User) -> str:
"""获取其他部门用户token"""
response = await client.post(
"/api/v1/auth/login",
json={"username": "other", "password": "other123"},
)
data = response.json()
return data["data"]["token"]
+151
View File
@@ -0,0 +1,151 @@
import pytest
from httpx import AsyncClient
@pytest.mark.asyncio
@pytest.mark.unit
async def test_login_success(client: AsyncClient, admin_user):
"""测试用户登录成功"""
response = await client.post(
"/api/v1/auth/login",
json={"username": "admin", "password": "admin123"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["message"] == "登录成功"
assert "token" in data["data"]
assert data["data"]["user"]["username"] == "admin"
assert data["data"]["user"]["role"] == "admin"
@pytest.mark.asyncio
@pytest.mark.unit
async def test_login_wrong_username(client: AsyncClient, admin_user):
"""测试错误的用户名"""
response = await client.post(
"/api/v1/auth/login",
json={"username": "wrong", "password": "admin123"},
)
assert response.status_code == 401
data = response.json()
assert data["success"] is False
assert data["error_code"] == "1002"
@pytest.mark.asyncio
@pytest.mark.unit
async def test_login_wrong_password(client: AsyncClient, admin_user):
"""测试错误的密码"""
response = await client.post(
"/api/v1/auth/login",
json={"username": "admin", "password": "wrong"},
)
assert response.status_code == 401
data = response.json()
assert data["success"] is False
assert data["error_code"] == "1002"
@pytest.mark.asyncio
@pytest.mark.unit
async def test_login_missing_username(client: AsyncClient, admin_user):
"""测试缺少用户名"""
response = await client.post(
"/api/v1/auth/login",
json={"password": "admin123"},
)
assert response.status_code == 400
data = response.json()
assert data["success"] is False
assert data["error_code"] == "1001"
@pytest.mark.asyncio
@pytest.mark.unit
async def test_login_missing_password(client: AsyncClient, admin_user):
"""测试缺少密码"""
response = await client.post(
"/api/v1/auth/login",
json={"username": "admin"},
)
assert response.status_code == 400
data = response.json()
assert data["success"] is False
assert data["error_code"] == "1001"
@pytest.mark.asyncio
@pytest.mark.unit
async def test_login_inactive_user(client: AsyncClient, inactive_user):
"""测试未激活用户登录"""
response = await client.post(
"/api/v1/auth/login",
json={"username": "inactive", "password": "inactive123"},
)
assert response.status_code == 401
data = response.json()
assert data["success"] is False
@pytest.mark.asyncio
@pytest.mark.unit
async def test_get_current_user_success(client: AsyncClient, admin_token: str):
"""测试获取当前用户信息成功"""
response = await client.get(
"/api/v1/auth/me",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["data"]["username"] == "admin"
assert data["data"]["role"] == "admin"
@pytest.mark.asyncio
@pytest.mark.unit
async def test_get_current_user_no_token(client: AsyncClient):
"""测试未提供token获取用户信息"""
response = await client.get("/api/v1/auth/me")
assert response.status_code == 401
data = response.json()
assert data["success"] is False
@pytest.mark.asyncio
@pytest.mark.unit
async def test_get_current_user_invalid_token(client: AsyncClient):
"""测试无效token获取用户信息"""
response = await client.get(
"/api/v1/auth/me",
headers={"Authorization": "Bearer invalid_token"},
)
assert response.status_code == 401
data = response.json()
assert data["success"] is False
assert data["error_code"] == "1003"
@pytest.mark.asyncio
@pytest.mark.unit
async def test_logout_success(client: AsyncClient, admin_token: str):
"""测试登出成功"""
response = await client.post(
"/api/v1/auth/logout",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["message"] == "登出成功"
+544
View File
@@ -0,0 +1,544 @@
import pytest
from httpx import AsyncClient
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_projects_all_users_success(
client: AsyncClient, admin_token: str, db_session
):
"""测试所有用户获取项目列表成功"""
# 创建测试项目
from src.models.project import Project
from datetime import date
project = Project(
project_no="PRJ001",
name="测试项目",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=1,
)
db_session.add(project)
await db_session.commit()
response = await client.get(
"/api/v1/projects",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert "items" in data["data"]
assert "total" in data["data"]
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_projects_with_pagination(
client: AsyncClient, admin_token: str, db_session
):
"""测试项目列表分页"""
from src.models.project import Project
from datetime import date
# 创建5个测试项目
for i in range(5):
project = Project(
project_no=f"PRJ{i:03d}",
name=f"测试项目{i}",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=1,
)
db_session.add(project)
await db_session.commit()
response = await client.get(
"/api/v1/projects?page=1&page_size=3",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["data"]["page"] == 1
assert data["data"]["page_size"] == 3
assert len(data["data"]["items"]) == 3
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_projects_with_filters(
client: AsyncClient, admin_token: str, db_session
):
"""测试项目列表组合筛选"""
from src.models.project import Project
from datetime import date
# 创建不同类型的项目
project1 = Project(
project_no="PRJ001",
name="基建项目",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=1,
)
project2 = Project(
project_no="PRJ002",
name="业扩项目",
engineering_type="业扩",
contract_amount=200.0,
signing_date=date(2026, 2, 1),
created_by=1,
)
db_session.add(project1)
db_session.add(project2)
await db_session.commit()
# 筛选:工程类别=基建 AND 签订日期>=2026-01-01
response = await client.get(
"/api/v1/projects?engineering_type=基建&signing_date_start=2026-01-01",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert len(data["data"]["items"]) == 1
assert data["data"]["items"][0]["engineering_type"] == "基建"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_projects_sort_by_contract_amount(
client: AsyncClient, admin_token: str, db_session
):
"""测试按合同金额排序"""
from src.models.project import Project
from datetime import date
# 创建不同金额的项目
project1 = Project(
project_no="PRJ001",
name="项目1",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=1,
)
project2 = Project(
project_no="PRJ002",
name="项目2",
engineering_type="基建",
contract_amount=200.0,
signing_date=date(2026, 1, 1),
created_by=1,
)
db_session.add(project1)
db_session.add(project2)
await db_session.commit()
response = await client.get(
"/api/v1/projects?sort_by=contract_amount&sort_order=desc",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["data"]["items"][0]["contract_amount"] == 200.0
assert data["data"]["items"][1]["contract_amount"] == 100.0
@pytest.mark.asyncio
@pytest.mark.integration
async def test_create_project_admin_success(
client: AsyncClient, admin_token: str, admin_user
):
"""测试管理员创建项目成功"""
response = await client.post(
"/api/v1/projects",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"project_no": "PRJ001",
"name": "新项目",
"engineering_type": "基建",
"contract_amount": 100.0,
"signing_date": "2026-01-01",
},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["message"] == "项目创建成功"
assert data["data"]["project_no"] == "PRJ001"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_create_project_market_success(
client: AsyncClient, market_token: str, market_user
):
"""测试市场部用户创建项目成功"""
response = await client.post(
"/api/v1/projects",
headers={"Authorization": f"Bearer {market_token}"},
json={
"project_no": "PRJ002",
"name": "市场部项目",
"engineering_type": "业扩",
"contract_amount": 200.0,
"signing_date": "2026-01-01",
},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
@pytest.mark.asyncio
@pytest.mark.integration
async def test_create_project_other_forbidden(
client: AsyncClient, other_token: str, other_user
):
"""测试其他部门用户创建项目被禁止"""
response = await client.post(
"/api/v1/projects",
headers={"Authorization": f"Bearer {other_token}"},
json={
"project_no": "PRJ003",
"name": "其他部门项目",
"engineering_type": "基建",
"contract_amount": 100.0,
},
)
assert response.status_code == 403
data = response.json()
assert data["success"] is False
assert data["error_code"] == "3001"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_create_project_duplicate_project_no(
client: AsyncClient, admin_token: str, db_session, admin_user
):
"""测试创建重复项目编号"""
from src.models.project import Project
from datetime import date
project = Project(
project_no="PRJ001",
name="已存在项目",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
db_session.add(project)
await db_session.commit()
response = await client.post(
"/api/v1/projects",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"project_no": "PRJ001",
"name": "新项目",
"engineering_type": "基建",
"contract_amount": 100.0,
},
)
assert response.status_code == 409
data = response.json()
assert data["success"] is False
assert data["error_code"] == "2002"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_create_project_missing_required_fields(
client: AsyncClient, admin_token: str
):
"""测试创建项目时缺少必填字段"""
response = await client.post(
"/api/v1/projects",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"project_no": "PRJ001",
# 缺少 name
"engineering_type": "基建",
"contract_amount": 100.0,
},
)
assert response.status_code == 400
data = response.json()
assert data["success"] is False
assert data["error_code"] == "1001"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_update_project_admin_success(
client: AsyncClient, admin_token: str, db_session, admin_user
):
"""测试管理员更新项目成功"""
from src.models.project import Project
from datetime import date
project = Project(
project_no="PRJ001",
name="原项目名称",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
db_session.add(project)
await db_session.commit()
await db_session.refresh(project)
response = await client.put(
f"/api/v1/projects/{project.id}",
headers={"Authorization": f"Bearer {admin_token}"},
json={"name": "更新后的项目名称", "contract_amount": 200.0},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["data"]["name"] == "更新后的项目名称"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_update_project_market_own_project(
client: AsyncClient, market_token: str, db_session, market_user
):
"""测试市场部用户更新自己的项目"""
from src.models.project import Project
from datetime import date
project = Project(
project_no="PRJ001",
name="市场部项目",
engineering_type="业扩",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=market_user.id,
)
db_session.add(project)
await db_session.commit()
await db_session.refresh(project)
response = await client.put(
f"/api/v1/projects/{project.id}",
headers={"Authorization": f"Bearer {market_token}"},
json={"name": "更新后的项目名称"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
@pytest.mark.asyncio
@pytest.mark.integration
async def test_update_project_market_other_project(
client: AsyncClient, market_token: str, db_session, admin_user
):
"""测试市场部用户更新其他人的项目被禁止"""
from src.models.project import Project
from datetime import date
project = Project(
project_no="PRJ001",
name="管理员项目",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
db_session.add(project)
await db_session.commit()
await db_session.refresh(project)
response = await client.put(
f"/api/v1/projects/{project.id}",
headers={"Authorization": f"Bearer {market_token}"},
json={"name": "更新后的项目名称"},
)
assert response.status_code == 403
data = response.json()
assert data["success"] is False
@pytest.mark.asyncio
@pytest.mark.integration
async def test_update_project_not_found(
client: AsyncClient, admin_token: str
):
"""测试更新不存在的项目"""
response = await client.put(
"/api/v1/projects/99999",
headers={"Authorization": f"Bearer {admin_token}"},
json={"name": "更新后的项目名称"},
)
assert response.status_code == 404
data = response.json()
assert data["success"] is False
assert data["error_code"] == "2001"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_delete_project_admin_success(
client: AsyncClient, admin_token: str, db_session, admin_user
):
"""测试管理员删除项目成功"""
from src.models.project import Project
from datetime import date
project = Project(
project_no="PRJ001",
name="待删除项目",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
db_session.add(project)
await db_session.commit()
await db_session.refresh(project)
response = await client.delete(
f"/api/v1/projects/{project.id}",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["message"] == "项目删除成功"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_delete_project_market_own_project(
client: AsyncClient, market_token: str, db_session, market_user
):
"""测试市场部用户删除自己的项目成功"""
from src.models.project import Project
from datetime import date
project = Project(
project_no="PRJ001",
name="市场部项目",
engineering_type="业扩",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=market_user.id,
)
db_session.add(project)
await db_session.commit()
await db_session.refresh(project)
response = await client.delete(
f"/api/v1/projects/{project.id}",
headers={"Authorization": f"Bearer {market_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
@pytest.mark.asyncio
@pytest.mark.integration
async def test_delete_project_market_other_project_forbidden(
client: AsyncClient, market_token: str, db_session, admin_user
):
"""测试市场部用户删除其他人的项目被禁止"""
from src.models.project import Project
from datetime import date
project = Project(
project_no="PRJ001",
name="管理员项目",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
db_session.add(project)
await db_session.commit()
await db_session.refresh(project)
response = await client.delete(
f"/api/v1/projects/{project.id}",
headers={"Authorization": f"Bearer {market_token}"},
)
assert response.status_code == 403
data = response.json()
assert data["success"] is False
@pytest.mark.asyncio
@pytest.mark.integration
async def test_delete_project_not_found(
client: AsyncClient, admin_token: str
):
"""测试删除不存在的项目"""
response = await client.delete(
"/api/v1/projects/99999",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 404
data = response.json()
assert data["success"] is False
assert data["error_code"] == "2001"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_project_detail_success(
client: AsyncClient, admin_token: str, db_session, admin_user
):
"""测试获取项目详情成功"""
from src.models.project import Project
from datetime import date
project = Project(
project_no="PRJ001",
name="测试项目",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
db_session.add(project)
await db_session.commit()
await db_session.refresh(project)
response = await client.get(
f"/api/v1/projects/{project.id}",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["data"]["project_no"] == "PRJ001"
+397
View File
@@ -0,0 +1,397 @@
import pytest
from httpx import AsyncClient
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_statistics_all_users_success(
client: AsyncClient, admin_token: str, db_session, admin_user
):
"""测试所有用户获取基础统计成功"""
from src.models.project import Project
from datetime import date
# 创建测试项目
project1 = Project(
project_no="PRJ001",
name="项目1",
engineering_type="基建",
contract_amount=100.0,
actual_receipt_amount=50.0,
actual_payment_amount=40.0,
cumulative_progress=50.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
project2 = Project(
project_no="PRJ002",
name="项目2",
engineering_type="业扩",
contract_amount=200.0,
actual_receipt_amount=100.0,
actual_payment_amount=80.0,
cumulative_progress=50.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
db_session.add(project1)
db_session.add(project2)
await db_session.commit()
response = await client.get(
"/api/v1/projects/statistics",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["data"]["total_count"] == 2
assert data["data"]["total_contract_amount"] == 300.0
assert data["data"]["total_receipt_amount"] == 150.0
assert data["data"]["total_payment_amount"] == 120.0
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_statistics_with_filters(
client: AsyncClient, admin_token: str, db_session, admin_user
):
"""测试带筛选条件的统计"""
from src.models.project import Project
from datetime import date
# 创建不同类型的项目
project1 = Project(
project_no="PRJ001",
name="基建项目",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
project2 = Project(
project_no="PRJ002",
name="业扩项目",
engineering_type="业扩",
contract_amount=200.0,
signing_date=date(2026, 2, 1),
created_by=admin_user.id,
)
db_session.add(project1)
db_session.add(project2)
await db_session.commit()
# 筛选:工程类别=基建
response = await client.get(
"/api/v1/projects/statistics?engineering_type=基建",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["data"]["total_count"] == 1
assert data["data"]["total_contract_amount"] == 100.0
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_statistics_empty_data(
client: AsyncClient, admin_token: str
):
"""测试无数据时的统计"""
response = await client.get(
"/api/v1/projects/statistics",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["data"]["total_count"] == 0
assert data["data"]["total_contract_amount"] == 0
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_group_statistics_by_engineering_type(
client: AsyncClient, admin_token: str, db_session, admin_user
):
"""测试按工程类别分组统计"""
from src.models.project import Project
from datetime import date
# 创建不同类型的项目
for i in range(3):
project = Project(
project_no=f"PRJ{i:03d}",
name=f"基建项目{i}",
engineering_type="基建",
contract_amount=100.0,
actual_receipt_amount=80.0,
actual_payment_amount=70.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
db_session.add(project)
for i in range(2):
project = Project(
project_no=f"PRJ{i+3:03d}",
name=f"业扩项目{i}",
engineering_type="业扩",
contract_amount=200.0,
actual_receipt_amount=150.0,
actual_payment_amount=130.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
db_session.add(project)
await db_session.commit()
response = await client.get(
"/api/v1/projects/statistics/group?group_by=engineering_type",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert len(data["data"]) == 2
# 验证基建工程统计
infra = next(item for item in data["data"] if item["engineering_type"] == "基建")
assert infra["count"] == 3
assert infra["total_contract_amount"] == 300.0
# 验证业扩工程统计
market = next(item for item in data["data"] if item["engineering_type"] == "业扩")
assert market["count"] == 2
assert market["total_contract_amount"] == 400.0
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_group_statistics_by_department(
client: AsyncClient, admin_token: str, db_session, admin_user
):
"""测试按项目部分组统计"""
from src.models.project import Project
from datetime import date
# 创建不同项目部的项目
project1 = Project(
project_no="PRJ001",
name="项目部一项目",
engineering_type="基建",
contract_amount=100.0,
project_department="项目部一",
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
project2 = Project(
project_no="PRJ002",
name="项目部二项目",
engineering_type="业扩",
contract_amount=200.0,
project_department="项目部二",
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
db_session.add(project1)
db_session.add(project2)
await db_session.commit()
response = await client.get(
"/api/v1/projects/statistics/group?group_by=project_department",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert len(data["data"]) == 2
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_group_statistics_with_date_filter(
client: AsyncClient, admin_token: str, db_session, admin_user
):
"""测试带日期筛选的分组统计"""
from src.models.project import Project
from datetime import date
# 创建不同日期的项目
project1 = Project(
project_no="PRJ001",
name="一月项目",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 15),
created_by=admin_user.id,
)
project2 = Project(
project_no="PRJ002",
name="二月项目",
engineering_type="业扩",
contract_amount=200.0,
signing_date=date(2026, 2, 15),
created_by=admin_user.id,
)
db_session.add(project1)
db_session.add(project2)
await db_session.commit()
# 只统计1月的项目
response = await client.get(
"/api/v1/projects/statistics/group?group_by=engineering_type&signing_date_start=2026-01-01&signing_date_end=2026-01-31",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert len(data["data"]) == 1
assert data["data"][0]["count"] == 1
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_timeline_statistics_by_month(
client: AsyncClient, admin_token: str, db_session, admin_user
):
"""测试按月统计"""
from src.models.project import Project
from datetime import date
# 创建不同月份的项目
for month in [1, 1, 2, 2, 2]:
project = Project(
project_no=f"PRJ{month:02d}",
name=f"{month}月项目",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, month, 15),
created_by=admin_user.id,
)
db_session.add(project)
await db_session.commit()
response = await client.get(
"/api/v1/projects/statistics/timeline?time_field=signing_date&group_by=month",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert len(data["data"]) == 2
# 验证1月统计
jan = next(item for item in data["data"] if item["month"] == "2026-01")
assert jan["count"] == 2
# 验证2月统计
feb = next(item for item in data["data"] if item["month"] == "2026-02")
assert feb["count"] == 3
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_timeline_statistics_by_year(
client: AsyncClient, admin_token: str, db_session, admin_user
):
"""测试按年统计"""
from src.models.project import Project
from datetime import date
# 创建不同年份的项目
for year in [2025, 2026, 2026]:
project = Project(
project_no=f"PRJ{year}",
name=f"{year}年项目",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(year, 1, 1),
created_by=admin_user.id,
)
db_session.add(project)
await db_session.commit()
response = await client.get(
"/api/v1/projects/statistics/timeline?time_field=signing_date&group_by=year",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert len(data["data"]) == 2
# 验证2025年统计
y2025 = next(item for item in data["data"] if item["year"] == "2025")
assert y2025["count"] == 1
# 验证2026年统计
y2026 = next(item for item in data["data"] if item["year"] == "2026")
assert y2026["count"] == 2
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_timeline_statistics_with_filter(
client: AsyncClient, admin_token: str, db_session, admin_user
):
"""测试带筛选条件的时间统计"""
from src.models.project import Project
from datetime import date
# 创建不同类型和日期的项目
project1 = Project(
project_no="PRJ001",
name="基建一月",
engineering_type="基建",
contract_amount=100.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
project2 = Project(
project_no="PRJ002",
name="业扩一月",
engineering_type="业扩",
contract_amount=200.0,
signing_date=date(2026, 1, 1),
created_by=admin_user.id,
)
project3 = Project(
project_no="PRJ003",
name="业扩二月",
engineering_type="业扩",
contract_amount=300.0,
signing_date=date(2026, 2, 1),
created_by=admin_user.id,
)
db_session.add(project1)
db_session.add(project2)
db_session.add(project3)
await db_session.commit()
# 只统计业扩项目
response = await client.get(
"/api/v1/projects/statistics/timeline?time_field=signing_date&group_by=month&engineering_type=业扩",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert len(data["data"]) == 2
# 验证1月业扩项目统计
jan = next(item for item in data["data"] if item["month"] == "2026-01")
assert jan["count"] == 1
+332
View File
@@ -0,0 +1,332 @@
import pytest
from httpx import AsyncClient
@pytest.mark.asyncio
@pytest.mark.integration
async def test_create_user_admin_success(
client: AsyncClient, admin_token: str
):
"""测试管理员创建用户成功"""
response = await client.post(
"/api/v1/users",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"username": "newuser",
"password": "newpass123",
"real_name": "新用户",
"department": "技术部",
"role": "other",
"email": "newuser@test.com",
"phone": "13800000005",
},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["message"] == "用户创建成功"
assert data["data"]["username"] == "newuser"
assert data["data"]["role"] == "other"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_create_user_market_forbidden(
client: AsyncClient, market_token: str
):
"""测试市场部用户创建用户被禁止"""
response = await client.post(
"/api/v1/users",
headers={"Authorization": f"Bearer {market_token}"},
json={
"username": "newuser",
"password": "newpass123",
"real_name": "新用户",
"department": "技术部",
"role": "other",
},
)
assert response.status_code == 403
data = response.json()
assert data["success"] is False
assert data["error_code"] == "3001"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_create_user_duplicate_username(
client: AsyncClient, admin_token: str, admin_user
):
"""测试创建重复用户名"""
response = await client.post(
"/api/v1/users",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"username": "admin",
"password": "newpass123",
"real_name": "重复用户",
"department": "技术部",
"role": "other",
},
)
assert response.status_code == 409
data = response.json()
assert data["success"] is False
assert data["error_code"] == "2002"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_create_user_invalid_role(
client: AsyncClient, admin_token: str
):
"""测试创建用户时使用无效角色"""
response = await client.post(
"/api/v1/users",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"username": "newuser",
"password": "newpass123",
"real_name": "新用户",
"department": "技术部",
"role": "invalid",
},
)
assert response.status_code == 400
data = response.json()
assert data["success"] is False
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_users_admin_success(
client: AsyncClient, admin_token: str
):
"""测试管理员获取用户列表成功"""
response = await client.get(
"/api/v1/users",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert "items" in data["data"]
assert "total" in data["data"]
assert len(data["data"]["items"]) >= 1
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_users_market_forbidden(
client: AsyncClient, market_token: str
):
"""测试市场部用户获取用户列表被禁止"""
response = await client.get(
"/api/v1/users",
headers={"Authorization": f"Bearer {market_token}"},
)
assert response.status_code == 403
data = response.json()
assert data["success"] is False
assert data["error_code"] == "3001"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_users_with_pagination(
client: AsyncClient, admin_token: str, admin_user, market_user, other_user
):
"""测试用户列表分页"""
response = await client.get(
"/api/v1/users?page=1&page_size=2",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["data"]["page"] == 1
assert data["data"]["page_size"] == 2
assert len(data["data"]["items"]) == 2
@pytest.mark.asyncio
@pytest.mark.integration
async def test_get_users_with_filter(
client: AsyncClient, admin_token: str, admin_user, market_user, other_user
):
"""测试用户列表筛选"""
response = await client.get(
"/api/v1/users?department=市场部",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
for user in data["data"]["items"]:
assert user["department"] == "市场部"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_update_user_admin_success(
client: AsyncClient, admin_token: str, market_user
):
"""测试管理员更新用户成功"""
response = await client.put(
f"/api/v1/users/{market_user.id}",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"real_name": "更新后的姓名",
"email": "updated@test.com",
},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["data"]["real_name"] == "更新后的姓名"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_update_user_market_forbidden(
client: AsyncClient, market_token: str, other_user
):
"""测试市场部用户更新用户被禁止"""
response = await client.put(
f"/api/v1/users/{other_user.id}",
headers={"Authorization": f"Bearer {market_token}"},
json={"real_name": "更新后的姓名"},
)
assert response.status_code == 403
data = response.json()
assert data["success"] is False
@pytest.mark.asyncio
@pytest.mark.integration
async def test_update_user_not_found(
client: AsyncClient, admin_token: str
):
"""测试更新不存在的用户"""
response = await client.put(
"/api/v1/users/99999",
headers={"Authorization": f"Bearer {admin_token}"},
json={"real_name": "更新后的姓名"},
)
assert response.status_code == 404
data = response.json()
assert data["success"] is False
assert data["error_code"] == "2001"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_delete_user_admin_success(
client: AsyncClient, admin_token: str, other_user
):
"""测试管理员删除用户成功"""
response = await client.delete(
f"/api/v1/users/{other_user.id}",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["message"] == "用户删除成功"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_delete_user_market_forbidden(
client: AsyncClient, market_token: str, other_user
):
"""测试市场部用户删除用户被禁止"""
response = await client.delete(
f"/api/v1/users/{other_user.id}",
headers={"Authorization": f"Bearer {market_token}"},
)
assert response.status_code == 403
data = response.json()
assert data["success"] is False
@pytest.mark.asyncio
@pytest.mark.integration
async def test_delete_user_not_found(
client: AsyncClient, admin_token: str
):
"""测试删除不存在的用户"""
response = await client.delete(
"/api/v1/users/99999",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert response.status_code == 404
data = response.json()
assert data["success"] is False
assert data["error_code"] == "2001"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_reset_password_admin_success(
client: AsyncClient, admin_token: str, other_user
):
"""测试管理员重置密码成功"""
response = await client.post(
f"/api/v1/users/{other_user.id}/reset-password",
headers={"Authorization": f"Bearer {admin_token}"},
json={"new_password": "newpassword123"},
)
assert response.status_code == 200
data = response.json()
assert data["success"] is True
assert data["message"] == "密码重置成功"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_reset_password_market_forbidden(
client: AsyncClient, market_token: str, other_user
):
"""测试市场部用户重置密码被禁止"""
response = await client.post(
f"/api/v1/users/{other_user.id}/reset-password",
headers={"Authorization": f"Bearer {market_token}"},
json={"new_password": "newpassword123"},
)
assert response.status_code == 403
data = response.json()
assert data["success"] is False
@pytest.mark.asyncio
@pytest.mark.integration
async def test_reset_password_invalid_password(
client: AsyncClient, admin_token: str, other_user
):
"""测试重置密码时使用无效密码格式"""
response = await client.post(
f"/api/v1/users/{other_user.id}/reset-password",
headers={"Authorization": f"Bearer {admin_token}"},
json={"new_password": "123"},
)
assert response.status_code == 400
data = response.json()
assert data["success"] is False
File diff suppressed because it is too large Load Diff
-934
View File
@@ -1,934 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>海洋项目管理系统 - 设计参考</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: #f0f2f5;
color: #333;
}
.page-selector {
position: fixed;
top: 10px;
left: 10px;
z-index: 1000;
background: white;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
}
.page-selector h3 {
margin-bottom: 10px;
font-size: 16px;
color: #1890ff;
}
.page-selector button {
display: block;
width: 100%;
padding: 8px 12px;
margin-bottom: 5px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.page-selector button:hover {
background: #40a9ff;
}
.page-selector button.active {
background: #096dd9;
}
.page {
display: none;
}
.page.active {
display: block;
}
/* Login Page Styles */
.login-page {
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #001529 0%, #1890ff 100%);
}
.login-box {
width: 400px;
background: white;
border-radius: 8px;
padding: 40px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.login-title {
font-size: 24px;
font-weight: 600;
color: #1890ff;
text-align: center;
margin-bottom: 32px;
}
.form-group {
margin-bottom: 20px;
}
.form-label {
display: block;
margin-bottom: 8px;
font-size: 14px;
font-weight: 500;
color: #333;
}
.form-input {
width: 100%;
height: 40px;
padding: 8px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
color: #333;
}
.form-input:focus {
border-color: #1890ff;
outline: none;
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
}
.checkbox-group {
display: flex;
align-items: center;
margin-bottom: 20px;
}
.checkbox-group input {
margin-right: 8px;
}
.btn-login {
width: 100%;
height: 40px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
}
.btn-login:hover {
background: #40a9ff;
}
/* Main Layout Styles */
.main-layout {
display: flex;
min-height: 100vh;
}
.sidebar {
width: 256px;
background: white;
border-right: 1px solid #e8e8e8;
padding-top: 64px;
position: fixed;
left: 0;
top: 0;
bottom: 0;
overflow-y: auto;
}
.menu-item {
padding: 12px 24px;
display: flex;
align-items: center;
gap: 10px;
color: #666;
cursor: pointer;
border-left: 3px solid transparent;
transition: all 0.3s;
}
.menu-item:hover {
background: #e6f7ff;
color: #1890ff;
}
.menu-item.active {
background: #e6f7ff;
color: #1890ff;
border-left-color: #1890ff;
}
.header {
height: 64px;
background: #001529;
position: fixed;
top: 0;
left: 256px;
right: 0;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 24px;
color: white;
z-index: 100;
}
.logo {
font-size: 18px;
font-weight: 600;
}
.user-info {
display: flex;
align-items: center;
gap: 12px;
}
.content {
margin-left: 256px;
margin-top: 64px;
padding: 24px;
background: #f0f2f5;
min-height: calc(100vh - 64px);
}
/* Dashboard Styles */
.stat-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24px;
margin-bottom: 24px;
}
.stat-card {
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.stat-label {
font-size: 14px;
color: #666;
margin-bottom: 8px;
}
.stat-value {
font-size: 32px;
font-weight: 600;
color: #1890ff;
}
.chart-section {
display: grid;
grid-template-columns: 2fr 1fr;
gap: 24px;
margin-bottom: 24px;
}
.card {
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.card-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 20px;
}
.chart-placeholder {
height: 300px;
background: #fafafa;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 14px;
}
.recent-project {
padding: 12px 0;
border-bottom: 1px solid #f0f0f0;
}
.project-name {
font-size: 14px;
font-weight: 500;
color: #333;
}
.project-meta {
font-size: 12px;
color: #999;
margin-top: 4px;
}
.project-amount {
font-size: 14px;
font-weight: 600;
color: #1890ff;
}
/* Project List Styles */
.toolbar {
background: white;
padding: 16px 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
margin-bottom: 24px;
display: flex;
justify-content: space-between;
align-items: center;
}
.toolbar-left {
display: flex;
gap: 12px;
align-items: center;
}
.search-input {
width: 240px;
height: 32px;
padding: 4px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
}
.select-input {
width: 120px;
height: 32px;
padding: 4px 12px;
border: 1px solid #d9d9d9;
border-radius: 4px;
font-size: 14px;
}
.btn-primary {
height: 32px;
padding: 0 16px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.btn-primary:hover {
background: #40a9ff;
}
.table-container {
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
overflow: hidden;
}
table {
width: 100%;
border-collapse: collapse;
}
thead th {
background: #fafafa;
padding: 12px 16px;
text-align: left;
font-weight: 600;
color: #333;
border-bottom: 1px solid #e8e8e8;
font-size: 14px;
}
tbody td {
padding: 12px 16px;
border-bottom: 1px solid #e8e8e8;
color: #666;
font-size: 14px;
}
tbody tr:hover {
background: #fafafa;
}
.action-link {
color: #1890ff;
cursor: pointer;
margin-right: 12px;
}
.action-link:hover {
color: #40a9ff;
}
.tag {
display: inline-block;
padding: 2px 8px;
border-radius: 2px;
font-size: 12px;
line-height: 1.5;
}
.tag-blue {
background: #e6f7ff;
border: 1px solid #91d5ff;
color: #1890ff;
}
.tag-green {
background: #f6ffed;
border: 1px solid #b7eb8f;
color: #52c41a;
}
.pagination {
height: 48px;
background: white;
border-top: 1px solid #e8e8e8;
display: flex;
justify-content: center;
align-items: center;
gap: 8px;
}
.page-btn {
width: 32px;
height: 32px;
border: 1px solid #d9d9d9;
border-radius: 4px;
background: white;
color: #666;
cursor: pointer;
transition: all 0.3s;
}
.page-btn:hover {
border-color: #1890ff;
color: #1890ff;
}
.page-btn.active {
background: #1890ff;
color: white;
border-color: #1890ff;
}
/* Project Detail Styles */
.breadcrumb {
height: 32px;
margin-bottom: 16px;
font-size: 14px;
color: #666;
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
}
.page-title {
font-size: 24px;
font-weight: 600;
color: #333;
}
.info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
margin-bottom: 24px;
}
.info-card {
background: white;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.info-card-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #e8e8e8;
}
.info-item {
display: flex;
margin-bottom: 12px;
}
.info-item-label {
width: 120px;
font-size: 14px;
color: #666;
}
.info-item-value {
flex: 1;
font-size: 14px;
color: #333;
}
.full-width {
grid-column: span 2;
}
.btn-group {
display: flex;
gap: 12px;
}
.btn-edit {
height: 32px;
padding: 0 16px;
background: #1890ff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-delete {
height: 32px;
padding: 0 16px;
background: #ff4d4f;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>
</head>
<body>
<!-- Page Selector -->
<div class="page-selector">
<h3>页面切换</h3>
<button class="active" onclick="showPage('login')">登录页面</button>
<button onclick="showPage('dashboard')">仪表盘</button>
<button onclick="showPage('project-list')">项目列表</button>
<button onclick="showPage('project-detail')">项目详情</button>
</div>
<!-- Login Page -->
<div id="login" class="page login-page active">
<div class="login-box">
<h1 class="login-title">海洋项目管理系统</h1>
<div class="form-group">
<label class="form-label">用户名</label>
<input type="text" class="form-input" placeholder="请输入用户名">
</div>
<div class="form-group">
<label class="form-label">密码</label>
<input type="password" class="form-input" placeholder="请输入密码">
</div>
<div class="checkbox-group">
<input type="checkbox" id="remember">
<label for="remember">记住密码</label>
</div>
<button class="btn-login">登录</button>
</div>
</div>
<!-- Dashboard Page -->
<div id="dashboard" class="page">
<div class="main-layout">
<div class="sidebar">
<div class="menu-item active">📊 仪表盘</div>
<div class="menu-item">📁 项目管理</div>
<div class="menu-item">👥 用户管理</div>
<div class="menu-item">📈 项目统计</div>
</div>
<div style="flex: 1">
<div class="header">
<div class="logo">海洋项目管理系统</div>
<div class="user-info">
<span>张三 (市场部)</span>
<button class="btn-primary" style="height: 28px; font-size: 12px;">登出</button>
</div>
</div>
<div class="content">
<h2 style="margin-bottom: 24px; font-size: 24px; font-weight: 600; color: #333;">仪表盘</h2>
<div class="stat-grid">
<div class="stat-card">
<div class="stat-label">项目总数</div>
<div class="stat-value">156</div>
</div>
<div class="stat-card">
<div class="stat-label">进行中</div>
<div class="stat-value">68</div>
</div>
<div class="stat-card">
<div class="stat-label">已完成</div>
<div class="stat-value">88</div>
</div>
<div class="stat-card">
<div class="stat-label">总合同金额(万元)</div>
<div class="stat-value">12,450</div>
</div>
</div>
<div class="chart-section">
<div class="card">
<div class="card-title">项目趋势图</div>
<div class="chart-placeholder">
[柱状图/折线图显示区域]
</div>
</div>
<div class="card">
<div class="card-title">最近项目</div>
<div class="recent-project">
<div class="project-name">某电力基建工程项目</div>
<div class="project-meta">2026-01-25</div>
<div class="project-amount">950万元</div>
</div>
<div class="recent-project">
<div class="project-name">业扩工程项目</div>
<div class="project-meta">2026-01-24</div>
<div class="project-amount">500万元</div>
</div>
<div class="recent-project">
<div class="project-name">客户工程项目</div>
<div class="project-meta">2026-01-23</div>
<div class="project-amount">300万元</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Project List Page -->
<div id="project-list" class="page">
<div class="main-layout">
<div class="sidebar">
<div class="menu-item">📊 仪表盘</div>
<div class="menu-item active">📁 项目管理</div>
<div class="menu-item">👥 用户管理</div>
<div class="menu-item">📈 项目统计</div>
</div>
<div style="flex: 1">
<div class="header">
<div class="logo">海洋项目管理系统</div>
<div class="user-info">
<span>张三 (市场部)</span>
<button class="btn-primary" style="height: 28px; font-size: 12px;">登出</button>
</div>
</div>
<div class="content">
<h2 style="margin-bottom: 24px; font-size: 24px; font-weight: 600; color: #333;">项目管理</h2>
<div class="toolbar">
<div class="toolbar-left">
<input type="text" class="search-input" placeholder="搜索项目名称、合同编号">
<select class="select-input">
<option>工程类别</option>
<option>基建</option>
<option>业扩</option>
<option>客户</option>
</select>
<select class="select-input">
<option>所属项目部</option>
<option>项目部一</option>
<option>项目部二</option>
</select>
</div>
<button class="btn-primary">+ 新建项目</button>
</div>
<div class="table-container">
<table>
<thead>
<tr>
<th>合同编号</th>
<th>项目名称</th>
<th>工程类别</th>
<th>签订日期</th>
<th>合同金额(万元)</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr>
<td>PRJ2026001</td>
<td>某电力基建工程项目</td>
<td><span class="tag tag-blue">基建</span></td>
<td>2026-01-25</td>
<td>950.00</td>
<td><span class="tag tag-green">进行中</span></td>
<td>
<span class="action-link">查看</span>
<span class="action-link">编辑</span>
<span class="action-link" style="color: #ff4d4f;">删除</span>
</td>
</tr>
<tr>
<td>PRJ2026002</td>
<td>业扩工程项目</td>
<td><span class="tag tag-blue">业扩</span></td>
<td>2026-01-24</td>
<td>500.00</td>
<td><span class="tag tag-green">进行中</span></td>
<td>
<span class="action-link">查看</span>
<span class="action-link">编辑</span>
<span class="action-link" style="color: #ff4d4f;">删除</span>
</td>
</tr>
<tr>
<td>PRJ2026003</td>
<td>客户工程项目</td>
<td><span class="tag tag-blue">客户</span></td>
<td>2026-01-23</td>
<td>300.00</td>
<td><span class="tag" style="background: #f5f5f5; color: #999;">已完成</span></td>
<td>
<span class="action-link">查看</span>
<span class="action-link">编辑</span>
<span class="action-link" style="color: #ff4d4f;">删除</span>
</td>
</tr>
</tbody>
</table>
<div class="pagination">
<button class="page-btn">&lt;</button>
<button class="page-btn active">1</button>
<button class="page-btn">2</button>
<button class="page-btn">3</button>
<button class="page-btn">&gt;</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Project Detail Page -->
<div id="project-detail" class="page">
<div class="main-layout">
<div class="sidebar">
<div class="menu-item">📊 仪表盘</div>
<div class="menu-item active">📁 项目管理</div>
<div class="menu-item">👥 用户管理</div>
<div class="menu-item">📈 项目统计</div>
</div>
<div style="flex: 1">
<div class="header">
<div class="logo">海洋项目管理系统</div>
<div class="user-info">
<span>张三 (市场部)</span>
<button class="btn-primary" style="height: 28px; font-size: 12px;">登出</button>
</div>
</div>
<div class="content">
<div class="breadcrumb">首页 > 项目管理 > 项目详情</div>
<div class="page-header">
<h1 class="page-title">某电力基建工程项目</h1>
<div class="btn-group">
<button class="btn-edit">编辑</button>
<button class="btn-delete">删除</button>
</div>
</div>
<div class="info-grid">
<div class="info-card">
<div class="info-card-title">基本信息</div>
<div class="info-item">
<div class="info-item-label">合同编号</div>
<div class="info-item-value">PRJ2026001</div>
</div>
<div class="info-item">
<div class="info-item-label">项目名称</div>
<div class="info-item-value">某电力基建工程项目</div>
</div>
<div class="info-item">
<div class="info-item-label">工程类别</div>
<div class="info-item-value"><span class="tag tag-blue">基建</span></div>
</div>
<div class="info-item">
<div class="info-item-label">合同金额</div>
<div class="info-item-value">950.00 万元</div>
</div>
</div>
<div class="info-card">
<div class="info-card-title">项目时间</div>
<div class="info-item">
<div class="info-item-label">签订日期</div>
<div class="info-item-value">2026-01-25</div>
</div>
<div class="info-item">
<div class="info-item-label">开工日期</div>
<div class="info-item-value">2026-01-15</div>
</div>
<div class="info-item">
<div class="info-item-label">计划竣工日期</div>
<div class="info-item-value">2026-12-31</div>
</div>
<div class="info-item">
<div class="info-item-label">实际竣工日期</div>
<div class="info-item-value">-</div>
</div>
</div>
<div class="info-card">
<div class="info-card-title">成本管理</div>
<div class="info-item">
<div class="info-item-label">总体成本控制</div>
<div class="info-item-value">800.00 万元</div>
</div>
<div class="info-item">
<div class="info-item-label">人工成本控制</div>
<div class="info-item-value">300.00 万元</div>
</div>
<div class="info-item">
<div class="info-item-label">材料成本控制</div>
<div class="info-item-value">400.00 万元</div>
</div>
<div class="info-item">
<div class="info-item-label">其他费用控制</div>
<div class="info-item-value">100.00 万元</div>
</div>
</div>
<div class="info-card">
<div class="info-card-title">合同财务</div>
<div class="info-item">
<div class="info-item-label">合同金额</div>
<div class="info-item-value">950.00 万元</div>
</div>
<div class="info-item">
<div class="info-item-label">质保金比例</div>
<div class="info-item-value">5.00%</div>
</div>
<div class="info-item">
<div class="info-item-label">质保金金额</div>
<div class="info-item-value">47.50 万元</div>
</div>
<div class="info-item">
<div class="info-item-label">质保到期日</div>
<div class="info-item-value">2028-12-31</div>
</div>
</div>
<div class="info-card">
<div class="info-card-title">收款付款</div>
<div class="info-item">
<div class="info-item-label">项目进度</div>
<div class="info-item-value">60.00%</div>
</div>
<div class="info-item">
<div class="info-item-label">应收款金额</div>
<div class="info-item-value">570.00 万元</div>
</div>
<div class="info-item">
<div class="info-item-label">实际收款金额</div>
<div class="info-item-value">475.00 万元</div>
</div>
<div class="info-item">
<div class="info-item-label">收款完成率</div>
<div class="info-item-value">50.00%</div>
</div>
</div>
<div class="info-card">
<div class="info-card-title">结算信息</div>
<div class="info-item">
<div class="info-item-label">成本结算金额</div>
<div class="info-item-value">-</div>
</div>
<div class="info-item">
<div class="info-item-label">到期结算项目</div>
<div class="info-item-value">0 个</div>
</div>
<div class="info-item">
<div class="info-item-label">未结算项目</div>
<div class="info-item-value">0 个</div>
</div>
</div>
<div class="info-card full-width">
<div class="info-card-title">项目管理</div>
<div class="info-item">
<div class="info-item-label">所属项目部</div>
<div class="info-item-value">项目部一</div>
</div>
<div class="info-item">
<div class="info-item-label">项目负责人</div>
<div class="info-item-value">李四 13900000001</div>
</div>
<div class="info-item">
<div class="info-item-label">工程款拨付方式</div>
<div class="info-item-value">按进度付款</div>
</div>
<div class="info-item">
<div class="info-item-label">存在的问题</div>
<div class="info-item-value">-</div>
</div>
<div class="info-item">
<div class="info-item-label">建议措施</div>
<div class="info-item-value">-</div>
</div>
<div class="info-item">
<div class="info-item-label">备注</div>
<div class="info-item-value">备注信息</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function showPage(pageId) {
// Hide all pages
document.querySelectorAll('.page').forEach(page => {
page.classList.remove('active');
});
// Remove active class from all buttons
document.querySelectorAll('.page-selector button').forEach(btn => {
btn.classList.remove('active');
});
// Show selected page
document.getElementById(pageId).classList.add('active');
// Add active class to clicked button
event.target.classList.add('active');
}
</script>
</body>
</html>
File diff suppressed because it is too large Load Diff
+158
View File
@@ -0,0 +1,158 @@
# 产品设计文档修改日志
## 修改时间
2026-01-25
## 修改说明
根据技术总监的要求,从产品设计文档中移除Excel数据导入功能,改为在部署时直接写入初始数据。
## 修改内容
### 1. 移除的内容
#### 1.1 产品特点(第1.3节)
**删除:**
- ❌ "支持Excel数据导入,便于数据迁移"
**修改为:**
- ✅ "数据初始化在部署时完成,无需手动导入"
#### 1.2 数据导入章节(第2.5节)
**完全删除:**
- ❌ 2.5.1 Excel导入功能
- ❌ 2.5.2 数据管理
**删除的子内容:**
- 支持从Excel文件批量导入项目数据
- 系统自动验证数据格式
- 导入前显示数据预览
- 导入后显示导入结果(成功数、失败数)
- 支持错误日志导出
- 查看已导入的数据
- 编辑和删除导入的数据
- 数据验证和提示
- 数据备份功能
#### 1.3 项目列表页(第3.2.5节)
**删除:**
- ❌ "批量操作(可选)"
#### 1.4 常见问题(第6.2节)
**删除:**
- ❌ Q: 导入Excel失败怎么办?
- ❌ A: 请检查Excel格式是否符合要求,查看错误日志。
#### 1.5 数据来源章节(第7.1节)
**删除:**
- ❌ "系统支持从Excel文件导入项目数据,Excel文件格式参照提供的模板。"
**修改为:**
- ✅ "系统初始数据在部署时直接写入数据库,包括:"
- 默认管理员账号
- 基础项目分类信息
- 示例项目数据(可选)
- ✅ "部署完成后,用户可以直接使用系统,无需手动导入数据。"
### 2. 保留的内容
- ✅ 项目管理功能(创建、编辑、删除、查询)
- ✅ 数据查询和统计功能
- ✅ 权限控制功能
- ✅ 用户管理功能
- ✅ 数据导出功能(在项目列表页导出统计数据)
### 3. 章节编号
章节编号保持不变,因为删除的是子章节(2.5),主章节编号不需要改变。
## 文档变化
### 修改前
- **文档行数**: 404行
- **包含功能**: 项目管理 + Excel导入
### 修改后
- **文档行数**: 389行
- **包含功能**: 项目管理 + 部署时初始化数据
- **精简**: 减少15行(约3.7%
## 功能说明
### 数据初始化方式变更
**修改前:**
- 用户登录系统后,手动导入Excel文件
- 系统验证数据格式并导入数据库
- 用户提供Excel模板
**修改后:**
- 部署系统时,由开发人员或运维人员直接写入初始数据
- 用户登录系统后,可以直接使用
- 无需手动导入任何数据
### 用户操作简化
**修改前:**
1. 部署系统
2. 登录系统
3. 导入Excel文件
4. 验证数据
5. 开始使用
**修改后:**
1. 部署系统(包含初始数据)
2. 登录系统
3. 开始使用
### 优势
1. **操作更简单**: 用户无需手动导入数据
2. **减少错误**: 避免Excel格式错误
3. **节省时间**: 用户登录即可使用
4. **数据更可靠**: 由专业人员准备初始数据
## 技术实现说明
本修改仅涉及产品设计文档,不涉及技术实现。技术文档(技术架构文档.md)中可能仍保留Excel数据导入的技术方案,供开发人员参考。
如果需要完全移除Excel数据导入功能,还需要修改以下文档:
- `技术架构文档.md` - 删除Excel导入相关的技术方案
- `后端架构设计.md` - 删除Excel导入相关的接口设计
- `api.md` - 删除Excel导入相关的API接口
## 影响范围
### 文档
-`产品设计文档.md` - 已修改
### 产品功能
- ✅ 移除Excel数据导入功能
- ✅ 简化初始数据加载方式
### 用户操作
- ✅ 用户无需手动导入数据
- ✅ 部署后直接使用系统
## 后续工作
### 待确认
- [ ] 确认技术文档是否需要同步删除Excel导入相关内容
- [ ] 确认后端开发计划是否需要调整
- [ ] 确认初始数据准备的流程和责任人
### 可选优化
- [ ] 考虑提供数据导出功能,方便用户备份数据
- [ ] 考虑提供数据恢复功能,方便用户导入备份
## 总结
本次修改成功从产品设计文档中移除了Excel数据导入功能,改为在部署时直接写入初始数据。这样可以简化用户操作,减少错误,节省时间。
文档从404行精简到389行,减少了3.7%的内容,使文档更加简洁和聚焦。
---
**修改人**: 技术总监
**修改时间**: 2026-01-25
**审核状态**: 待审核
+8 -22
View File
@@ -16,7 +16,7 @@
- 无需复杂配置,降低使用门槛 - 无需复杂配置,降低使用门槛
- 基于角色的权限控制(RBAC - 基于角色的权限控制(RBAC
- 简单易用的用户界面 - 简单易用的用户界面
- 支持Excel数据导入,便于数据迁移 - 数据初始化在部署时完成,无需手动导入
## 2. 功能需求 ## 2. 功能需求
@@ -181,21 +181,6 @@
- 项目管理相关字段 - 项目管理相关字段
- 不能修改:合同编号、项目名称、工程类别、业主信息等基础字段 - 不能修改:合同编号、项目名称、工程类别、业主信息等基础字段
### 2.5 数据导入
#### 2.5.1 Excel导入功能
- 支持从Excel文件批量导入项目数据
- 系统自动验证数据格式
- 导入前显示数据预览
- 导入后显示导入结果(成功数、失败数)
- 支持错误日志导出
#### 2.5.2 数据管理
- 查看已导入的数据
- 编辑和删除导入的数据
- 数据验证和提示
- 数据备份功能
## 3. 用户界面 ## 3. 用户界面
### 3.1 界面设计原则 ### 3.1 界面设计原则
@@ -250,7 +235,6 @@
- 关键词搜索 - 关键词搜索
- 排序功能 - 排序功能
- 创建新项目按钮 - 创建新项目按钮
- 批量操作(可选)
- 分页功能 - 分页功能
#### 3.2.6 项目详情页 #### 3.2.6 项目详情页
@@ -341,9 +325,6 @@ A: 目前系统不支持批量编辑,请逐个编辑。
**Q: 项目创建后能否修改工程类别?** **Q: 项目创建后能否修改工程类别?**
A: 可以,但有权限限制,请联系管理员。 A: 可以,但有权限限制,请联系管理员。
**Q: 导入Excel失败怎么办?**
A: 请检查Excel格式是否符合要求,查看错误日志。
### 6.3 联系支持 ### 6.3 联系支持
- 技术支持:提供技术帮助和问题解答 - 技术支持:提供技术帮助和问题解答
- 系统管理员:负责用户管理和权限分配 - 系统管理员:负责用户管理和权限分配
@@ -351,8 +332,13 @@ A: 请检查Excel格式是否符合要求,查看错误日志。
## 7. 数据来源 ## 7. 数据来源
### 7.1 始数据 ### 7.1 始数据
系统支持从Excel文件导入项目数据,Excel文件格式参照提供的模板。 系统初始数据在部署时直接写入数据库,包括:
- 默认管理员账号
- 基础项目分类信息
- 示例项目数据(可选)
部署完成后,用户可以直接使用系统,无需手动导入数据。
### 7.2 工程分类 ### 7.2 工程分类
根据业务需求,工程项目分为以下类别: 根据业务需求,工程项目分为以下类别:
+2
View File
@@ -0,0 +1,2 @@
VITE_API_BASE_URL=http://localhost:5000/api/v1
VITE_APP_TITLE=海洋项目管理系统
+2
View File
@@ -0,0 +1,2 @@
VITE_API_BASE_URL=https://api.example.com/api/v1
VITE_APP_TITLE=海洋项目管理系统
+39
View File
@@ -0,0 +1,39 @@
import js from '@eslint/js';
import react from 'eslint-plugin-react';
import reactHooks from 'eslint-plugin-react-hooks';
import reactRefresh from 'eslint-plugin-react-refresh';
export default [
{
ignores: ['dist', '.eslintrc.cjs'],
},
js.configs.recommended,
{
plugins: {
'react': react.configs.flat.recommended,
'react-hooks': reactHooks.configs.flat.recommended,
'react-refresh': reactRefresh.configs.flat.recommended,
},
languageOptions: {
ecmaVersion: 2020,
globals: {
...globals.browser,
...globals.node,
...globals.es2021,
},
},
settings: { react: { version: 'detect' } },
rules: {
...react.configs.flat.recommended.rules,
...reactHooks.configs.flat.recommended.rules,
...reactRefresh.configs.flat.recommended.rules,
'react/jsx-no-target-blank': 'off',
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'react/react-in-jsx-scope': 'off',
'no-console': ['warn', { allow: ['warn', 'error'] }],
},
},
];
+9
View File
@@ -0,0 +1,9 @@
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 100,
"arrowParens": "avoid",
"endOfLine": "lf"
}
+890
View File
@@ -0,0 +1,890 @@
# 海洋项目管理系统 - 测试用例文档
## 目录
1. [单元测试用例](#1-单元测试用例)
2. [集成测试用例](#2-集成测试用例)
3. [E2E测试用例](#3-e2e测试用例)
---
## 1. 单元测试用例
### 1.1 工具函数测试
#### TC-001: formatMoney 函数测试
| 用例ID | TC-001-01 |
|--------|-----------|
| 测试模块 | utils/format.js |
| 测试函数 | formatMoney |
| 优先级 | 高 |
| 前置条件 | 无 |
**测试场景**
| 场景 | 输入 | 预期输出 |
|------|------|---------|
| 正整数 | 1234 | "1,234.00" |
| 小数 | 1234.56 | "1,234.56" |
| 千分位 | 10000 | "10,000.00" |
| 零 | 0 | "0.00" |
| 负数 | -1234.56 | "-1,234.56" |
| 四舍五入 | 100.123 | "100.12" |
| 大数字 | 9999999999 | "9,999,999,999.00" |
---
#### TC-002: formatPercentage 函数测试
| 用例ID | TC-002-01 |
|--------|-----------|
| 测试模块 | utils/format.js |
| 测试函数 | formatPercentage |
| 优先级 | 高 |
| 前置条件 | 无 |
**测试场景**
| 场景 | 输入 | 预期输出 |
|------|------|---------|
| 50% | 0.5 | "50.00%" |
| 100% | 1 | "100.00%" |
| 0% | 0 | "0.00%" |
| 小数 | 0.6666 | "66.66%" |
| 负数 | -0.5 | "-50.00%" |
---
#### TC-003: formatDate 函数测试
| 用例ID | TC-003-01 |
|--------|-----------|
| 测试模块 | utils/format.js |
| 测试函数 | formatDate |
| 优先级 | 高 |
| 前置条件 | 无 |
**测试场景**
| 场景 | 输入 | 预期输出 |
|------|------|---------|
| 日期字符串 | "2026-01-25" | "2026-01-25" |
| Date对象 | new Date("2026-01-25") | "2026-01-25" |
| 时间戳 | 1706150400000 | "2026-01-25" |
---
#### TC-004: validateEmail 函数测试
| 用例ID | TC-004-01 |
|--------|-----------|
| 测试模块 | utils/validate.js |
| 测试函数 | validateEmail |
| 优先级 | 高 |
| 前置条件 | 无 |
**测试场景**
| 场景 | 输入 | 预期输出 |
|------|------|---------|
| 有效邮箱 | "test@example.com" | true |
| 有效邮箱(带点) | "test.name@example.com" | true |
| 无效邮箱(无@ | "testexample.com" | false |
| 无效邮箱(无域名) | "test@" | false |
| 无效邮箱(无用户名) | "@example.com" | false |
| 空字符串 | "" | false |
---
#### TC-005: validatePhone 函数测试
| 用例ID | TC-005-01 |
|--------|-----------|
| 测试模块 | utils/validate.js |
| 测试函数 | validatePhone |
| 优先级 | 高 |
| 前置条件 | 无 |
**测试场景**
| 场景 | 输入 | 预期输出 |
|------|------|---------|
| 有效手机号 | "13800000001" | true |
| 有效手机号(其他运营商) | "15900139000" | true |
| 无效手机号(太短) | "1380000" | false |
| 无效手机号(太长) | "138000000001" | false |
| 无效手机号(非数字) | "138a0000001" | false |
| 空字符串 | "" | false |
---
### 1.2 Hooks测试
#### TC-006: useAuth Hook 测试
| 用例ID | TC-006-01 |
|--------|-----------|
| 测试模块 | hooks/useAuth.js |
| 测试Hook | useAuth |
| 优先级 | 高 |
| 前置条件 | AuthProvider包裹 |
**测试场景**
| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 初始状态 | 渲染组件 | isAuthenticated=false, user=null, token=null |
| 登录成功 | 调用login('admin', 'password123') | isAuthenticated=true, user有值, token有值 |
| 登录失败 | 调用login('admin', 'wrong') | isAuthenticated=false, 显示错误提示 |
| 登出 | 调用logout() | isAuthenticated=false, user=null, token=null, localStorage清空 |
| hasRole | 调用hasRole(['admin']) | 返回true或false |
| canEdit | 调用canEdit(projectId) | 根据角色和创建人返回true或false |
---
#### TC-007: useTable Hook 测试
| 用例ID | TC-007-01 |
|--------|-----------|
| 测试模块 | hooks/useTable.js |
| 测试Hook | useTable |
| 优先级 | 中 |
| 前置条件 | 无 |
**测试场景**
| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 初始状态 | 初始化Hook | pagination={current:1, pageSize:10}, filters={}, sorter={} |
| 页码变化 | 调用handleTableChange({current:2}) | pagination.current=2 |
| 筛选变化 | 调用handleTableChange({current:1}, {engineering_type:'基建'}) | filters.engineering_type='基建' |
| 排序变化 | 调用handleTableChange({current:1}, {}, {field:'signing_date', order:'desc'}) | sorter.field='signing_date', sorter.order='desc' |
| 重置 | 调用resetTable() | 恢复初始状态 |
| 选择行 | 调用setSelectedRowKeys([1,2,3]) | selectedRowKeys=[1,2,3] |
---
#### TC-008: useDebounce Hook 测试
| 用例ID | TC-008-01 |
|--------|-----------|
| 测试模块 | hooks/useDebounce.js |
| 测试Hook | useDebounce |
| 优先级 | 低 |
| 前置条件 | 无 |
**测试场景**
| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 防抖生效 | 快速改变值3次 | 只返回最后一次的值,延迟300ms |
| 默认延迟 | 不传delay参数 | 使用默认延迟300ms |
| 自定义延迟 | 传入delay=500 | 使用自定义延迟500ms |
---
### 1.3 组件测试
#### TC-009: Button 组件测试
| 用例ID | TC-009-01 |
|--------|-----------|
| 测试模块 | components/Common/Button |
| 测试组件 | Button |
| 优先级 | 高 |
| 前置条件 | 无 |
**测试场景**
| 场景 | Props | 预期结果 |
|------|-------|---------|
| 渲染按钮 | children="点击我" | 显示"点击我"文字 |
| 不同类型 | type="primary" | 蓝色按钮 |
| 不同类型 | type="default" | 白色按钮 |
| 不同类型 | type="danger" | 红色按钮 |
| 加载状态 | loading=true | 显示加载图标,禁用按钮 |
| 禁用状态 | disabled=true | 按钮不可点击 |
| 点击事件 | onClick=mockFn | 点击时调用mockFn |
---
#### TC-010: Tag 组件测试
| 用例ID | TC-010-01 |
|--------|-----------|
| 测试模块 | components/Common/Tag |
| 测试组件 | Tag |
| 优先级 | 中 |
| 前置条件 | 无 |
**测试场景**
| 场景 | Props | 预期结果 |
|------|-------|---------|
| 渲染标签 | children="基建" | 显示"基建"文字 |
| 不同类型 | type="new" | 蓝色背景和文字 |
| 不同类型 | type="inProgress" | 绿色背景和文字 |
| 不同类型 | type="completed" | 灰色背景和文字 |
| 不同类型 | type="cancelled" | 红色背景和文字 |
| 自定义颜色 | color="#ff0000" | 使用自定义颜色 |
---
#### TC-011: StatCard 组件测试
| 用例ID | TC-011-01 |
|--------|-----------|
| 测试模块 | components/Business/StatCard |
| 测试组件 | StatCard |
| 优先级 | 中 |
| 前置条件 | 无 |
**测试场景**
| 场景 | Props | 预期结果 |
|------|-------|---------|
| 渲染卡片 | title="项目总数", value=100 | 显示标题和数值 |
| 显示图标 | icon="📊" | 显示图标 |
| 前缀 | prefix="¥" | 数值前显示"¥" |
| 后缀 | suffix="万元" | 数值后显示"万元" |
| 金额格式化 | value=1234.56 | 显示"1,234.56万元" |
---
#### TC-012: Modal 组件测试
| 用例ID | TC-012-01 |
|--------|-----------|
| 测试模块 | components/Common/Modal |
| 测试组件 | Modal |
| 优先级 | 高 |
| 前置条件 | 无 |
**测试场景**
| 场景 | Props | 预期结果 |
|------|-------|---------|
| 显示模态框 | visible=true | 显示模态框 |
| 隐藏模态框 | visible=false | 不显示模态框 |
| 标题 | title="新建项目" | 显示标题 |
| 确定按钮 | onOk=mockFn, okText="保存" | 显示"保存"按钮,点击调用mockFn |
| 取消按钮 | onCancel=mockFn, cancelText="取消" | 显示"取消"按钮,点击调用mockFn |
| 加载状态 | confirmLoading=true | 确定按钮显示加载状态 |
| 自定义宽度 | width={800} | 宽度为800px |
| 点击遮罩关闭 | 点击遮罩层 | 调用onCancel |
---
### 1.4 API服务测试
#### TC-013: projectAPI 测试
| 用例ID | TC-013-01 |
|--------|-----------|
| 测试模块 | services/project.js |
| 测试API | projectAPI |
| 优先级 | 高 |
| 前置条件 | 模拟api.get/post/put/delete |
**测试场景**
| 场景 | 调用 | 预期结果 |
|------|------|---------|
| 获取列表 | getList({page:1, pageSize:10}) | api.get('/projects', {params:{page:1, pageSize:10}}) |
| 获取详情 | getDetail(1) | api.get('/projects/1') |
| 创建项目 | create({name:'新项目'}) | api.post('/projects', {name:'新项目'}) |
| 更新项目 | update(1, {name:'更新项目'}) | api.put('/projects/1', {name:'更新项目'}) |
| 删除项目 | delete(1) | api.delete('/projects/1') |
| 批量删除 | batchDelete([1,2,3]) | api.post('/projects/batch-delete', {ids:[1,2,3]}) |
| 获取统计 | getStatistics() | api.get('/projects/statistics') |
| 获取分组统计 | getGroupStatistics({groupBy:'engineering_type'}) | api.get('/projects/statistics/group', {params:{groupBy:'engineering_type'}}) |
| 获取时间统计 | getTimelineStatistics({timeField:'signing_date'}) | api.get('/projects/statistics/timeline', {params:{timeField:'signing_date'}}) |
| 导出项目 | export() | api.get('/projects/export', {responseType:'blob'}) |
---
#### TC-014: userAPI 测试
| 用例ID | TC-014-01 |
|--------|-----------|
| 测试模块 | services/user.js |
| 测试API | userAPI |
| 优先级 | 中 |
| 前置条件 | 模拟api.get/post/put/delete |
**测试场景**
| 场景 | 调用 | 预期结果 |
|------|------|---------|
| 获取列表 | getList({page:1, pageSize:10}) | api.get('/users', {params:{page:1, pageSize:10}}) |
| 获取详情 | getDetail(1) | api.get('/users/1') |
| 创建用户 | create({username:'test'}) | api.post('/users', {username:'test'}) |
| 更新用户 | update(1, {realName:'新姓名'}) | api.put('/users/1', {realName:'新姓名'}) |
| 删除用户 | delete(1) | api.delete('/users/1') |
| 重置密码 | resetPassword(1, 'newPassword') | api.post('/users/1/reset-password', {new_password:'newPassword'}) |
---
## 2. 集成测试用例
### 2.1 页面组件测试
#### TC-015: Login 页面测试
| 用例ID | TC-015-01 |
|--------|-----------|
| 测试模块 | pages/Login |
| 测试页面 | Login |
| 优先级 | 高 |
| 前置条件 | 无 |
**测试场景**
| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 渲染登录页 | 访问/login | 显示登录表单、Logo、用户名输入框、密码输入框、登录按钮 |
| 必填字段验证 | 不输入直接点击登录 | 显示"请输入用户名"和"请输入密码"错误 |
| 用户名太短 | 输入2个字符 | 显示"用户名至少3个字符"错误 |
| 密码太短 | 输入5个字符 | 显示"密码至少6个字符"错误 |
| 登录成功 | 输入正确用户名和密码,点击登录 | 调用login API,保存Token到localStorage,跳转到/dashboard |
| 登录失败 | 输入错误密码,点击登录 | 显示"用户名或密码错误"提示 |
| 记住密码 | 勾选"记住密码",点击登录 | localStorage保存用户名和密码(加密) |
| 加载状态 | 点击登录按钮 | 按钮显示加载图标,禁用状态 |
| Enter键提交 | 输入后按Enter键 | 触发登录 |
---
#### TC-016: Dashboard 页面测试
| 用例ID | TC-016-01 |
|--------|-----------|
| 测试模块 | pages/Dashboard |
| 测试页面 | Dashboard |
| 优先级 | 高 |
| 前置条件 | 已登录 |
**测试场景**
| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 渲染仪表盘 | 访问/dashboard | 显示统计卡片、图表、最近项目列表 |
| 加载统计卡片 | 页面加载 | 调用getBasicStatistics API,显示4个统计卡片 |
| 加载图表 | 页面加载 | 调用getTimelineStatistics API,显示项目趋势图 |
| 加载最近项目 | 页面加载 | 调用getProjects API,显示最近5个项目 |
| 点击项目卡片 | 点击统计卡片 | 跳转到对应功能页面 |
| 点击最近项目 | 点击项目项 | 跳转到项目详情页 |
| 刷新数据 | 点击刷新按钮 | 重新获取所有数据 |
| 自动刷新 | 等待5分钟 | 自动刷新统计数据 |
---
#### TC-017: ProjectList 页面测试
| 用例ID | TC-017-01 |
|--------|-----------|
| 测试模块 | pages/ProjectList |
| 测试页面 | ProjectList |
| 优先级 | 高 |
| 前置条件 | 已登录 |
**测试场景**
| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 渲染列表页 | 访问/projects | 显示工具栏、项目表格、分页控件 |
| 加载项目列表 | 页面加载 | 调用getList API,默认参数page=1, pageSize=10 |
| 搜索项目 | 输入关键词,按Enter | 调用getList API,参数包含keyword |
| 按工程类别筛选 | 选择"基建" | 调用getList API,参数engineering_type='基建' |
| 按日期筛选 | 选择日期范围 | 调用getList API,参数包含signing_date_start和signing_date_end |
| 按金额筛选 | 输入金额范围 | 调用getList API,参数包含contract_amount_min和contract_amount_max |
| 组合筛选 | 同时设置多个条件 | 调用getList API,所有条件AND关系 |
| 清除筛选 | 点击"清除"按钮 | 重置所有筛选条件,刷新列表 |
| 排序 | 点击表头"签订日期" | 切换升序/降序,调用getList API,参数sort_by和sort_order |
| 分页 | 点击页码 | 调用getList API,参数page更新 |
| 查看项目 | 点击"查看"按钮 | 跳转到/projects/:id |
| 新建项目 | 点击"新建项目"按钮 | 打开新建项目模态框 |
| 编辑项目 | 点击"编辑"按钮 | 调用getDetail API,填充表单,打开编辑模态框 |
| 删除项目 | 点击"删除"按钮 | 显示确认对话框,确认后调用delete API |
| 权限控制 | 市场部用户查看其他部门项目 | "编辑"和"删除"按钮不显示 |
| 表格行点击 | 点击表格行 | 跳转到项目详情页 |
---
#### TC-018: ProjectDetail 页面测试
| 用例ID | TC-018-01 |
|--------|-----------|
| 测试模块 | pages/ProjectDetail |
| 测试页面 | ProjectDetail |
| 优先级 | 高 |
| 前置条件 | 已登录 |
**测试场景**
| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 渲染详情页 | 访问/projects/1 | 调用getDetail API,显示项目信息卡片 |
| 信息分组显示 | 页面加载 | 显示多个信息分组(基本信息、项目时间、成本管理等) |
| 格式化金额 | 显示金额字段 | 千分位分隔,保留两位小数,单位"万元" |
| 格式化日期 | 显示日期字段 | 格式为YYYY-MM-DD |
| 编辑项目 | 点击"编辑"按钮 | 打开编辑模态框,填充当前数据 |
| 删除项目 | 点击"删除"按钮 | 显示确认对话框,确认后调用delete API,跳转到列表页 |
| 返回列表 | 点击面包屑"项目管理" | 跳转到/projects |
| 字段级权限 | 其他部门用户编辑 | 基础信息字段只读,其他字段可编辑 |
| 金额计算 | 修改成本字段 | 自动计算总成本控制金额 |
| 进度计算 | 修改进度字段 | 自动更新进度百分比 |
---
#### TC-019: UserList 页面测试
| 用例ID | TC-019-01 |
|--------|-----------|
| 测试模块 | pages/UserList |
| 测试页面 | UserList |
| 优先级 | 高 |
| 前置条件 | 已登录,管理员角色 |
**测试场景**
| 场景 | 操作 | 预期结果 |
|------|------|---------|
| 渲染用户列表 | 访问/users | 显示工具栏、用户表格、分页控件 |
| 加载用户列表 | 页面加载 | 调用getUsers API |
| 搜索用户 | 输入关键词,按Enter | 调用getUsers API,参数包含keyword |
| 按部门筛选 | 选择"市场部" | 调用getUsers API,参数department='市场部' |
| 按角色筛选 | 选择"admin" | 调用getUsers API,参数role='admin' |
| 新建用户 | 点击"新建用户"按钮 | 打开新建用户模态框 |
| 编辑用户 | 点击"编辑"按钮 | 调用getUserDetail API,填充表单,打开编辑模态框 |
| 用户名只读 | 编辑模式 | 用户名字段禁用,不可修改 |
| 删除用户 | 点击"删除"按钮 | 显示确认对话框,确认后调用delete API |
| 重置密码 | 点击"重置密码"按钮 | 显示重置密码对话框,输入新密码后调用resetPassword API |
| 权限控制 | 非管理员用户访问 | 重定向到403页或显示权限错误 |
---
### 2.2 交互流程测试
#### TC-020: 完整的项目创建流程测试
| 用例ID | TC-020-01 |
|--------|-----------|
| 测试模块 | 交互流程 |
| 测试场景 | 项目创建流程 |
| 优先级 | 高 |
| 前置条件 | 已登录,有创建项目权限 |
**测试步骤**
| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 访问/projects | 显示项目列表页 |
| 2 | 点击"新建项目"按钮 | 打开新建项目模态框 |
| 3 | 输入合同编号"PRJ001" | 输入框显示"PRJ001" |
| 4 | 输入项目名称"测试项目" | 输入框显示"测试项目" |
| 5 | 选择工程类别"基建" | 下拉框显示"基建" |
| 6 | 输入合同金额"100" | 输入框显示"100" |
| 7 | 选择签订日期"2026-01-25" | 日期选择器显示"2026-01-25" |
| 8 | 不输入必填字段,点击保存 | 显示必填字段验证错误 |
| 9 | 填写所有必填字段,点击保存 | 调用create APIloading=true |
| 10 | API返回成功 | 模态框关闭,列表刷新,显示成功提示"项目创建成功" |
| 11 | API返回失败(合同编号重复) | 显示错误提示"合同编号已存在" |
| 12 | 新项目出现在列表中 | 列表第一行显示新项目 |
---
#### TC-021: 完整的项目编辑流程测试
| 用例ID | TC-021-01 |
|--------|-----------|
| 测试模块 | 交互流程 |
| 测试场景 | 项目编辑流程 |
| 优先级 | 高 |
| 前置条件 | 已登录,有编辑权限 |
**测试步骤**
| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 访问/projects | 显示项目列表页 |
| 2 | 找到项目PRJ001 | 列表显示该项目 |
| 3 | 点击"编辑"按钮 | 调用getDetail API,打开编辑模态框 |
| 4 | 表单填充当前数据 | 输入框显示项目当前值 |
| 5 | 修改项目名称为"更新项目" | 输入框显示"更新项目" |
| 6 | 修改合同金额为"200" | 输入框显示"200" |
| 7 | 点击保存 | 调用update APIloading=true |
| 8 | API返回成功 | 模态框关闭,列表刷新,显示成功提示"项目更新成功" |
| 9 | API返回失败 | 显示错误提示 |
| 10 | 列表显示更新后的数据 | 项目名称和金额更新 |
---
#### TC-022: 完整的项目删除流程测试
| 用例ID | TC-022-01 |
|--------|-----------|
| 测试模块 | 交互流程 |
| 测试场景 | 项目删除流程 |
| 优先级 | 中 |
| 前置条件 | 已登录,有删除权限 |
**测试步骤**
| 步骤 | 操作 | 预期结果 |
|------|------|---------|
| 1 | 访问/projects | 显示项目列表页 |
| 2 | 找到项目PRJ001 | 列表显示该项目 |
| 3 | 点击"删除"按钮 | 显示确认对话框,内容"确定要删除该项目吗?" |
| 4 | 点击"取消"按钮 | 对话框关闭,项目不被删除 |
| 5 | 再次点击"删除"按钮 | 显示确认对话框 |
| 6 | 点击"确定"按钮 | 调用delete API |
| 7 | API返回成功 | 列表刷新,显示成功提示"项目删除成功",项目从列表消失 |
| 8 | API返回失败 | 显示错误提示 |
| 9 | 项目不存在 | 显示404错误提示 |
---
## 3. E2E测试用例
### 3.1 认证流程E2E测试
#### TC-E2E-001: 登录流程E2E测试
| 用例ID | TC-E2E-001 |
|--------|-----------|
| 测试模块 | 登录流程 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 后端服务运行,测试账号存在 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 正常登录 | 1. 访问http://localhost:3000<br>2. 输入用户名"admin"<br>3. 输入密码"password123"<br>4. 点击登录按钮 | 1. 显示登录页<br>2. 自动跳转到/dashboard<br>3. 顶部导航显示用户信息"系统管理员(管理部)" |
| 登录失败 | 1. 访问登录页<br>2. 输入正确用户名<br>3. 输入错误密码<br>4. 点击登录按钮 | 1. 显示错误提示"用户名或密码错误"<br>2. 停留在登录页<br>3. 输入框保留输入值 |
| 字段验证 | 1. 访问登录页<br>2. 不输入任何内容<br>3. 点击登录按钮 | 1. 用户名和密码输入框显示红色边框<br>2. 显示"请输入用户名"和"请输入密码"提示<br>3. 登录按钮无法提交 |
| 记住密码 | 1. 勾选"记住密码"<br>2. 输入用户名和密码<br>3. 点击登录<br>4. 刷新页面 | 1. 用户名和密码自动填充<br>2. 复选框保持选中 |
---
#### TC-E2E-002: 登出流程E2E测试
| 用例ID | TC-E2E-002 |
|--------|-----------|
| 测试模块 | 登出流程 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 登出 | 1. 点击顶部导航"登出"按钮<br>2. 点击确认对话框"确定"按钮 | 1. 显示确认对话框"确定要退出登录吗?"<br>2. 跳转到/login<br>3. localStorage清空 |
| 取消登出 | 1. 点击"登出"按钮<br>2. 点击"取消"按钮 | 1. 对话框关闭<br>2. 保持在当前页面,保持登录状态 |
---
### 3.2 项目管理E2E测试
#### TC-E2E-003: 项目列表E2E测试
| 用例ID | TC-E2E-003 |
|--------|-----------|
| 测试模块 | 项目列表 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 浏览列表 | 1. 点击侧边栏"项目管理"<br>2. 等待页面加载<br>3. 查看项目表格 | 1. 跳转到/projects<br>2. 显示项目表格<br>3. 表格显示项目数据,每页10条 |
| 分页浏览 | 1. 点击"下一页"按钮<br>2. 点击第3页<br>3. 点击"上一页" | 1. 显示第2页数据<br>2. 显示第3页数据<br>3. 显示第2页数据 |
| 搜索项目 | 1. 在搜索框输入"基建"<br>2. 按Enter键 | 1. 表格只显示包含"基建"的项目<br>2. 显示搜索结果数量 |
| 筛选项目 | 1. 点击"工程类别"下拉框<br>2. 选择"基建"<br>3. 点击"签订日期"日期选择器<br>4. 选择日期范围<br>5. 点击"确定" | 1. 表格只显示基建项目<br>2. 签订日期在范围内的项目 |
| 清除筛选 | 1. 设置多个筛选条件<br>2. 点击"清除"按钮 | 1. 所有筛选条件清空<br>2. 列表显示所有数据 |
---
#### TC-E2E-004: 项目创建E2E测试
| 用例ID | TC-E2E-004 |
|--------|-----------|
| 测试模块 | 项目创建 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录,有创建项目权限 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 创建项目 | 1. 访问/projects<br>2. 点击"新建项目"按钮<br>3. 输入合同编号"PRJ999"<br>4. 输入项目名称"E2E测试项目"<br>5. 选择工程类别"基建"<br>6. 输入合同金额"100"<br>7. 选择签订日期"2026-01-25"<br>8. 点击"保存"按钮<br>9. 等待API响应 | 1. 打开新建项目模态框<br>2. 模态框标题"新建项目"<br>3-7. 所有输入框显示输入值<br>8. 按钮显示loading状态<br>9. 模态框关闭<br>10. 显示成功提示"项目创建成功"<br>11. 列表第一行显示新项目 |
| 验证失败创建 | 1. 点击"新建项目"<br>2. 不输入必填字段<br>3. 点击"保存" | 1. 必填字段显示红色边框<br>2. 显示错误提示<br>3. 模态框不关闭 |
---
#### TC-E2E-005: 项目编辑E2E测试
| 用例ID | TC-E2E-005 |
|--------|-----------|
| 测试模块 | 项目编辑 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录,有编辑权限 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 编辑项目 | 1. 访问/projects<br>2. 找到项目PRJ001<br>3. 点击"编辑"按钮<br>4. 修改项目名称为"E2E更新项目"<br>5. 修改合同金额为"200"<br>6. 点击"保存"按钮<br>7. 等待API响应 | 1. 打开编辑项目模态框<br>2. 模态框标题"编辑项目"<br>3. 表单填充当前数据<br>4-5. 输入框显示新值<br>6. 按钮显示loading状态<br>7. 模态框关闭<br>8. 显示成功提示"项目更新成功"<br>9. 列表显示更新后的数据 |
| 权限限制编辑 | 1. 其他部门用户登录<br>2. 编辑项目PRJ001<br>3. 查看表单字段 | 1. 基础信息字段(合同编号、项目名称等)只读<br>2. 成本、财务等字段可编辑 |
---
#### TC-E2E-006: 项目删除E2E测试
| 用例ID | TC-E2E-006 |
|--------|-----------|
| 测试模块 | 项目删除 |
| 测试类型 | E2E |
| 优先级 | 中 |
| 前置条件 | 已登录,有删除权限 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 删除项目 | 1. 访问/projects<br>2. 找到项目PRJ999<br>3. 点击"删除"按钮<br>4. 查看确认对话框内容<br>5. 点击"确定"按钮<br>6. 等待API响应 | 1. 显示确认对话框<br>2. 对话框内容"确定要删除该项目吗?"<br>3. 显示项目名称和合同编号<br>4. 点击"确定"后<br>5. 显示成功提示"项目删除成功"<br>6. 列表刷新,项目消失 |
| 取消删除 | 1. 点击"删除"按钮<br>2. 点击"取消"按钮 | 1. 对话框关闭<br>2. 项目不被删除,仍在列表中 |
---
### 3.3 用户管理E2E测试
#### TC-E2E-007: 用户列表E2E测试
| 用例ID | TC-E2E-007 |
|--------|-----------|
| 测试模块 | 用户列表 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录,管理员角色 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 浏览用户列表 | 1. 点击侧边栏"用户管理"<br>2. 等待页面加载 | 1. 跳转到/users<br>2. 显示用户表格<br>3. 表格显示所有用户 |
| 搜索用户 | 1. 在搜索框输入"admin"<br>2. 按Enter键 | 1. 表格只显示匹配的用户 |
| 按部门筛选 | 1. 点击"部门"下拉框<br>2. 选择"市场部" | 1. 表格只显示市场部用户 |
| 按角色筛选 | 1. 点击"角色"下拉框<br>2. 选择"admin" | 1. 表格只显示管理员用户 |
---
#### TC-E2E-008: 用户创建E2E测试
| 用例ID | TC-E2E-008 |
|--------|-----------|
| 测试模块 | 用户创建 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录,管理员角色 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 创建用户 | 1. 访问/users<br>2. 点击"新建用户"按钮<br>3. 输入用户名"testuser"<br>4. 输入密码"password123"<br>5. 输入真实姓名"测试用户"<br>6. 选择部门"管理部"<br>7. 选择角色"admin"<br>8. 点击"保存"按钮<br>9. 等待API响应 | 1. 打开新建用户模态框<br>2. 模态框标题"新建用户"<br>3-7. 所有输入框显示输入值<br>8. 按钮显示loading状态<br>9. 模态框关闭<br>10. 显示成功提示"用户创建成功"<br>11. 列表显示新用户 |
| 用户名重复 | 1. 创建用户,用户名"admin"<br>2. 点击"保存" | 1. 显示错误提示"用户名已存在"<br>2. 模态框不关闭 |
---
#### TC-E2E-009: 用户编辑E2E测试
| 用例ID | TC-E2E-009 |
|--------|-----------|
| 测试模块 | 用户编辑 |
| 测试类型 | E2E |
| 优先级 | 中 |
| 前置条件 | 已登录,管理员角色 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 编辑用户 | 1. 访问/users<br>2. 找到用户testuser<br>3. 点击"编辑"按钮<br>4. 修改真实姓名为"测试用户更新"<br>5. 修改邮箱为新邮箱<br>6. 点击"保存"按钮<br>7. 等待API响应 | 1. 打开编辑用户模态框<br>2. 用户名字段只读<br>3. 其他字段可编辑<br>4-5. 输入框显示新值<br>6. 按钮显示loading状态<br>7. 模态框关闭<br>8. 显示成功提示<br>9. 列表显示更新后的数据 |
---
#### TC-E2E-010: 用户删除E2E测试
| 用例ID | TC-E2E-010 |
|--------|-----------|
| 测试模块 | 用户删除 |
| 测试类型 | E2E |
| 优先级 | 中 |
| 前置条件 | 已登录,管理员角色 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 删除用户 | 1. 访问/users<br>2. 找到用户testuser<br>3. 点击"删除"按钮<br>4. 点击"确定"按钮<br>5. 等待API响应 | 1. 显示确认对话框<br>2. 点击"确定"后<br>3. 显示成功提示"用户删除成功"<br>4. 列表刷新,用户消失 |
---
#### TC-E2E-011: 重置密码E2E测试
| 用例ID | TC-E2E-011 |
|--------|-----------|
| 测试模块 | 重置密码 |
| 测试类型 | E2E |
| 优先级 | 中 |
| 前置条件 | 已登录,管理员角色 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 重置密码 | 1. 访问/users<br>2. 找到用户testuser<br>3. 点击"重置密码"按钮<br>4. 输入新密码"newpassword123"<br>5. 点击"确定"按钮<br>6. 等待API响应 | 1. 显示重置密码对话框<br>2. 输入框显示新密码<br>3. 点击"确定"后<br>4. 显示成功提示"密码重置成功" |
---
### 3.4 统计分析E2E测试
#### TC-E2E-012: 仪表盘E2E测试
| 用例ID | TC-E2E-012 |
|--------|-----------|
| 测试模块 | 仪表盘 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 查看仪表盘 | 1. 访问/dashboard<br>2. 等待页面加载 | 1. 显示4个统计卡片<br>2. 显示项目趋势图<br>3. 显示最近项目列表 |
| 查看统计卡片 | 1. 查看统计卡片数据 | 1. 项目总数、进行中、已完成、总合同金额<br>2. 数值格式化(千分位、保留两位小数) |
| 查看图表 | 1. 鼠标悬停在图表上 | 1. 显示Tooltip,显示详细数据 |
| 点击最近项目 | 1. 点击最近项目列表项 | 1. 跳转到项目详情页 |
| 刷新数据 | 1. 点击刷新按钮 | 1. 重新获取所有数据 |
---
#### TC-E2E-013: 项目统计E2E测试
| 用例ID | TC-E2E-013 |
|--------|-----------|
| 测试模块 | 项目统计 |
| 测试类型 | E2E |
| 优先级 | 高 |
| 前置条件 | 已登录 |
**测试场景**
| 场景 | 步骤 | 预期结果 |
|------|------|---------|
| 查看统计页 | 1. 点击侧边栏"项目统计"<br>2. 等待页面加载 | 1. 显示筛选工具栏<br>2. 显示统计图表<br>3. 显示分组统计表格 |
| 基础统计 | 1. 查看基础统计卡片 | 1. 显示8个统计卡片<br>2. 数值格式化 |
| 分组统计 | 1. 查看按工程类别分组 | 1. 显示饼图和柱状图<br>2. 显示分组统计表格 |
| 时间维度统计 | 1. 选择时间维度"按月"<br>2. 查看折线图 | 1. 显示每月项目数量和金额趋势 |
| 筛选统计 | 1. 选择工程类别"基建"<br>2. 选择日期范围 | 1. 所有统计只显示符合条件的数据 |
| 导出报表 | 1. 点击"导出报表"按钮<br>2. 等待下载完成 | 1. 下载Excel文件<br>2. 文件名"项目统计_YYYY-MM-DD.xlsx" |
---
## 4. 测试数据准备
### 4.1 测试用户数据
```javascript
const testUsers = [
{
id: 1,
username: 'admin',
password: 'password123',
real_name: '系统管理员',
department: '管理部',
role: 'admin',
email: 'admin@example.com',
is_active: true,
},
{
id: 2,
username: 'market',
password: 'password123',
real_name: '市场部用户',
department: '市场部',
role: 'market',
email: 'market@example.com',
is_active: true,
},
{
id: 3,
username: 'other',
password: 'password123',
real_name: '其他部门用户',
department: '技术部',
role: 'other',
email: 'other@example.com',
is_active: true,
},
];
```
### 4.2 测试项目数据
```javascript
const testProjects = [
{
id: 1,
project_no: 'PRJ001',
name: '某电力基建工程项目',
engineering_type: '基建',
contract_amount: 950.00,
signing_date: '2026-01-01',
start_date: '2026-01-15',
planned_end_date: '2026-12-31',
project_department: '项目部一',
project_leader: '李四',
created_by: 2,
},
{
id: 2,
project_no: 'PRJ002',
name: '业扩工程项目',
engineering_type: '业扩',
contract_amount: 500.00,
signing_date: '2026-01-10',
start_date: '2026-01-20',
planned_end_date: '2026-06-30',
project_department: '项目部二',
project_leader: '王五',
created_by: 2,
},
];
```
---
## 5. 测试覆盖率目标
| 类型 | 目标覆盖率 |
|------|----------|
| 语句覆盖率 | 80% |
| 分支覆盖率 | 75% |
| 函数覆盖率 | 80% |
| 行覆盖率 | 80% |
---
**文档维护**: 前端程序员
**文档类型**: 测试用例文档
**最后更新**: 2026-01-25
File diff suppressed because it is too large Load Diff
+13
View File
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>海洋项目管理系统</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
# @babel/helper-globals
> A collection of JavaScript globals for Babel internal usage
See our website [@babel/helper-globals](https://babeljs.io/docs/babel-helper-globals) for more information.
## Install
Using npm:
```sh
npm install --save @babel/helper-globals
```
or using yarn:
```sh
yarn add @babel/helper-globals
```
+911
View File
@@ -0,0 +1,911 @@
[
"AbortController",
"AbortSignal",
"AbsoluteOrientationSensor",
"AbstractRange",
"Accelerometer",
"AI",
"AICreateMonitor",
"AITextSession",
"AnalyserNode",
"Animation",
"AnimationEffect",
"AnimationEvent",
"AnimationPlaybackEvent",
"AnimationTimeline",
"AsyncDisposableStack",
"Attr",
"Audio",
"AudioBuffer",
"AudioBufferSourceNode",
"AudioContext",
"AudioData",
"AudioDecoder",
"AudioDestinationNode",
"AudioEncoder",
"AudioListener",
"AudioNode",
"AudioParam",
"AudioParamMap",
"AudioProcessingEvent",
"AudioScheduledSourceNode",
"AudioSinkInfo",
"AudioWorklet",
"AudioWorkletGlobalScope",
"AudioWorkletNode",
"AudioWorkletProcessor",
"AuthenticatorAssertionResponse",
"AuthenticatorAttestationResponse",
"AuthenticatorResponse",
"BackgroundFetchManager",
"BackgroundFetchRecord",
"BackgroundFetchRegistration",
"BarcodeDetector",
"BarProp",
"BaseAudioContext",
"BatteryManager",
"BeforeUnloadEvent",
"BiquadFilterNode",
"Blob",
"BlobEvent",
"Bluetooth",
"BluetoothCharacteristicProperties",
"BluetoothDevice",
"BluetoothRemoteGATTCharacteristic",
"BluetoothRemoteGATTDescriptor",
"BluetoothRemoteGATTServer",
"BluetoothRemoteGATTService",
"BluetoothUUID",
"BroadcastChannel",
"BrowserCaptureMediaStreamTrack",
"ByteLengthQueuingStrategy",
"Cache",
"CacheStorage",
"CanvasCaptureMediaStream",
"CanvasCaptureMediaStreamTrack",
"CanvasGradient",
"CanvasPattern",
"CanvasRenderingContext2D",
"CaptureController",
"CaretPosition",
"CDATASection",
"ChannelMergerNode",
"ChannelSplitterNode",
"ChapterInformation",
"CharacterBoundsUpdateEvent",
"CharacterData",
"Clipboard",
"ClipboardEvent",
"ClipboardItem",
"CloseEvent",
"CloseWatcher",
"CommandEvent",
"Comment",
"CompositionEvent",
"CompressionStream",
"ConstantSourceNode",
"ContentVisibilityAutoStateChangeEvent",
"ConvolverNode",
"CookieChangeEvent",
"CookieDeprecationLabel",
"CookieStore",
"CookieStoreManager",
"CountQueuingStrategy",
"Credential",
"CredentialsContainer",
"CropTarget",
"Crypto",
"CryptoKey",
"CSPViolationReportBody",
"CSS",
"CSSAnimation",
"CSSConditionRule",
"CSSContainerRule",
"CSSCounterStyleRule",
"CSSFontFaceRule",
"CSSFontFeatureValuesRule",
"CSSFontPaletteValuesRule",
"CSSGroupingRule",
"CSSImageValue",
"CSSImportRule",
"CSSKeyframeRule",
"CSSKeyframesRule",
"CSSKeywordValue",
"CSSLayerBlockRule",
"CSSLayerStatementRule",
"CSSMarginRule",
"CSSMathClamp",
"CSSMathInvert",
"CSSMathMax",
"CSSMathMin",
"CSSMathNegate",
"CSSMathProduct",
"CSSMathSum",
"CSSMathValue",
"CSSMatrixComponent",
"CSSMediaRule",
"CSSNamespaceRule",
"CSSNestedDeclarations",
"CSSNumericArray",
"CSSNumericValue",
"CSSPageDescriptors",
"CSSPageRule",
"CSSPerspective",
"CSSPositionTryDescriptors",
"CSSPositionTryRule",
"CSSPositionValue",
"CSSPropertyRule",
"CSSRotate",
"CSSRule",
"CSSRuleList",
"CSSScale",
"CSSScopeRule",
"CSSSkew",
"CSSSkewX",
"CSSSkewY",
"CSSStartingStyleRule",
"CSSStyleDeclaration",
"CSSStyleRule",
"CSSStyleSheet",
"CSSStyleValue",
"CSSSupportsRule",
"CSSTransformComponent",
"CSSTransformValue",
"CSSTransition",
"CSSTranslate",
"CSSUnitValue",
"CSSUnparsedValue",
"CSSVariableReferenceValue",
"CSSViewTransitionRule",
"CustomElementRegistry",
"CustomEvent",
"CustomStateSet",
"DataTransfer",
"DataTransferItem",
"DataTransferItemList",
"DecompressionStream",
"DelayNode",
"DelegatedInkTrailPresenter",
"DeviceMotionEvent",
"DeviceMotionEventAcceleration",
"DeviceMotionEventRotationRate",
"DeviceOrientationEvent",
"DevicePosture",
"DisposableStack",
"Document",
"DocumentFragment",
"DocumentPictureInPicture",
"DocumentPictureInPictureEvent",
"DocumentTimeline",
"DocumentType",
"DOMError",
"DOMException",
"DOMImplementation",
"DOMMatrix",
"DOMMatrixReadOnly",
"DOMParser",
"DOMPoint",
"DOMPointReadOnly",
"DOMQuad",
"DOMRect",
"DOMRectList",
"DOMRectReadOnly",
"DOMStringList",
"DOMStringMap",
"DOMTokenList",
"DragEvent",
"DynamicsCompressorNode",
"EditContext",
"Element",
"ElementInternals",
"EncodedAudioChunk",
"EncodedVideoChunk",
"ErrorEvent",
"Event",
"EventCounts",
"EventSource",
"EventTarget",
"External",
"EyeDropper",
"FeaturePolicy",
"FederatedCredential",
"Fence",
"FencedFrameConfig",
"FetchLaterResult",
"File",
"FileList",
"FileReader",
"FileSystem",
"FileSystemDirectoryEntry",
"FileSystemDirectoryHandle",
"FileSystemDirectoryReader",
"FileSystemEntry",
"FileSystemFileEntry",
"FileSystemFileHandle",
"FileSystemHandle",
"FileSystemObserver",
"FileSystemWritableFileStream",
"FocusEvent",
"FontData",
"FontFace",
"FontFaceSet",
"FontFaceSetLoadEvent",
"FormData",
"FormDataEvent",
"FragmentDirective",
"GainNode",
"Gamepad",
"GamepadAxisMoveEvent",
"GamepadButton",
"GamepadButtonEvent",
"GamepadEvent",
"GamepadHapticActuator",
"GamepadPose",
"Geolocation",
"GeolocationCoordinates",
"GeolocationPosition",
"GeolocationPositionError",
"GPU",
"GPUAdapter",
"GPUAdapterInfo",
"GPUBindGroup",
"GPUBindGroupLayout",
"GPUBuffer",
"GPUBufferUsage",
"GPUCanvasContext",
"GPUColorWrite",
"GPUCommandBuffer",
"GPUCommandEncoder",
"GPUCompilationInfo",
"GPUCompilationMessage",
"GPUComputePassEncoder",
"GPUComputePipeline",
"GPUDevice",
"GPUDeviceLostInfo",
"GPUError",
"GPUExternalTexture",
"GPUInternalError",
"GPUMapMode",
"GPUOutOfMemoryError",
"GPUPipelineError",
"GPUPipelineLayout",
"GPUQuerySet",
"GPUQueue",
"GPURenderBundle",
"GPURenderBundleEncoder",
"GPURenderPassEncoder",
"GPURenderPipeline",
"GPUSampler",
"GPUShaderModule",
"GPUShaderStage",
"GPUSupportedFeatures",
"GPUSupportedLimits",
"GPUTexture",
"GPUTextureUsage",
"GPUTextureView",
"GPUUncapturedErrorEvent",
"GPUValidationError",
"GravitySensor",
"Gyroscope",
"HashChangeEvent",
"Headers",
"HID",
"HIDConnectionEvent",
"HIDDevice",
"HIDInputReportEvent",
"Highlight",
"HighlightRegistry",
"History",
"HTMLAllCollection",
"HTMLAnchorElement",
"HTMLAreaElement",
"HTMLAudioElement",
"HTMLBaseElement",
"HTMLBodyElement",
"HTMLBRElement",
"HTMLButtonElement",
"HTMLCanvasElement",
"HTMLCollection",
"HTMLDataElement",
"HTMLDataListElement",
"HTMLDetailsElement",
"HTMLDialogElement",
"HTMLDirectoryElement",
"HTMLDivElement",
"HTMLDListElement",
"HTMLDocument",
"HTMLElement",
"HTMLEmbedElement",
"HTMLFencedFrameElement",
"HTMLFieldSetElement",
"HTMLFontElement",
"HTMLFormControlsCollection",
"HTMLFormElement",
"HTMLFrameElement",
"HTMLFrameSetElement",
"HTMLHeadElement",
"HTMLHeadingElement",
"HTMLHRElement",
"HTMLHtmlElement",
"HTMLIFrameElement",
"HTMLImageElement",
"HTMLInputElement",
"HTMLLabelElement",
"HTMLLegendElement",
"HTMLLIElement",
"HTMLLinkElement",
"HTMLMapElement",
"HTMLMarqueeElement",
"HTMLMediaElement",
"HTMLMenuElement",
"HTMLMetaElement",
"HTMLMeterElement",
"HTMLModElement",
"HTMLObjectElement",
"HTMLOListElement",
"HTMLOptGroupElement",
"HTMLOptionElement",
"HTMLOptionsCollection",
"HTMLOutputElement",
"HTMLParagraphElement",
"HTMLParamElement",
"HTMLPictureElement",
"HTMLPreElement",
"HTMLProgressElement",
"HTMLQuoteElement",
"HTMLScriptElement",
"HTMLSelectedContentElement",
"HTMLSelectElement",
"HTMLSlotElement",
"HTMLSourceElement",
"HTMLSpanElement",
"HTMLStyleElement",
"HTMLTableCaptionElement",
"HTMLTableCellElement",
"HTMLTableColElement",
"HTMLTableElement",
"HTMLTableRowElement",
"HTMLTableSectionElement",
"HTMLTemplateElement",
"HTMLTextAreaElement",
"HTMLTimeElement",
"HTMLTitleElement",
"HTMLTrackElement",
"HTMLUListElement",
"HTMLUnknownElement",
"HTMLVideoElement",
"IDBCursor",
"IDBCursorWithValue",
"IDBDatabase",
"IDBFactory",
"IDBIndex",
"IDBKeyRange",
"IDBObjectStore",
"IDBOpenDBRequest",
"IDBRequest",
"IDBTransaction",
"IDBVersionChangeEvent",
"IdentityCredential",
"IdentityCredentialError",
"IdentityProvider",
"IdleDeadline",
"IdleDetector",
"IIRFilterNode",
"Image",
"ImageBitmap",
"ImageBitmapRenderingContext",
"ImageCapture",
"ImageData",
"ImageDecoder",
"ImageTrack",
"ImageTrackList",
"Ink",
"InputDeviceCapabilities",
"InputDeviceInfo",
"InputEvent",
"IntersectionObserver",
"IntersectionObserverEntry",
"Keyboard",
"KeyboardEvent",
"KeyboardLayoutMap",
"KeyframeEffect",
"LanguageDetector",
"LargestContentfulPaint",
"LaunchParams",
"LaunchQueue",
"LayoutShift",
"LayoutShiftAttribution",
"LinearAccelerationSensor",
"Location",
"Lock",
"LockManager",
"MathMLElement",
"MediaCapabilities",
"MediaCapabilitiesInfo",
"MediaDeviceInfo",
"MediaDevices",
"MediaElementAudioSourceNode",
"MediaEncryptedEvent",
"MediaError",
"MediaKeyError",
"MediaKeyMessageEvent",
"MediaKeys",
"MediaKeySession",
"MediaKeyStatusMap",
"MediaKeySystemAccess",
"MediaList",
"MediaMetadata",
"MediaQueryList",
"MediaQueryListEvent",
"MediaRecorder",
"MediaRecorderErrorEvent",
"MediaSession",
"MediaSource",
"MediaSourceHandle",
"MediaStream",
"MediaStreamAudioDestinationNode",
"MediaStreamAudioSourceNode",
"MediaStreamEvent",
"MediaStreamTrack",
"MediaStreamTrackAudioSourceNode",
"MediaStreamTrackAudioStats",
"MediaStreamTrackEvent",
"MediaStreamTrackGenerator",
"MediaStreamTrackProcessor",
"MediaStreamTrackVideoStats",
"MessageChannel",
"MessageEvent",
"MessagePort",
"MIDIAccess",
"MIDIConnectionEvent",
"MIDIInput",
"MIDIInputMap",
"MIDIMessageEvent",
"MIDIOutput",
"MIDIOutputMap",
"MIDIPort",
"MimeType",
"MimeTypeArray",
"ModelGenericSession",
"ModelManager",
"MouseEvent",
"MutationEvent",
"MutationObserver",
"MutationRecord",
"NamedNodeMap",
"NavigateEvent",
"Navigation",
"NavigationActivation",
"NavigationCurrentEntryChangeEvent",
"NavigationDestination",
"NavigationHistoryEntry",
"NavigationPreloadManager",
"NavigationTransition",
"Navigator",
"NavigatorLogin",
"NavigatorManagedData",
"NavigatorUAData",
"NetworkInformation",
"Node",
"NodeFilter",
"NodeIterator",
"NodeList",
"Notification",
"NotifyPaintEvent",
"NotRestoredReasonDetails",
"NotRestoredReasons",
"Observable",
"OfflineAudioCompletionEvent",
"OfflineAudioContext",
"OffscreenCanvas",
"OffscreenCanvasRenderingContext2D",
"Option",
"OrientationSensor",
"OscillatorNode",
"OTPCredential",
"OverconstrainedError",
"PageRevealEvent",
"PageSwapEvent",
"PageTransitionEvent",
"PannerNode",
"PasswordCredential",
"Path2D",
"PaymentAddress",
"PaymentManager",
"PaymentMethodChangeEvent",
"PaymentRequest",
"PaymentRequestUpdateEvent",
"PaymentResponse",
"Performance",
"PerformanceElementTiming",
"PerformanceEntry",
"PerformanceEventTiming",
"PerformanceLongAnimationFrameTiming",
"PerformanceLongTaskTiming",
"PerformanceMark",
"PerformanceMeasure",
"PerformanceNavigation",
"PerformanceNavigationTiming",
"PerformanceObserver",
"PerformanceObserverEntryList",
"PerformancePaintTiming",
"PerformanceResourceTiming",
"PerformanceScriptTiming",
"PerformanceServerTiming",
"PerformanceTiming",
"PeriodicSyncManager",
"PeriodicWave",
"Permissions",
"PermissionStatus",
"PERSISTENT",
"PictureInPictureEvent",
"PictureInPictureWindow",
"Plugin",
"PluginArray",
"PointerEvent",
"PopStateEvent",
"Presentation",
"PresentationAvailability",
"PresentationConnection",
"PresentationConnectionAvailableEvent",
"PresentationConnectionCloseEvent",
"PresentationConnectionList",
"PresentationReceiver",
"PresentationRequest",
"PressureObserver",
"PressureRecord",
"ProcessingInstruction",
"Profiler",
"ProgressEvent",
"PromiseRejectionEvent",
"ProtectedAudience",
"PublicKeyCredential",
"PushManager",
"PushSubscription",
"PushSubscriptionOptions",
"RadioNodeList",
"Range",
"ReadableByteStreamController",
"ReadableStream",
"ReadableStreamBYOBReader",
"ReadableStreamBYOBRequest",
"ReadableStreamDefaultController",
"ReadableStreamDefaultReader",
"RelativeOrientationSensor",
"RemotePlayback",
"ReportBody",
"ReportingObserver",
"Request",
"ResizeObserver",
"ResizeObserverEntry",
"ResizeObserverSize",
"Response",
"RestrictionTarget",
"RTCCertificate",
"RTCDataChannel",
"RTCDataChannelEvent",
"RTCDtlsTransport",
"RTCDTMFSender",
"RTCDTMFToneChangeEvent",
"RTCEncodedAudioFrame",
"RTCEncodedVideoFrame",
"RTCError",
"RTCErrorEvent",
"RTCIceCandidate",
"RTCIceTransport",
"RTCPeerConnection",
"RTCPeerConnectionIceErrorEvent",
"RTCPeerConnectionIceEvent",
"RTCRtpReceiver",
"RTCRtpScriptTransform",
"RTCRtpSender",
"RTCRtpTransceiver",
"RTCSctpTransport",
"RTCSessionDescription",
"RTCStatsReport",
"RTCTrackEvent",
"Scheduler",
"Scheduling",
"Screen",
"ScreenDetailed",
"ScreenDetails",
"ScreenOrientation",
"ScriptProcessorNode",
"ScrollTimeline",
"SecurityPolicyViolationEvent",
"Selection",
"Sensor",
"SensorErrorEvent",
"Serial",
"SerialPort",
"ServiceWorker",
"ServiceWorkerContainer",
"ServiceWorkerRegistration",
"ShadowRoot",
"SharedStorage",
"SharedStorageAppendMethod",
"SharedStorageClearMethod",
"SharedStorageDeleteMethod",
"SharedStorageModifierMethod",
"SharedStorageSetMethod",
"SharedStorageWorklet",
"SharedWorker",
"SnapEvent",
"SourceBuffer",
"SourceBufferList",
"SpeechSynthesis",
"SpeechSynthesisErrorEvent",
"SpeechSynthesisEvent",
"SpeechSynthesisUtterance",
"SpeechSynthesisVoice",
"StaticRange",
"StereoPannerNode",
"Storage",
"StorageBucket",
"StorageBucketManager",
"StorageEvent",
"StorageManager",
"StylePropertyMap",
"StylePropertyMapReadOnly",
"StyleSheet",
"StyleSheetList",
"SubmitEvent",
"Subscriber",
"SubtleCrypto",
"SuppressedError",
"SVGAElement",
"SVGAngle",
"SVGAnimatedAngle",
"SVGAnimatedBoolean",
"SVGAnimatedEnumeration",
"SVGAnimatedInteger",
"SVGAnimatedLength",
"SVGAnimatedLengthList",
"SVGAnimatedNumber",
"SVGAnimatedNumberList",
"SVGAnimatedPreserveAspectRatio",
"SVGAnimatedRect",
"SVGAnimatedString",
"SVGAnimatedTransformList",
"SVGAnimateElement",
"SVGAnimateMotionElement",
"SVGAnimateTransformElement",
"SVGAnimationElement",
"SVGCircleElement",
"SVGClipPathElement",
"SVGComponentTransferFunctionElement",
"SVGDefsElement",
"SVGDescElement",
"SVGElement",
"SVGEllipseElement",
"SVGFEBlendElement",
"SVGFEColorMatrixElement",
"SVGFEComponentTransferElement",
"SVGFECompositeElement",
"SVGFEConvolveMatrixElement",
"SVGFEDiffuseLightingElement",
"SVGFEDisplacementMapElement",
"SVGFEDistantLightElement",
"SVGFEDropShadowElement",
"SVGFEFloodElement",
"SVGFEFuncAElement",
"SVGFEFuncBElement",
"SVGFEFuncGElement",
"SVGFEFuncRElement",
"SVGFEGaussianBlurElement",
"SVGFEImageElement",
"SVGFEMergeElement",
"SVGFEMergeNodeElement",
"SVGFEMorphologyElement",
"SVGFEOffsetElement",
"SVGFEPointLightElement",
"SVGFESpecularLightingElement",
"SVGFESpotLightElement",
"SVGFETileElement",
"SVGFETurbulenceElement",
"SVGFilterElement",
"SVGForeignObjectElement",
"SVGGElement",
"SVGGeometryElement",
"SVGGradientElement",
"SVGGraphicsElement",
"SVGImageElement",
"SVGLength",
"SVGLengthList",
"SVGLinearGradientElement",
"SVGLineElement",
"SVGMarkerElement",
"SVGMaskElement",
"SVGMatrix",
"SVGMetadataElement",
"SVGMPathElement",
"SVGNumber",
"SVGNumberList",
"SVGPathElement",
"SVGPatternElement",
"SVGPoint",
"SVGPointList",
"SVGPolygonElement",
"SVGPolylineElement",
"SVGPreserveAspectRatio",
"SVGRadialGradientElement",
"SVGRect",
"SVGRectElement",
"SVGScriptElement",
"SVGSetElement",
"SVGStopElement",
"SVGStringList",
"SVGStyleElement",
"SVGSVGElement",
"SVGSwitchElement",
"SVGSymbolElement",
"SVGTextContentElement",
"SVGTextElement",
"SVGTextPathElement",
"SVGTextPositioningElement",
"SVGTitleElement",
"SVGTransform",
"SVGTransformList",
"SVGTSpanElement",
"SVGUnitTypes",
"SVGUseElement",
"SVGViewElement",
"SyncManager",
"TaskAttributionTiming",
"TaskController",
"TaskPriorityChangeEvent",
"TaskSignal",
"TEMPORARY",
"Text",
"TextDecoder",
"TextDecoderStream",
"TextEncoder",
"TextEncoderStream",
"TextEvent",
"TextFormat",
"TextFormatUpdateEvent",
"TextMetrics",
"TextTrack",
"TextTrackCue",
"TextTrackCueList",
"TextTrackList",
"TextUpdateEvent",
"TimeEvent",
"TimeRanges",
"ToggleEvent",
"Touch",
"TouchEvent",
"TouchList",
"TrackEvent",
"TransformStream",
"TransformStreamDefaultController",
"TransitionEvent",
"TreeWalker",
"TrustedHTML",
"TrustedScript",
"TrustedScriptURL",
"TrustedTypePolicy",
"TrustedTypePolicyFactory",
"UIEvent",
"URL",
"URLPattern",
"URLSearchParams",
"USB",
"USBAlternateInterface",
"USBConfiguration",
"USBConnectionEvent",
"USBDevice",
"USBEndpoint",
"USBInterface",
"USBInTransferResult",
"USBIsochronousInTransferPacket",
"USBIsochronousInTransferResult",
"USBIsochronousOutTransferPacket",
"USBIsochronousOutTransferResult",
"USBOutTransferResult",
"UserActivation",
"ValidityState",
"VideoColorSpace",
"VideoDecoder",
"VideoEncoder",
"VideoFrame",
"VideoPlaybackQuality",
"ViewTimeline",
"ViewTransition",
"ViewTransitionTypeSet",
"VirtualKeyboard",
"VirtualKeyboardGeometryChangeEvent",
"VisibilityStateEntry",
"VisualViewport",
"VTTCue",
"VTTRegion",
"WakeLock",
"WakeLockSentinel",
"WaveShaperNode",
"WebAssembly",
"WebGL2RenderingContext",
"WebGLActiveInfo",
"WebGLBuffer",
"WebGLContextEvent",
"WebGLFramebuffer",
"WebGLObject",
"WebGLProgram",
"WebGLQuery",
"WebGLRenderbuffer",
"WebGLRenderingContext",
"WebGLSampler",
"WebGLShader",
"WebGLShaderPrecisionFormat",
"WebGLSync",
"WebGLTexture",
"WebGLTransformFeedback",
"WebGLUniformLocation",
"WebGLVertexArrayObject",
"WebSocket",
"WebSocketError",
"WebSocketStream",
"WebTransport",
"WebTransportBidirectionalStream",
"WebTransportDatagramDuplexStream",
"WebTransportError",
"WebTransportReceiveStream",
"WebTransportSendStream",
"WGSLLanguageFeatures",
"WheelEvent",
"Window",
"WindowControlsOverlay",
"WindowControlsOverlayGeometryChangeEvent",
"Worker",
"Worklet",
"WorkletGlobalScope",
"WritableStream",
"WritableStreamDefaultController",
"WritableStreamDefaultWriter",
"XMLDocument",
"XMLHttpRequest",
"XMLHttpRequestEventTarget",
"XMLHttpRequestUpload",
"XMLSerializer",
"XPathEvaluator",
"XPathExpression",
"XPathResult",
"XRAnchor",
"XRAnchorSet",
"XRBoundedReferenceSpace",
"XRCamera",
"XRCPUDepthInformation",
"XRDepthInformation",
"XRDOMOverlayState",
"XRFrame",
"XRHand",
"XRHitTestResult",
"XRHitTestSource",
"XRInputSource",
"XRInputSourceArray",
"XRInputSourceEvent",
"XRInputSourcesChangeEvent",
"XRJointPose",
"XRJointSpace",
"XRLayer",
"XRLightEstimate",
"XRLightProbe",
"XRPose",
"XRRay",
"XRReferenceSpace",
"XRReferenceSpaceEvent",
"XRRenderState",
"XRRigidTransform",
"XRSession",
"XRSessionEvent",
"XRSpace",
"XRSystem",
"XRTransientInputHitTestResult",
"XRTransientInputHitTestSource",
"XRView",
"XRViewerPose",
"XRViewport",
"XRWebGLBinding",
"XRWebGLDepthInformation",
"XRWebGLLayer",
"XSLTProcessor"
]
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
# @babel/helper-string-parser
> A utility package to parse strings
See our website [@babel/helper-string-parser](https://babeljs.io/docs/babel-helper-string-parser) for more information.
## Install
Using npm:
```sh
npm install --save @babel/helper-string-parser
```
or using yarn:
```sh
yarn add @babel/helper-string-parser
```
+295
View File
@@ -0,0 +1,295 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.readCodePoint = readCodePoint;
exports.readInt = readInt;
exports.readStringContents = readStringContents;
var _isDigit = function isDigit(code) {
return code >= 48 && code <= 57;
};
const forbiddenNumericSeparatorSiblings = {
decBinOct: new Set([46, 66, 69, 79, 95, 98, 101, 111]),
hex: new Set([46, 88, 95, 120])
};
const isAllowedNumericSeparatorSibling = {
bin: ch => ch === 48 || ch === 49,
oct: ch => ch >= 48 && ch <= 55,
dec: ch => ch >= 48 && ch <= 57,
hex: ch => ch >= 48 && ch <= 57 || ch >= 65 && ch <= 70 || ch >= 97 && ch <= 102
};
function readStringContents(type, input, pos, lineStart, curLine, errors) {
const initialPos = pos;
const initialLineStart = lineStart;
const initialCurLine = curLine;
let out = "";
let firstInvalidLoc = null;
let chunkStart = pos;
const {
length
} = input;
for (;;) {
if (pos >= length) {
errors.unterminated(initialPos, initialLineStart, initialCurLine);
out += input.slice(chunkStart, pos);
break;
}
const ch = input.charCodeAt(pos);
if (isStringEnd(type, ch, input, pos)) {
out += input.slice(chunkStart, pos);
break;
}
if (ch === 92) {
out += input.slice(chunkStart, pos);
const res = readEscapedChar(input, pos, lineStart, curLine, type === "template", errors);
if (res.ch === null && !firstInvalidLoc) {
firstInvalidLoc = {
pos,
lineStart,
curLine
};
} else {
out += res.ch;
}
({
pos,
lineStart,
curLine
} = res);
chunkStart = pos;
} else if (ch === 8232 || ch === 8233) {
++pos;
++curLine;
lineStart = pos;
} else if (ch === 10 || ch === 13) {
if (type === "template") {
out += input.slice(chunkStart, pos) + "\n";
++pos;
if (ch === 13 && input.charCodeAt(pos) === 10) {
++pos;
}
++curLine;
chunkStart = lineStart = pos;
} else {
errors.unterminated(initialPos, initialLineStart, initialCurLine);
}
} else {
++pos;
}
}
return {
pos,
str: out,
firstInvalidLoc,
lineStart,
curLine,
containsInvalid: !!firstInvalidLoc
};
}
function isStringEnd(type, ch, input, pos) {
if (type === "template") {
return ch === 96 || ch === 36 && input.charCodeAt(pos + 1) === 123;
}
return ch === (type === "double" ? 34 : 39);
}
function readEscapedChar(input, pos, lineStart, curLine, inTemplate, errors) {
const throwOnInvalid = !inTemplate;
pos++;
const res = ch => ({
pos,
ch,
lineStart,
curLine
});
const ch = input.charCodeAt(pos++);
switch (ch) {
case 110:
return res("\n");
case 114:
return res("\r");
case 120:
{
let code;
({
code,
pos
} = readHexChar(input, pos, lineStart, curLine, 2, false, throwOnInvalid, errors));
return res(code === null ? null : String.fromCharCode(code));
}
case 117:
{
let code;
({
code,
pos
} = readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors));
return res(code === null ? null : String.fromCodePoint(code));
}
case 116:
return res("\t");
case 98:
return res("\b");
case 118:
return res("\u000b");
case 102:
return res("\f");
case 13:
if (input.charCodeAt(pos) === 10) {
++pos;
}
case 10:
lineStart = pos;
++curLine;
case 8232:
case 8233:
return res("");
case 56:
case 57:
if (inTemplate) {
return res(null);
} else {
errors.strictNumericEscape(pos - 1, lineStart, curLine);
}
default:
if (ch >= 48 && ch <= 55) {
const startPos = pos - 1;
const match = /^[0-7]+/.exec(input.slice(startPos, pos + 2));
let octalStr = match[0];
let octal = parseInt(octalStr, 8);
if (octal > 255) {
octalStr = octalStr.slice(0, -1);
octal = parseInt(octalStr, 8);
}
pos += octalStr.length - 1;
const next = input.charCodeAt(pos);
if (octalStr !== "0" || next === 56 || next === 57) {
if (inTemplate) {
return res(null);
} else {
errors.strictNumericEscape(startPos, lineStart, curLine);
}
}
return res(String.fromCharCode(octal));
}
return res(String.fromCharCode(ch));
}
}
function readHexChar(input, pos, lineStart, curLine, len, forceLen, throwOnInvalid, errors) {
const initialPos = pos;
let n;
({
n,
pos
} = readInt(input, pos, lineStart, curLine, 16, len, forceLen, false, errors, !throwOnInvalid));
if (n === null) {
if (throwOnInvalid) {
errors.invalidEscapeSequence(initialPos, lineStart, curLine);
} else {
pos = initialPos - 1;
}
}
return {
code: n,
pos
};
}
function readInt(input, pos, lineStart, curLine, radix, len, forceLen, allowNumSeparator, errors, bailOnError) {
const start = pos;
const forbiddenSiblings = radix === 16 ? forbiddenNumericSeparatorSiblings.hex : forbiddenNumericSeparatorSiblings.decBinOct;
const isAllowedSibling = radix === 16 ? isAllowedNumericSeparatorSibling.hex : radix === 10 ? isAllowedNumericSeparatorSibling.dec : radix === 8 ? isAllowedNumericSeparatorSibling.oct : isAllowedNumericSeparatorSibling.bin;
let invalid = false;
let total = 0;
for (let i = 0, e = len == null ? Infinity : len; i < e; ++i) {
const code = input.charCodeAt(pos);
let val;
if (code === 95 && allowNumSeparator !== "bail") {
const prev = input.charCodeAt(pos - 1);
const next = input.charCodeAt(pos + 1);
if (!allowNumSeparator) {
if (bailOnError) return {
n: null,
pos
};
errors.numericSeparatorInEscapeSequence(pos, lineStart, curLine);
} else if (Number.isNaN(next) || !isAllowedSibling(next) || forbiddenSiblings.has(prev) || forbiddenSiblings.has(next)) {
if (bailOnError) return {
n: null,
pos
};
errors.unexpectedNumericSeparator(pos, lineStart, curLine);
}
++pos;
continue;
}
if (code >= 97) {
val = code - 97 + 10;
} else if (code >= 65) {
val = code - 65 + 10;
} else if (_isDigit(code)) {
val = code - 48;
} else {
val = Infinity;
}
if (val >= radix) {
if (val <= 9 && bailOnError) {
return {
n: null,
pos
};
} else if (val <= 9 && errors.invalidDigit(pos, lineStart, curLine, radix)) {
val = 0;
} else if (forceLen) {
val = 0;
invalid = true;
} else {
break;
}
}
++pos;
total = total * radix + val;
}
if (pos === start || len != null && pos - start !== len || invalid) {
return {
n: null,
pos
};
}
return {
n: total,
pos
};
}
function readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors) {
const ch = input.charCodeAt(pos);
let code;
if (ch === 123) {
++pos;
({
code,
pos
} = readHexChar(input, pos, lineStart, curLine, input.indexOf("}", pos) - pos, true, throwOnInvalid, errors));
++pos;
if (code !== null && code > 0x10ffff) {
if (throwOnInvalid) {
errors.invalidCodePoint(pos, lineStart, curLine);
} else {
return {
code: null,
pos
};
}
}
} else {
({
code,
pos
} = readHexChar(input, pos, lineStart, curLine, 4, false, throwOnInvalid, errors));
}
return {
code,
pos
};
}
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"names":["isDigit","code","forbiddenNumericSeparatorSiblings","decBinOct","Set","hex","isAllowedNumericSeparatorSibling","bin","ch","oct","dec","readStringContents","type","input","pos","lineStart","curLine","errors","initialPos","initialLineStart","initialCurLine","out","firstInvalidLoc","chunkStart","length","unterminated","slice","charCodeAt","isStringEnd","res","readEscapedChar","str","containsInvalid","inTemplate","throwOnInvalid","readHexChar","String","fromCharCode","readCodePoint","fromCodePoint","strictNumericEscape","startPos","match","exec","octalStr","octal","parseInt","next","len","forceLen","n","readInt","invalidEscapeSequence","radix","allowNumSeparator","bailOnError","start","forbiddenSiblings","isAllowedSibling","invalid","total","i","e","Infinity","val","prev","numericSeparatorInEscapeSequence","Number","isNaN","has","unexpectedNumericSeparator","_isDigit","invalidDigit","indexOf","invalidCodePoint"],"sources":["../src/index.ts"],"sourcesContent":["// We inline this package\n// eslint-disable-next-line import/no-extraneous-dependencies\nimport * as charCodes from \"charcodes\";\n\n// The following character codes are forbidden from being\n// an immediate sibling of NumericLiteralSeparator _\nconst forbiddenNumericSeparatorSiblings = {\n decBinOct: new Set<number>([\n charCodes.dot,\n charCodes.uppercaseB,\n charCodes.uppercaseE,\n charCodes.uppercaseO,\n charCodes.underscore, // multiple separators are not allowed\n charCodes.lowercaseB,\n charCodes.lowercaseE,\n charCodes.lowercaseO,\n ]),\n hex: new Set<number>([\n charCodes.dot,\n charCodes.uppercaseX,\n charCodes.underscore, // multiple separators are not allowed\n charCodes.lowercaseX,\n ]),\n};\n\nconst isAllowedNumericSeparatorSibling = {\n // 0 - 1\n bin: (ch: number) => ch === charCodes.digit0 || ch === charCodes.digit1,\n\n // 0 - 7\n oct: (ch: number) => ch >= charCodes.digit0 && ch <= charCodes.digit7,\n\n // 0 - 9\n dec: (ch: number) => ch >= charCodes.digit0 && ch <= charCodes.digit9,\n\n // 0 - 9, A - F, a - f,\n hex: (ch: number) =>\n (ch >= charCodes.digit0 && ch <= charCodes.digit9) ||\n (ch >= charCodes.uppercaseA && ch <= charCodes.uppercaseF) ||\n (ch >= charCodes.lowercaseA && ch <= charCodes.lowercaseF),\n};\n\nexport type StringContentsErrorHandlers = EscapedCharErrorHandlers & {\n unterminated(\n initialPos: number,\n initialLineStart: number,\n initialCurLine: number,\n ): void;\n};\n\nexport function readStringContents(\n type: \"single\" | \"double\" | \"template\",\n input: string,\n pos: number,\n lineStart: number,\n curLine: number,\n errors: StringContentsErrorHandlers,\n) {\n const initialPos = pos;\n const initialLineStart = lineStart;\n const initialCurLine = curLine;\n\n let out = \"\";\n let firstInvalidLoc = null;\n let chunkStart = pos;\n const { length } = input;\n for (;;) {\n if (pos >= length) {\n errors.unterminated(initialPos, initialLineStart, initialCurLine);\n out += input.slice(chunkStart, pos);\n break;\n }\n const ch = input.charCodeAt(pos);\n if (isStringEnd(type, ch, input, pos)) {\n out += input.slice(chunkStart, pos);\n break;\n }\n if (ch === charCodes.backslash) {\n out += input.slice(chunkStart, pos);\n const res = readEscapedChar(\n input,\n pos,\n lineStart,\n curLine,\n type === \"template\",\n errors,\n );\n if (res.ch === null && !firstInvalidLoc) {\n firstInvalidLoc = { pos, lineStart, curLine };\n } else {\n out += res.ch;\n }\n ({ pos, lineStart, curLine } = res);\n chunkStart = pos;\n } else if (\n ch === charCodes.lineSeparator ||\n ch === charCodes.paragraphSeparator\n ) {\n ++pos;\n ++curLine;\n lineStart = pos;\n } else if (ch === charCodes.lineFeed || ch === charCodes.carriageReturn) {\n if (type === \"template\") {\n out += input.slice(chunkStart, pos) + \"\\n\";\n ++pos;\n if (\n ch
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
# @babel/helper-validator-identifier
> Validate identifier/keywords name
See our website [@babel/helper-validator-identifier](https://babeljs.io/docs/babel-helper-validator-identifier) for more information.
## Install
Using npm:
```sh
npm install --save @babel/helper-validator-identifier
```
or using yarn:
```sh
yarn add @babel/helper-validator-identifier
```
@@ -0,0 +1,70 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isIdentifierChar = isIdentifierChar;
exports.isIdentifierName = isIdentifierName;
exports.isIdentifierStart = isIdentifierStart;
let nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0560-\u0588\u05d0-\u05ea\u05ef-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u0860-\u086a\u0870-\u0887\u0889-\u088f\u08a0-\u08c9\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u09fc\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0af9\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c5c\u0c5d\u0c60\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cdc-\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d04-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e86-\u0e8a\u0e8c-\u0ea3\u0ea5\u0ea7-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u1711\u171f-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1878\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4c\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1c80-\u1c8a\u1c90-\u1cba\u1cbd-\u1cbf\u1ce9-\u1cec\u1cee-\u1cf3\u1cf5\u1cf6\u1cfa\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31bf\u31f0-\u31ff\u3400-\u4dbf\u4e00-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua7dc\ua7f1-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd\ua8fe\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab69\uab70-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
let nonASCIIidentifierChars = "\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u07fd\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u0897-\u089f\u08ca-\u08e1\u08e3-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u09fe\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0afa-\u0aff\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b55-\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c04\u0c3c\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0cf3\u0d00-\u0d03\u0d3b\u0d3c\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d81-\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0ebc\u0ec8-\u0ece\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u180f-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1abf-\u1add\u1ae0-\u1aeb\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf4\u1cf7-\u1cf9\u1dc0-\u1dff\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\u30fb\ua620-\ua629\ua66f\ua674-\ua67d\ua69e\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua82c\ua880\ua881\ua8b4-\ua8c5\ua8d0-\ua8d9\ua8e0-\ua8f1\ua8ff-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f\uff65";
const nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
const nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
nonASCIIidentifierStartChars = nonASCIIidentifierChars = null;
const astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 14, 29, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 19, 35, 5, 35, 5, 39, 9, 51, 13, 10, 2, 14, 2, 6, 2, 1, 2, 10, 2, 14, 2, 6, 2, 1, 4, 51, 13, 310, 10, 21, 11, 7, 25, 5, 2, 41, 2, 8, 70, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 66, 18, 2, 1, 11, 21, 11, 25, 7, 25, 39, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 28, 43, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 56, 50, 14, 50, 14, 35, 39, 27, 10, 22, 251, 41, 7, 1, 17, 5, 57, 28, 11, 0, 9, 21, 43, 17, 47, 20, 28, 22, 13, 52, 58, 1, 3, 0, 14, 44, 33, 24, 27, 35, 30, 0, 3, 0, 9, 34, 4, 0, 13, 47, 15, 3, 22, 0, 2, 0, 36, 17, 2, 24, 20, 1, 64, 6, 2, 0, 2, 3, 2, 14, 2, 9, 8, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 19, 0, 13, 4, 31, 9, 2, 0, 3, 0, 2, 37, 2, 0, 26, 0, 2, 0, 45, 52, 19, 3, 21, 2, 31, 47, 21, 1, 2, 0, 185, 46, 42, 3, 37, 47, 21, 0, 60, 42, 14, 0, 72, 26, 38, 6, 186, 43, 117, 63, 32, 7, 3, 0, 3, 7, 2, 1, 2, 23, 16, 0, 2, 0, 95, 7, 3, 38, 17, 0, 2, 0, 29, 0, 11, 39, 8, 0, 22, 0, 12, 45, 20, 0, 19, 72, 200, 32, 32, 8, 2, 36, 18, 0, 50, 29, 113, 6, 2, 1, 2, 37, 22, 0, 26, 5, 2, 1, 2, 31, 15, 0, 24, 43, 261, 18, 16, 0, 2, 12, 2, 33, 125, 0, 80, 921, 103, 110, 18, 195, 2637, 96, 16, 1071, 18, 5, 26, 3994, 6, 582, 6842, 29, 1763, 568, 8, 30, 18, 78, 18, 29, 19, 47, 17, 3, 32, 20, 6, 18, 433, 44, 212, 63, 33, 24, 3, 24, 45, 74, 6, 0, 67, 12, 65, 1, 2, 0, 15, 4, 10, 7381, 42, 31, 98, 114, 8702, 3, 2, 6, 2, 1, 2, 290, 16, 0, 30, 2, 3, 0, 15, 3, 9, 395, 2309, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 1845, 30, 7, 5, 262, 61, 147, 44, 11, 6, 17, 0, 322, 29, 19, 43, 485, 27, 229, 29, 3, 0, 208, 30, 2, 2, 2, 1, 2, 6, 3, 4, 10, 1, 225, 6, 2, 3, 2, 1, 2, 14, 2, 196, 60, 67, 8, 0, 1205, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42719, 33, 4381, 3, 5773, 3, 7472, 16, 621, 2467, 541, 1507, 4938, 6, 8489];
const astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 574, 3, 9, 9, 7, 9, 32, 4, 318, 1, 78, 5, 71, 10, 50, 3, 123, 2, 54, 14, 32, 10, 3, 1, 11, 3, 46, 10, 8, 0, 46, 9, 7, 2, 37, 13, 2, 9, 6, 1, 45, 0, 13, 2, 49, 13, 9, 3, 2, 11, 83, 11, 7, 0, 3, 0, 158, 11, 6, 9, 7, 3, 56, 1, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 68, 8, 2, 0, 3, 0, 2, 3, 2, 4, 2, 0, 15, 1, 83, 17, 10, 9, 5, 0, 82, 19, 13, 9, 214, 6, 3, 8, 28, 1, 83, 16, 16, 9, 82, 12, 9, 9, 7, 19, 58, 14, 5, 9, 243, 14, 166, 9, 71, 5, 2, 1, 3, 3, 2, 0, 2, 1, 13, 9, 120, 6, 3, 6, 4, 0, 29, 9, 41, 6, 2, 3, 9, 0, 10, 10, 47, 15, 199, 7, 137, 9, 54, 7, 2, 7, 17, 9, 57, 21, 2, 13, 123, 5, 4, 0, 2, 1, 2, 6, 2, 0, 9, 9, 49, 4, 2, 1, 2, 4, 9, 9, 55, 9, 266, 3, 10, 1, 2, 0, 49, 6, 4, 4, 14, 10, 5350, 0, 7, 14, 11465, 27, 2343, 9, 87, 9, 39, 4, 60, 6, 26, 9, 535, 9, 470, 0, 2, 54, 8, 3, 82, 0, 12, 1, 19628, 1, 4178, 9, 519, 45, 3, 22, 543, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 513, 54, 5, 49, 9, 0, 15, 0, 23, 4, 2, 14, 1361, 6, 2, 16, 3, 6, 2, 1, 2, 4, 101, 0, 161, 6, 10, 9, 357, 0, 62, 13, 499, 13, 245, 1, 2, 9, 233, 0, 3, 0, 8, 1, 6, 0, 475, 6, 110, 6, 6, 9, 4759, 9, 787719, 239];
function isInAstralSet(code, set) {
let pos = 0x10000;
for (let i = 0, length = set.length; i < length; i += 2) {
pos += set[i];
if (pos > code) return false;
pos += set[i + 1];
if (pos >= code) return true;
}
return false;
}
function isIdentifierStart(code) {
if (code < 65) return code === 36;
if (code <= 90) return true;
if (code < 97) return code === 95;
if (code <= 122) return true;
if (code <= 0xffff) {
return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
}
return isInAstralSet(code, astralIdentifierStartCodes);
}
function isIdentifierChar(code) {
if (code < 48) return code === 36;
if (code < 58) return true;
if (code < 65) return false;
if (code <= 90) return true;
if (code < 97) return code === 95;
if (code <= 122) return true;
if (code <= 0xffff) {
return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
}
return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes);
}
function isIdentifierName(name) {
let isFirst = true;
for (let i = 0; i < name.length; i++) {
let cp = name.charCodeAt(i);
if ((cp & 0xfc00) === 0xd800 && i + 1 < name.length) {
const trail = name.charCodeAt(++i);
if ((trail & 0xfc00) === 0xdc00) {
cp = 0x10000 + ((cp & 0x3ff) << 10) + (trail & 0x3ff);
}
}
if (isFirst) {
isFirst = false;
if (!isIdentifierStart(cp)) {
return false;
}
} else if (!isIdentifierChar(cp)) {
return false;
}
}
return !isFirst;
}
//# sourceMappingURL=identifier.js.map
File diff suppressed because one or more lines are too long
+22
View File
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+19
View File
@@ -0,0 +1,19 @@
# @babel/helper-validator-option
> Validate plugin/preset options
See our website [@babel/helper-validator-option](https://babeljs.io/docs/babel-helper-validator-option) for more information.
## Install
Using npm:
```sh
npm install --save @babel/helper-validator-option
```
or using yarn:
```sh
yarn add @babel/helper-validator-option
```
@@ -0,0 +1,39 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.findSuggestion = findSuggestion;
const {
min
} = Math;
function levenshtein(a, b) {
let t = [],
u = [],
i,
j;
const m = a.length,
n = b.length;
if (!m) {
return n;
}
if (!n) {
return m;
}
for (j = 0; j <= n; j++) {
t[j] = j;
}
for (i = 1; i <= m; i++) {
for (u = [i], j = 1; j <= n; j++) {
u[j] = a[i - 1] === b[j - 1] ? t[j - 1] : min(t[j - 1], t[j], u[j - 1]) + 1;
}
t = u;
}
return u[n];
}
function findSuggestion(str, arr) {
const distances = arr.map(el => levenshtein(el, str));
return arr[distances.indexOf(min(...distances))];
}
//# sourceMappingURL=find-suggestion.js.map
@@ -0,0 +1 @@
{"version":3,"names":["min","Math","levenshtein","a","b","t","u","i","j","m","length","n","findSuggestion","str","arr","distances","map","el","indexOf"],"sources":["../src/find-suggestion.ts"],"sourcesContent":["const { min } = Math;\n\n// a minimal leven distance implementation\n// balanced maintainability with code size\n// It is not blazingly fast but should be okay for Babel user case\n// where it will be run for at most tens of time on strings\n// that have less than 20 ASCII characters\n\n// https://rosettacode.org/wiki/Levenshtein_distance#ES5\nfunction levenshtein(a: string, b: string): number {\n let t = [],\n u: number[] = [],\n i,\n j;\n const m = a.length,\n n = b.length;\n if (!m) {\n return n;\n }\n if (!n) {\n return m;\n }\n for (j = 0; j <= n; j++) {\n t[j] = j;\n }\n for (i = 1; i <= m; i++) {\n for (u = [i], j = 1; j <= n; j++) {\n u[j] =\n a[i - 1] === b[j - 1] ? t[j - 1] : min(t[j - 1], t[j], u[j - 1]) + 1;\n }\n t = u;\n }\n return u[n];\n}\n\n/**\n * Given a string `str` and an array of candidates `arr`,\n * return the first of elements in candidates that has minimal\n * Levenshtein distance with `str`.\n * @export\n * @param {string} str\n * @param {string[]} arr\n * @returns {string}\n */\nexport function findSuggestion(str: string, arr: readonly string[]): string {\n const distances = arr.map<number>(el => levenshtein(el, str));\n return arr[distances.indexOf(min(...distances))];\n}\n"],"mappings":";;;;;;AAAA,MAAM;EAAEA;AAAI,CAAC,GAAGC,IAAI;AASpB,SAASC,WAAWA,CAACC,CAAS,EAAEC,CAAS,EAAU;EACjD,IAAIC,CAAC,GAAG,EAAE;IACRC,CAAW,GAAG,EAAE;IAChBC,CAAC;IACDC,CAAC;EACH,MAAMC,CAAC,GAAGN,CAAC,CAACO,MAAM;IAChBC,CAAC,GAAGP,CAAC,CAACM,MAAM;EACd,IAAI,CAACD,CAAC,EAAE;IACN,OAAOE,CAAC;EACV;EACA,IAAI,CAACA,CAAC,EAAE;IACN,OAAOF,CAAC;EACV;EACA,KAAKD,CAAC,GAAG,CAAC,EAAEA,CAAC,IAAIG,CAAC,EAAEH,CAAC,EAAE,EAAE;IACvBH,CAAC,CAACG,CAAC,CAAC,GAAGA,CAAC;EACV;EACA,KAAKD,CAAC,GAAG,CAAC,EAAEA,CAAC,IAAIE,CAAC,EAAEF,CAAC,EAAE,EAAE;IACvB,KAAKD,CAAC,GAAG,CAACC,CAAC,CAAC,EAAEC,CAAC,GAAG,CAAC,EAAEA,CAAC,IAAIG,CAAC,EAAEH,CAAC,EAAE,EAAE;MAChCF,CAAC,CAACE,CAAC,CAAC,GACFL,CAAC,CAACI,CAAC,GAAG,CAAC,CAAC,KAAKH,CAAC,CAACI,CAAC,GAAG,CAAC,CAAC,GAAGH,CAAC,CAACG,CAAC,GAAG,CAAC,CAAC,GAAGR,GAAG,CAACK,CAAC,CAACG,CAAC,GAAG,CAAC,CAAC,EAAEH,CAAC,CAACG,CAAC,CAAC,EAAEF,CAAC,CAACE,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;IACxE;IACAH,CAAC,GAAGC,CAAC;EACP;EACA,OAAOA,CAAC,CAACK,CAAC,CAAC;AACb;AAWO,SAASC,cAAcA,CAACC,GAAW,EAAEC,GAAsB,EAAU;EAC1E,MAAMC,SAAS,GAAGD,GAAG,CAACE,GAAG,CAASC,EAAE,IAAIf,WAAW,CAACe,EAAE,EAAEJ,GAAG,CAAC,CAAC;EAC7D,OAAOC,GAAG,CAACC,SAAS,CAACG,OAAO,CAAClB,GAAG,CAAC,GAAGe,SAAS,CAAC,CAAC,CAAC;AAClD","ignoreList":[]}
BIN
View File
Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Toru Nagashima
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Toru Nagashima
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
File diff suppressed because it is too large Load Diff
View File
+19
View File
@@ -0,0 +1,19 @@
Copyright OpenJS Foundation and other contributors, <www.openjsf.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+211
View File
@@ -0,0 +1,211 @@
/*
* WARNING: This file is autogenerated using the tools/update-eslint-all.js
* script. Do not edit manually.
*/
"use strict";
/* eslint quote-props: off -- autogenerated so don't lint */
module.exports = Object.freeze({
"rules": {
"accessor-pairs": "error",
"array-callback-return": "error",
"arrow-body-style": "error",
"block-scoped-var": "error",
"camelcase": "error",
"capitalized-comments": "error",
"class-methods-use-this": "error",
"complexity": "error",
"consistent-return": "error",
"consistent-this": "error",
"constructor-super": "error",
"curly": "error",
"default-case": "error",
"default-case-last": "error",
"default-param-last": "error",
"dot-notation": "error",
"eqeqeq": "error",
"for-direction": "error",
"func-name-matching": "error",
"func-names": "error",
"func-style": "error",
"getter-return": "error",
"grouped-accessor-pairs": "error",
"guard-for-in": "error",
"id-denylist": "error",
"id-length": "error",
"id-match": "error",
"init-declarations": "error",
"line-comment-position": "error",
"logical-assignment-operators": "error",
"max-classes-per-file": "error",
"max-depth": "error",
"max-lines": "error",
"max-lines-per-function": "error",
"max-nested-callbacks": "error",
"max-params": "error",
"max-statements": "error",
"multiline-comment-style": "error",
"new-cap": "error",
"no-alert": "error",
"no-array-constructor": "error",
"no-async-promise-executor": "error",
"no-await-in-loop": "error",
"no-bitwise": "error",
"no-caller": "error",
"no-case-declarations": "error",
"no-class-assign": "error",
"no-compare-neg-zero": "error",
"no-cond-assign": "error",
"no-console": "error",
"no-const-assign": "error",
"no-constant-binary-expression": "error",
"no-constant-condition": "error",
"no-constructor-return": "error",
"no-continue": "error",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-div-regex": "error",
"no-dupe-args": "error",
"no-dupe-class-members": "error",
"no-dupe-else-if": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-duplicate-imports": "error",
"no-else-return": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-function": "error",
"no-empty-pattern": "error",
"no-empty-static-block": "error",
"no-eq-null": "error",
"no-eval": "error",
"no-ex-assign": "error",
"no-extend-native": "error",
"no-extra-bind": "error",
"no-extra-boolean-cast": "error",
"no-extra-label": "error",
"no-fallthrough": "error",
"no-func-assign": "error",
"no-global-assign": "error",
"no-implicit-coercion": "error",
"no-implicit-globals": "error",
"no-implied-eval": "error",
"no-import-assign": "error",
"no-inline-comments": "error",
"no-inner-declarations": "error",
"no-invalid-regexp": "error",
"no-invalid-this": "error",
"no-irregular-whitespace": "error",
"no-iterator": "error",
"no-label-var": "error",
"no-labels": "error",
"no-lone-blocks": "error",
"no-lonely-if": "error",
"no-loop-func": "error",
"no-loss-of-precision": "error",
"no-magic-numbers": "error",
"no-misleading-character-class": "error",
"no-multi-assign": "error",
"no-multi-str": "error",
"no-negated-condition": "error",
"no-nested-ternary": "error",
"no-new": "error",
"no-new-func": "error",
"no-new-native-nonconstructor": "error",
"no-new-symbol": "error",
"no-new-wrappers": "error",
"no-nonoctal-decimal-escape": "error",
"no-obj-calls": "error",
"no-object-constructor": "error",
"no-octal": "error",
"no-octal-escape": "error",
"no-param-reassign": "error",
"no-plusplus": "error",
"no-promise-executor-return": "error",
"no-proto": "error",
"no-prototype-builtins": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-restricted-exports": "error",
"no-restricted-globals": "error",
"no-restricted-imports": "error",
"no-restricted-properties": "error",
"no-restricted-syntax": "error",
"no-return-assign": "error",
"no-script-url": "error",
"no-self-assign": "error",
"no-self-compare": "error",
"no-sequences": "error",
"no-setter-return": "error",
"no-shadow": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-template-curly-in-string": "error",
"no-ternary": "error",
"no-this-before-super": "error",
"no-throw-literal": "error",
"no-undef": "error",
"no-undef-init": "error",
"no-undefined": "error",
"no-underscore-dangle": "error",
"no-unexpected-multiline": "error",
"no-unmodified-loop-condition": "error",
"no-unneeded-ternary": "error",
"no-unreachable": "error",
"no-unreachable-loop": "error",
"no-unsafe-finally": "error",
"no-unsafe-negation": "error",
"no-unsafe-optional-chaining": "error",
"no-unused-expressions": "error",
"no-unused-labels": "error",
"no-unused-private-class-members": "error",
"no-unused-vars": "error",
"no-use-before-define": "error",
"no-useless-backreference": "error",
"no-useless-call": "error",
"no-useless-catch": "error",
"no-useless-computed-key": "error",
"no-useless-concat": "error",
"no-useless-constructor": "error",
"no-useless-escape": "error",
"no-useless-rename": "error",
"no-useless-return": "error",
"no-var": "error",
"no-void": "error",
"no-warning-comments": "error",
"no-with": "error",
"object-shorthand": "error",
"one-var": "error",
"operator-assignment": "error",
"prefer-arrow-callback": "error",
"prefer-const": "error",
"prefer-destructuring": "error",
"prefer-exponentiation-operator": "error",
"prefer-named-capture-group": "error",
"prefer-numeric-literals": "error",
"prefer-object-has-own": "error",
"prefer-object-spread": "error",
"prefer-promise-reject-errors": "error",
"prefer-regex-literals": "error",
"prefer-rest-params": "error",
"prefer-spread": "error",
"prefer-template": "error",
"radix": "error",
"require-atomic-updates": "error",
"require-await": "error",
"require-unicode-regexp": "error",
"require-yield": "error",
"sort-imports": "error",
"sort-keys": "error",
"sort-vars": "error",
"strict": "error",
"symbol-description": "error",
"unicode-bom": "error",
"use-isnan": "error",
"valid-typeof": "error",
"vars-on-top": "error",
"yoda": "error"
}
});
+76
View File
@@ -0,0 +1,76 @@
/**
* @fileoverview Configuration applied when a user configuration extends from
* eslint:recommended.
* @author Nicholas C. Zakas
*/
"use strict";
/* eslint sort-keys: ["error", "asc"] -- Long, so make more readable */
/** @type {import("../lib/shared/types").ConfigData} */
module.exports = Object.freeze({
rules: Object.freeze({
"constructor-super": "error",
"for-direction": "error",
"getter-return": "error",
"no-async-promise-executor": "error",
"no-case-declarations": "error",
"no-class-assign": "error",
"no-compare-neg-zero": "error",
"no-cond-assign": "error",
"no-const-assign": "error",
"no-constant-condition": "error",
"no-control-regex": "error",
"no-debugger": "error",
"no-delete-var": "error",
"no-dupe-args": "error",
"no-dupe-class-members": "error",
"no-dupe-else-if": "error",
"no-dupe-keys": "error",
"no-duplicate-case": "error",
"no-empty": "error",
"no-empty-character-class": "error",
"no-empty-pattern": "error",
"no-ex-assign": "error",
"no-extra-boolean-cast": "error",
"no-extra-semi": "error",
"no-fallthrough": "error",
"no-func-assign": "error",
"no-global-assign": "error",
"no-import-assign": "error",
"no-inner-declarations": "error",
"no-invalid-regexp": "error",
"no-irregular-whitespace": "error",
"no-loss-of-precision": "error",
"no-misleading-character-class": "error",
"no-mixed-spaces-and-tabs": "error",
"no-new-symbol": "error",
"no-nonoctal-decimal-escape": "error",
"no-obj-calls": "error",
"no-octal": "error",
"no-prototype-builtins": "error",
"no-redeclare": "error",
"no-regex-spaces": "error",
"no-self-assign": "error",
"no-setter-return": "error",
"no-shadow-restricted-names": "error",
"no-sparse-arrays": "error",
"no-this-before-super": "error",
"no-undef": "error",
"no-unexpected-multiline": "error",
"no-unreachable": "error",
"no-unsafe-finally": "error",
"no-unsafe-negation": "error",
"no-unsafe-optional-chaining": "error",
"no-unused-labels": "error",
"no-unused-vars": "error",
"no-useless-backreference": "error",
"no-useless-catch": "error",
"no-useless-escape": "error",
"no-with": "error",
"require-yield": "error",
"use-isnan": "error",
"valid-typeof": "error"
})
});
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
View File
File diff suppressed because it is too large Load Diff
+63
View File
@@ -0,0 +1,63 @@
{
"name": "@humanwhocodes/config-array",
"version": "0.13.0",
"description": "Glob-based configuration matching.",
"author": "Nicholas C. Zakas",
"main": "api.js",
"files": [
"api.js",
"LICENSE",
"README.md"
],
"repository": {
"type": "git",
"url": "git+https://github.com/humanwhocodes/config-array.git"
},
"bugs": {
"url": "https://github.com/humanwhocodes/config-array/issues"
},
"homepage": "https://github.com/humanwhocodes/config-array#readme",
"scripts": {
"build": "rollup -c",
"format": "nitpik",
"lint": "eslint *.config.js src/*.js tests/*.js",
"lint:fix": "eslint --fix *.config.js src/*.js tests/*.js",
"prepublish": "npm run build",
"test:coverage": "nyc --include src/*.js npm run test",
"test": "mocha -r esm tests/ --recursive"
},
"gitHooks": {
"pre-commit": "lint-staged"
},
"lint-staged": {
"*.js": [
"eslint --fix --ignore-pattern '!.eslintrc.js'"
]
},
"keywords": [
"configuration",
"configarray",
"config file"
],
"license": "Apache-2.0",
"engines": {
"node": ">=10.10.0"
},
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.3",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
"devDependencies": {
"@nitpik/javascript": "0.4.0",
"@nitpik/node": "0.0.5",
"chai": "4.3.10",
"eslint": "8.52.0",
"esm": "3.2.25",
"lint-staged": "15.0.2",
"mocha": "6.2.3",
"nyc": "15.1.0",
"rollup": "3.28.1",
"yorkie": "2.0.0"
}
}
+201
View File
@@ -0,0 +1,201 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
@@ -0,0 +1,22 @@
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var module$1 = require('module');
var url = require('url');
var path = require('path');
/**
* @fileoverview Universal module importer
*/
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const __filename$1 = url.fileURLToPath((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('module-importer.cjs', document.baseURI).href)));
const __dirname$1 = path.dirname(__filename$1);
const require$1 = module$1.createRequire(__dirname$1 + "/");
const { ModuleImporter } = require$1("./module-importer.cjs");
exports.ModuleImporter = ModuleImporter;
@@ -0,0 +1,63 @@
/**
* @fileoverview Universal module importer
*/
//-----------------------------------------------------------------------------
// Imports
//-----------------------------------------------------------------------------
const { createRequire } = require("module");
const { pathToFileURL } = require("url");
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const SLASHES = new Set(["/", "\\"]);
/**
* Normalizes directories to have a trailing slash.
* Resolve is pretty finicky -- if the directory name doesn't have
* a trailing slash then it tries to look in the parent directory.
* i.e., if the directory is "/usr/nzakas/foo" it will start the
* search in /usr/nzakas. However, if the directory is "/user/nzakas/foo/",
* then it will start the search in /user/nzakas/foo.
* @param {string} directory The directory to check.
* @returns {string} The normalized directory.
*/
function normalizeDirectory(directory) {
if (!SLASHES.has(directory[directory.length-1])) {
return directory + "/";
}
return directory;
}
//-----------------------------------------------------------------------------
// Exports
//-----------------------------------------------------------------------------
/**
* Class for importing both CommonJS and ESM modules in Node.js.
*/
exports.ModuleImporter = class ModuleImporter {
/**
* Creates a new instance.
* @param {string} [cwd] The current working directory to resolve from.
*/
constructor(cwd = process.cwd()) {
/**
* The base directory from which paths should be resolved.
* @type {string}
*/
this.cwd = normalizeDirectory(cwd);
}
/**
* Resolves a module based on its name or location.
* @param {string} specifier Either an npm package name or
* relative file path.
* @returns {string|undefined} The location of the import.
* @throws {Error} If
+29
View File
@@ -0,0 +1,29 @@
BSD 3-Clause License
Copyright (c) 2019, Human Who Codes
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+7
View File
@@ -0,0 +1,7 @@
/**
* @filedescription Object Schema Package
*/
exports.ObjectSchema = require("./object-schema").ObjectSchema;
exports.MergeStrategy = require("./merge-strategy").MergeStrategy;
exports.ValidationStrategy = require("./validation-strategy").ValidationStrategy;
@@ -0,0 +1,53 @@
/**
* @filedescription Merge Strategy
*/
"use strict";
//-----------------------------------------------------------------------------
// Class
//-----------------------------------------------------------------------------
/**
* Container class for several different merge strategies.
*/
class MergeStrategy {
/**
* Merges two keys by overwriting the first with the second.
* @param {*} value1 The value from the first object key.
* @param {*} value2 The value from the second object key.
* @returns {*} The second value.
*/
static overwrite(value1, value2) {
return value2;
}
/**
* Merges two keys by replacing the first with the second only if the
* second is defined.
* @param {*} value1 The value from the first object key.
* @param {*} value2 The value from the second object key.
* @returns {*} The second value if it is defined.
*/
static replace(value1, value2) {
if (typeof value2 !== "undefined") {
return value2;
}
return value1;
}
/**
* Merges two properties by assigning properties from the second to the first.
* @param {*} value1 The value from the first object key.
* @param {*} value2 The value from the second object key.
* @returns {*} A new object containing properties from both value1 and
* value2.
*/
static assign(value1, value2) {
return Object.assign({}, value1, value2);
}
}
exports.MergeStrategy = MergeStrategy;
+301
View File
@@ -0,0 +1,301 @@
/**
* @filedescription Object Schema
*/
"use strict";
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
const { MergeStrategy } = require("./merge-strategy");
const { ValidationStrategy } = require("./validation-strategy");
//-----------------------------------------------------------------------------
// Private
//-----------------------------------------------------------------------------
const strategies = Symbol("strategies");
const requiredKeys = Symbol("requiredKeys");
/**
* Validates a schema strategy.
* @param {string} name The name of the key this strategy is for.
* @param {Object} strategy The strategy for the object key.
* @param {boolean} [strategy.required=true] Whether the key is required.
* @param {string[]} [strategy.requires] Other keys that are required when
* this key is present.
* @param {Function} strategy.merge A method to call when merging two objects
* with the same key.
* @param {Function} strategy.validate A method to call when validating an
* object with the key.
* @returns {void}
* @throws {Error} When the strategy is missing a name.
* @throws {Error} When the strategy is missing a merge() method.
* @throws {Error} When the strategy is missing a validate() method.
*/
function validateDefinition(name, strategy) {
let hasSchema = false;
if (strategy.schema) {
if (typeof strategy.schema === "object") {
hasSchema = true;
} else {
throw new TypeError("Schema must be an object.");
}
}
if (typeof strategy.merge === "string") {
if (!(strategy.merge in MergeStrategy)) {
throw new TypeError(`Definition for key "${name}" missing valid merge strategy.`);
}
} else if (!hasSchema && typeof strategy.merge !== "function") {
throw new TypeError(`Definition for key "${name}" must have a merge property.`);
}
if (typeof strategy.validate === "string") {
if (!(strategy.validate in ValidationStrategy)) {
throw new TypeError(`Definition for key "${name}" missing valid validation strategy.`);
}
} else if (!hasSchema && typeof strategy.validate !== "function") {
throw new TypeError(`Definition for key "${name}" must have a validate() method.`);
}
}
//-----------------------------------------------------------------------------
// Errors
//-----------------------------------------------------------------------------
/**
* Error when an unexpected key is found.
*/
class UnexpectedKeyError extends Error {
/**
* Creates a new instance.
* @param {string} key The key that was unexpected.
*/
constructor(key) {
super(`Unexpected key "${key}" found.`);
}
}
/**
* Error when a required key is missing.
*/
class MissingKeyError extends Error {
/**
* Creates a new instance.
* @param {string} key The key that was missing.
*/
constructor(key) {
super(`Missing required key "${key}".`);
}
}
/**
* Error when a key requires other keys that are missing.
*/
class MissingDependentKeysError extends Error {
/**
* Creates a new instance.
* @param {string} key The key that was unexpected.
* @param {Array<string>} requiredKeys The keys that are required.
*/
constructor(key, requiredKeys) {
super(`Key "${key}" requires keys "${requiredKeys.join("\", \"")}".`);
}
}
/**
* Wrapper error for errors occuring during a merge or validate operation.
*/
class WrapperError extends Error {
/**
* Creates a new instance.
* @param {string} key The object key causing the error.
* @param {Error} source The source error.
*/
constructor(key, source) {
super(`Key "${key}": ${source.message}`, { cause: source });
// copy over custom properties that aren't represented
for (const key of Object.keys(source)) {
if (!(key in this)) {
this[key] = source[key];
}
}
}
}
//-----------------------------------------------------------------------------
// Main
//-----------------------------------------------------------------------------
/**
* Represents an object validation/merging schema.
*/
class ObjectSchema {
/**
* Creates a new instance.
*/
constructor(definitions) {
if (!definitions) {
throw new Error("Schema definitions missing.");
}
/**
* Track all strategies in the schema by key.
* @type {Map}
* @property strategies
*/
this[strategies] = new Map();
/**
* Separately track any keys that are required for faster validation.
* @type {Map}
* @property requiredKeys
*/
this[requiredKeys] = new Map();
// add in all strategies
for (const key of Object.keys(definitions)) {
validateDefinition(key, definitions[key]);
// normalize merge and validate methods if subschema is present
if (typeof definitions[key].schema === "object") {
const schema = new ObjectSchema(definitions[key].schema);
definitions[key] = {
...definitions[key],
merge(first = {}, second = {}) {
return schema.merge(first, second);
},
validate(value) {
ValidationStrategy.object(value);
schema.validate(value);
}
};
}
// normalize the merge method in case there's a string
if (typeof definitions[key].merge === "string") {
definitions[key] = {
...definitions[key],
merge: MergeStrategy[definitions[key].merge]
};
};
// normalize the validate method in case there's a string
if (typeof definitions[key].validate === "string") {
definitions[key] = {
...definitions[key],
validate: ValidationStrategy[definitions[key].validate]
};
};
this[strategies].set(key, definitions[key]);
if (definitions[key].required) {
this[requiredKeys].set(key, definitions[key]);
}
}
}
/**
* Determines if a strategy has been registered for the given object key.
* @param {string} key The object key to find a strategy for.
* @returns {boolean} True if the key has a strategy registered, false if not.
*/
hasKey(key) {
return this[strategies].has(key);
}
/**
* Merges objects together to create a new object comprised of the keys
* of the all objects. Keys are merged based on the each key's merge
* strategy.
* @param {...Object} objects The objects to merge.
* @returns {Object} A new object with a mix of all objects' keys.
* @throws {Error} If any object is invalid.
*/
merge(...objects) {
// double check arguments
if (objects.length < 2) {
throw new TypeError("merge() requires at least two arguments.");
}
if (objects.some(object => (object == null || typeof object !== "object"))) {
throw new TypeError("All arguments must be objects.");
}
return objects.reduce((result, object) => {
this.validate(object);
for (const [key, strategy] of this[strategies]) {
try {
if (key in result || key in object) {
const value = strategy.merge.call(this, result[key], object[key]);
if (value !== undefined) {
result[key] = value;
}
}
} catch (ex) {
throw new WrapperError(key, ex);
}
}
return result;
}, {});
}
/**
* Validates an object's keys based on the validate strategy for each key.
* @param {Object} object The object to validate.
* @returns {void}
* @throws {Error} When the object is invalid.
*/
validate(object) {
// check existing keys first
for (const key of Object.keys(object)) {
// check to see if the key is defined
if (!this.hasKey(key)) {
throw new UnexpectedKeyError(key);
}
// validate existing keys
const strategy = this[strategies].get(key);
// first check to see if any other keys are required
if (Array.isArray(strategy.requires)) {
if (!strategy.requires.every(otherKey => otherKey in object)) {
throw new MissingDependentKeysError(key, strategy.requires);
}
}
// now apply remaining validation strategy
try {
strategy.validate.call(strategy, object[key]);
} catch (ex) {
throw new WrapperError(key, ex);
}
}
// ensure required keys aren't missing
for (const [key] of this[requiredKeys]) {
if (!(key in object)) {
throw new MissingKeyError(key);
}
}
}
}
exports.ObjectSchema = ObjectSchema;
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Meta Platforms, Inc. and affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
View File
+60
View File
@@ -0,0 +1,60 @@
'use strict';
Object.defineProperty(exports, '__esModule', {
value: true
});
exports.SnapshotFormat = void 0;
function _typebox() {
const data = require('@sinclair/typebox');
_typebox = function () {
return data;
};
return data;
}
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
const RawSnapshotFormat = _typebox().Type.Partial(
_typebox().Type.Object({
callToJSON: _typebox().Type.Readonly(_typebox().Type.Boolean()),
compareKeys: _typebox().Type.Readonly(_typebox().Type.Null()),
escapeRegex: _typebox().Type.Readonly(_typebox().Type.Boolean()),
escapeString: _typebox().Type.Readonly(_typebox().Type.Boolean()),
highlight: _typebox().Type.Readonly(_typebox().Type.Boolean()),
indent: _typebox().Type.Readonly(
_typebox().Type.Number({
minimum: 0
})
),
maxDepth: _typebox().Type.Readonly(
_typebox().Type.Number({
minimum: 0
})
),
maxWidth: _typebox().Type.Readonly(
_typebox().Type.Number({
minimum: 0
})
),
min: _typebox().Type.Readonly(_typebox().Type.Boolean()),
printBasicPrototype: _typebox().Type.Readonly(_typebox().Type.Boolean()),
printFunctionName: _typebox().Type.Readonly(_typebox().Type.Boolean()),
theme: _typebox().Type.Readonly(
_typebox().Type.Partial(
_typebox().Type.Object({
comment: _typebox().Type.Readonly(_typebox().Type.String()),
content: _typebox().Type.Readonly(_typebox().Type.String()),
prop: _typebox().Type.Readonly(_typebox().Type.String()),
tag: _typebox().Type.Readonly(_typebox().Type.String()),
value: _typebox().Type.Readonly(_typebox().Type.String())
})
)
)
})
);
const SnapshotFormat = _typebox().Type.Strict(RawSnapshotFormat);
exports.SnapshotFormat = SnapshotFormat;
+29
View File
@@ -0,0 +1,29 @@
{
"name": "@jest/schemas",
"version": "29.6.3",
"repository": {
"type": "git",
"url": "https://github.com/jestjs/jest.git",
"directory": "packages/jest-schemas"
},
"license": "MIT",
"main": "./build/index.js",
"types": "./build/index.d.ts",
"exports": {
".": {
"types": "./build/index.d.ts",
"default": "./build/index.js"
},
"./package.json": "./package.json"
},
"dependencies": {
"@sinclair/typebox": "^0.27.8"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
},
"publishConfig": {
"access": "public"
},
"gitHead": "fb7d95c8af6e0d65a8b65348433d8a0ea0725b5b"
}
+19
View File
@@ -0,0 +1,19 @@
Copyright 2024 Justin Ridgewell <justin@ridgewell.name>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+89
View File
@@ -0,0 +1,89 @@
import type { SourceMapInput } from '@jridgewell/trace-mapping';
import type { DecodedSourceMap, EncodedSourceMap, Pos, Mapping } from './types.cts';
export type { DecodedSourceMap, EncodedSourceMap, Mapping };
export type Options = {
file?: string | null;
sourceRoot?: string | null;
};
/**
* Provides the state to generate a sourcemap.
*/
export declare class GenMapping {
private _names;
private _sources;
private _sourcesContent;
private _mappings;
private _ignoreList;
file: string | null | undefined;
sourceRoot: string | null | undefined;
constructor({ file, sourceRoot }?: Options);
}
/**
* A low-level API to associate a generated position with an original source position. Line and
* column here are 0-based, unlike `addMapping`.
*/
export declare function addSegment(map: GenMapping, genLine: number, genColumn: number, source?: null, sourceLine?: null, sourceColumn?: null, name?: null, content?: null): void;
export declare function addSegment(map: GenMapping, genLine: number, genColumn: number, source: string, sourceLine: number, sourceColumn: number, name?: null, content?: string | null): void;
export declare function addSegment(map: GenMapping, genLine: number, genColumn: number, source: string, sourceLine: number, sourceColumn: number, name: string, content?: string | null): void;
/**
* A high-level API to associate a generated position with an original source position. Line is
* 1-based, but column is 0-based, due to legacy behavior in `source-map` library.
*/
export declare function addMapping(map: GenMapping, mapping: {
generated: Pos;
source?: null;
original?: null;
name?: null;
content?: null;
}): void;
export declare function addMapping(map: GenMapping, mapping: {
generated: Pos;
source: string;
original: Pos;
name?: null;
content?: string | null;
}): void;
export declare function addMapping(map: GenMapping, mapping: {
generated: Pos;
source: string;
original: Pos;
name: string;
content?: string | null;
}): void;
/**
* Same as `addSegment`, but will only add the segment if it generates useful information in the
* resulting map. This only works correctly if segments are added **in order**, meaning you should
* not add a segment with a lower generated line/column than one that came before.
*/
export declare const maybeAddSegment: typeof addSegment;
/**
* Same as `addMapping`, but will only add the mapping if it generates useful information in the
* resulting map. This only works correctly if mappings are added **in order**, meaning you should
* not add a mapping with a lower generated line/column than one that came before.
*/
export declare const maybeAddMapping: typeof addMapping;
/**
* Adds/removes the content of the source file to the source map.
*/
export declare function setSourceContent(map: GenMapping, source: string, content: string | null): void;
export declare function setIgnore(map: GenMapping, source: string, ignore?: boolean): void;
/**
* Returns a sourcemap object (with decoded mappings) suitable for passing to a library that expects
* a sourcemap, or to JSON.stringify.
*/
export declare function toDecodedMap(map: GenMapping): DecodedSourceMap;
/**
* Returns a sourcemap object (with encoded mappings) suitable for passing to a library that expects
* a sourcemap, or to JSON.stringify.
*/
export declare function toEncodedMap(map: GenMapping): EncodedSourceMap;
/**
* Constructs a new GenMapping, using the already present mappings of the input.
*/
export declare function fromMap(input: SourceMapInput): GenMapping;
/**
* Returns an array of high-level mapping objects for every recorded segment, which could then be
* passed to the `source-map` library.
*/
export declare function allMappings(map: GenMapping): Mapping[];
//# sourceMappingURL=gen-mapping.d.ts.map
+33
View File
@@ -0,0 +1,33 @@
type Key = string | number | symbol;
/**
* SetArray acts like a `Set` (allowing only one occurrence of a string `key`), but provides the
* index of the `key` in the backing array.
*
* This is designed to allow synchronizing a second array with the contents of the backing array,
* like how in a sourcemap `sourcesContent[i]` is the source content associated with `source[i]`,
* and there are never duplicates.
*/
export declare class SetArray<T extends Key = Key> {
private _indexes;
array: readonly T[];
constructor();
}
/**
* Gets the index associated with `key` in the backing array, if it is already present.
*/
export declare function get<T extends Key>(setarr: SetArray<T>, key: T): number | undefined;
/**
* Puts `key` into the backing array, if it is not already present. Returns
* the index of the `key` in the backing array.
*/
export declare function put<T extends Key>(setarr: SetArray<T>, key: T): number;
/**
* Pops the last added item out of the SetArray.
*/
export declare function pop<T extends Key>(setarr: SetArray<T>): void;
/**
* Removes the key, if it exists in the set.
*/
export declare function remove<T extends Key>(setarr: SetArray<T>, key: T): void;
export {};
//# sourceMappingURL=set-array.d.ts.map
@@ -0,0 +1,13 @@
type GeneratedColumn = number;
type SourcesIndex = number;
type SourceLine = number;
type SourceColumn = number;
type NamesIndex = number;
export type SourceMapSegment = [GeneratedColumn] | [GeneratedColumn, SourcesIndex, SourceLine, SourceColumn] | [GeneratedColumn, SourcesIndex, SourceLine, SourceColumn, NamesIndex];
export declare const COLUMN = 0;
export declare const SOURCES_INDEX = 1;
export declare const SOURCE_LINE = 2;
export declare const SOURCE_COLUMN = 3;
export declare const NAMES_INDEX = 4;
export {};
//# sourceMappingURL=sourcemap-segment.d.ts.map
View File
+19
View File
@@ -0,0 +1,19 @@
Copyright 2024 Justin Ridgewell <justin@ridgewell.name>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,15 @@
import type { MapSource as MapSourceType } from './source-map-tree.cts';
import type { SourceMapInput, SourceMapLoader } from './types.cts';
/**
* Recursively builds a tree structure out of sourcemap files, with each node
* being either an `OriginalSource` "leaf" or a `SourceMapTree` composed of
* `OriginalSource`s and `SourceMapTree`s.
*
* Every sourcemap is composed of a collection of source files and mappings
* into locations of those source files. When we generate a `SourceMapTree` for
* the sourcemap, we attempt to load each source file's own sourcemap. If it
* does not have an associated sourcemap, it is considered an original,
* unmodified source file.
*/
export = function buildSourceMapTree(input: SourceMapInput | SourceMapInput[], loader: SourceMapLoader): MapSourceType;
//# sourceMappingURL=build-source-map-tree.d.ts.map
+21
View File
@@ -0,0 +1,21 @@
import SourceMap from './source-map.cts';
import type { SourceMapInput, SourceMapLoader, Options } from './types.cts';
export type { SourceMapSegment, EncodedSourceMap, EncodedSourceMap as RawSourceMap, DecodedSourceMap, SourceMapInput, SourceMapLoader, LoaderContext, Options, } from './types.cts';
export type { SourceMap };
/**
* Traces through all the mappings in the root sourcemap, through the sources
* (and their sourcemaps), all the way back to the original source location.
*
* `loader` will be called every time we encounter a source file. If it returns
* a sourcemap, we will recurse into that sourcemap to continue the trace. If
* it returns a falsey value, that source file is treated as an original,
* unmodified source file.
*
* Pass `excludeContent` to exclude any self-containing source file content
* from the output sourcemap.
*
* Pass `decodedMappings` to receive a SourceMap with decoded (instead of
* VLQ encoded) mappings.
*/
export = function remapping(input: SourceMapInput | SourceMapInput[], loader: SourceMapLoader, options?: boolean | Options): SourceMap;
//# sourceMappingURL=remapping.d.ts.map
@@ -0,0 +1,46 @@
import { GenMapping } from '@jridgewell/gen-mapping';
import type { TraceMap } from '@jridgewell/trace-mapping';
export type SourceMapSegmentObject = {
column: number;
line: number;
name: string;
source: string;
content: string | null;
ignore: boolean;
};
export type OriginalSource = {
map: null;
sources: Sources[];
source: string;
content: string | null;
ignore: boolean;
};
export type MapSource = {
map: TraceMap;
sources: Sources[];
source: string;
content: null;
ignore: false;
};
export type Sources = OriginalSource | MapSource;
/**
* MapSource represents a single sourcemap, with the ability to trace mappings into its child nodes
* (which may themselves be SourceMapTrees).
*/
export declare function MapSource(map: TraceMap, sources: Sources[]): MapSource;
/**
* A "leaf" node in the sourcemap tree, representing an original, unmodified source file. Recursive
* segment tracing ends at the `OriginalSource`.
*/
export declare function OriginalSource(source: string, content: string | null, ignore: boolean): OriginalSource;
/**
* traceMappings is only called on the root level SourceMapTree, and begins the process of
* resolving each mapping in terms of the original source files.
*/
export declare function traceMappings(tree: MapSource): GenMapping;
/**
* originalPositionFor is only called on children SourceMapTrees. It recurses down into its own
* child SourceMapTrees, until we find the original source map.
*/
export declare function originalPositionFor(source: Sources, line: number, column: number, name: string): SourceMapSegmentObject | null;
//# sourceMappingURL=source-map-tree.d.ts.map
+19
View File
@@ -0,0 +1,19 @@
Copyright 2019 Justin Ridgewell <jridgewell@google.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
File diff suppressed because one or more lines are too long
+240
View File
@@ -0,0 +1,240 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.resolveURI = factory());
})(this, (function () { 'use strict';
// Matches the scheme of a URL, eg "http://"
const schemeRegex = /^[\w+.-]+:\/\//;
/**
* Matches the parts of a URL:
* 1. Scheme, including ":", guaranteed.
* 2. User/password, including "@", optional.
* 3. Host, guaranteed.
* 4. Port, including ":", optional.
* 5. Path, including "/", optional.
* 6. Query, including "?", optional.
* 7. Hash, including "#", optional.
*/
const urlRegex = /^([\w+.-]+:)\/\/([^@/#?]*@)?([^:/#?]*)(:\d+)?(\/[^#?]*)?(\?[^#]*)?(#.*)?/;
/**
* File URLs are weird. They dont' need the regular `//` in the scheme, they may or may not start
* with a leading `/`, they can have a domain (but only if they don't start with a Windows drive).
*
* 1. Host, optional.
* 2. Path, which may include "/", guaranteed.
* 3. Query, including "?", optional.
* 4. Hash, including "#", optional.
*/
const fileRegex = /^file:(?:\/\/((?![a-z]:)[^/#?]*)?)?(\/?[^#?]*)(\?[^#]*)?(#.*)?/i;
function isAbsoluteUrl(input) {
return schemeRegex.test(input);
}
function isSchemeRelativeUrl(input) {
return input.startsWith('//');
}
function isAbsolutePath(input) {
return input.startsWith('/');
}
function isFileUrl(input) {
return input.startsWith('file:');
}
function isRelative(input) {
return /^[.?#]/.test(input);
}
function parseAbsoluteUrl(input) {
const match = urlRegex.exec(input);
return makeUrl(match[1], match[2] || '', match[3], match[4] || '', match[5] || '/', match[6] || '', match[7] || '');
}
function parseFileUrl(input) {
const match = fileRegex.exec(input);
const path = match[2];
return makeUrl('file:', '', match[1] || '', '', isAbsolutePath(path) ? path : '/' + path, match[3] || '', match[4] || '');
}
function makeUrl(scheme, user, host, port, path, query, hash) {
return {
scheme,
user,
host,
port,
path,
query,
hash,
type: 7 /* Absolute */,
};
}
function parseUrl(input) {
if (isSchemeRelativeUrl(input)) {
const url = parseAbsoluteUrl('http:' + input);
url.scheme = '';
url.type = 6 /* SchemeRelative */;
return url;
}
if (isAbsolutePath(input)) {
const url = parseAbsoluteUrl('http://foo.com' + input);
url.scheme = '';
url.host = '';
url.type = 5 /* AbsolutePath */;
return url;
}
if (isFileUrl(input))
return parseFileUrl(input);
if (isAbsoluteUrl(input))
return parseAbsoluteUrl(input);
const url = parseAbsoluteUrl('http://foo.com/' + input);
url.scheme = '';
url.host = '';
url.type = input
? input.startsWith('?')
? 3 /* Query */
: input.startsWith('#')
? 2 /* Hash */
: 4 /* RelativePath */
: 1 /* Empty */;
return url;
}
function stripPathFilename(path) {
// If a path ends with a parent directory "..", then it's a relative path with excess parent
// paths. It's not a file, so we can't strip it.
if (path.endsWith('/..'))
return path;
const index = path.lastIndexOf('/');
return path.slice(0, index + 1);
}
function mergePaths(url, base) {
normalizePath(base, base.type);
// If the path is just a "/", then it was an empty path to begin with (remember, we're a relative
// path).
if (url.path === '/') {
url.path = base.path;
}
else {
// Resolution happens relative to the base path's directory, not the file.
url.path = stripPathFilename(base.path) + url.path;
}
}
/**
* The path can have empty directories "//", unneeded parents "foo/..", or current directory
* "foo/.". We need to normalize to a standard representation.
*/
function normalizePath(url, type) {
const rel = type <= 4 /* RelativePath */;
const pieces = url.path.split('/');
// We need to preserve the first piece always, so that we output a leading slash. The item at
// pieces[0] is an empty string.
let pointer = 1;
// Positive is the number of real directories we've output, used for popping a parent directory.
// Eg, "foo/bar/.." will have a positive 2, and we can decrement to be left with just "foo".
let positive = 0;
// We need to keep a trailing slash if we encounter an empty directory (eg, splitting "foo/" will
// generate `["foo", ""]` pieces). And, if we pop a parent directory. But once we encounter a
// real directory, we won't need to append, unless the other conditions happen again.
let addTrailingSlash = false;
for (let i = 1; i < pieces.length; i++) {
const piece = pieces[i];
// An empty directory, could be a trailing slash, or just a double "//" in the path.
if (!piece) {
addTrailingSlash = true;
continue;
}
// If we encounter a real directory, then we don't need to append anymore.
addTrailingSlash = false;
// A current directory, which we can always drop.
if (piece === '.')
continue;
// A parent directory, we need to see if there are any real directories we can pop. Else, we
// have an excess of parents, and we'll need to keep the "..".
if (piece === '..') {
if (positive) {
addTrailingSlash = true;
positive--;
pointer--;
}
else if (rel) {
// If we're in a relativePath, then we need to keep the excess parents. Else, in an absolute
// URL, protocol relative URL, or an absolute path, we don't need to keep excess.
pieces[pointer++] = piece;
}
continue;
}
// We've encountered a real directory. Move it to the next insertion pointer, which accounts for
// any popped or dropped directories.
pieces[pointer++] = piece;
positive++;
}
let path = '';
for (let i = 1; i < pointer; i++) {
path += '/' + pieces[i];
}
if (!path || (addTrailingSlash && !path.endsWith('/..'))) {
path += '/';
}
url.path = path;
}
/**
* Attempts to resolve `input` URL/path relative to `base`.
*/
function resolve(input, base) {
if (!input && !base)
return '';
const url = parseUrl(input);
let inputType = url.type;
if (base && inputType !== 7 /* Absolute */) {
const baseUrl = parseUrl(base);
const baseType = baseUrl.type;
switch (inputType) {
case 1 /* Empty */:
url.hash = baseUrl.hash;
// fall through
case 2 /* Hash */:
url.query = baseUrl.query;
// fall through
case 3 /* Query */:
case 4 /* RelativePath */:
mergePaths(url, baseUrl);
// fall through
case 5 /* AbsolutePath */:
// The host, user, and port are joined, you can't copy one without the others.
url.user = baseUrl.user;
url.host = baseUrl.host;
url.port = baseUrl.port;
// fall through
case 6 /* SchemeRelative */:
// The input doesn't have a schema at least, so we need to copy at least that over.
url.scheme = baseUrl.scheme;
}
if (baseType > inputType)
inputType = baseType;
}
normalizePath(url, inputType);
const queryHash = url.query + url.hash;
switch (inputType) {
// This is impossible, because of the empty checks at the start of the function.
// case UrlType.Empty:
case 2 /* Hash */:
case 3 /* Query */:
return queryHash;
case 4 /* RelativePath */: {
// The first char is always a "/", and we need it to be relative.
const path = url.path.slice(1);
if (!path)
return queryHash || '.';
if (isRelative(base || input) && !isRelative(path)) {
// If base started with a leading ".", or there is no base and input started with a ".",
// then we need to ensure that the relative path starts with a ".". We don't know if
// relative starts with a "..", though, so check before prepending.
return './' + path + queryHash;
}
return path + queryHash;
}
case 5 /* AbsolutePath */:
return url.path + queryHash;
default:
return url.scheme + '//' + url.user + url.host + url.port + url.path + queryHash;
}
}
return resolve;
}));
//# sourceMappingURL=resolve-uri.umd.js.map
+69
View File
@@ -0,0 +1,69 @@
{
"name": "@jridgewell/resolve-uri",
"version": "3.1.2",
"description": "Resolve a URI relative to an optional base URI",
"keywords": [
"resolve",
"uri",
"url",
"path"
],
"author": "Justin Ridgewell <justin@ridgewell.name>",
"license": "MIT",
"repository": "https://github.com/jridgewell/resolve-uri",
"main": "dist/resolve-uri.umd.js",
"module": "dist/resolve-uri.mjs",
"types": "dist/types/resolve-uri.d.ts",
"exports": {
".": [
{
"types": "./dist/types/resolve-uri.d.ts",
"browser": "./dist/resolve-uri.umd.js",
"require": "./dist/resolve-uri.umd.js",
"import": "./dist/resolve-uri.mjs"
},
"./dist/resolve-uri.umd.js"
],
"./package.json": "./package.json"
},
"files": [
"dist"
],
"engines": {
"node": ">=6.0.0"
},
"scripts": {
"prebuild": "rm -rf dist",
"build": "run-s -n build:*",
"build:rollup": "rollup -c rollup.config.js",
"build:ts": "tsc --project tsconfig.build.json",
"lint": "run-s -n lint:*",
"lint:prettier": "npm run test:lint:prettier -- --write",
"lint:ts": "npm run test:lint:ts -- --fix",
"pretest": "run-s build:rollup",
"test": "run-s -n test:lint test:only",
"test:debug": "mocha --inspect-brk",
"test:lint": "run-s -n test:lint:*",
"test:lint:prettier": "prettier --check '{src,test}/**/*.ts'",
"test:lint:ts": "eslint '{src,test}/**/*.ts'",
"test:only": "mocha",
"test:coverage": "c8 mocha",
"test:watch": "mocha --watch",
"prepublishOnly": "npm run preversion",
"preversion": "run-s test build"
},
"devDependencies": {
"@jridgewell/resolve-uri-latest": "npm:@jridgewell/resolve-uri@*",
"@rollup/plugin-typescript": "8.3.0",
"@typescript-eslint/eslint-plugin": "5.10.0",
"@typescript-eslint/parser": "5.10.0",
"c8": "7.11.0",
"eslint": "8.7.0",
"eslint-config-prettier": "8.3.0",
"mocha": "9.2.0",
"npm-run-all": "4.1.5",
"prettier": "2.5.1",
"rollup": "2.66.0",
"typescript": "4.5.5"
}
}
+19
View File
@@ -0,0 +1,19 @@
Copyright 2024 Justin Ridgewell <justin@ridgewell.name>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+50
View File
@@ -0,0 +1,50 @@
type Line = number;
type Column = number;
type Kind = number;
type Name = number;
type Var = number;
type SourcesIndex = number;
type ScopesIndex = number;
type Mix<A, B, O> = (A & O) | (B & O);
export type OriginalScope = Mix<[
Line,
Column,
Line,
Column,
Kind
], [
Line,
Column,
Line,
Column,
Kind,
Name
], {
vars: Var[];
}>;
export type GeneratedRange = Mix<[
Line,
Column,
Line,
Column
], [
Line,
Column,
Line,
Column,
SourcesIndex,
ScopesIndex
], {
callsite: CallSite | null;
bindings: Binding[];
isScope: boolean;
}>;
export type CallSite = [SourcesIndex, Line, Column];
type Binding = BindingExpressionRange[];
export type BindingExpressionRange = [Name] | [Name, Line, Column];
export declare function decodeOriginalScopes(input: string): OriginalScope[];
export declare function encodeOriginalScopes(scopes: OriginalScope[]): string;
export declare function decodeGeneratedRanges(input: string): GeneratedRange[];
export declare function encodeGeneratedRanges(ranges: GeneratedRange[]): string;
export {};
//# sourceMappingURL=scopes.d.ts.map
@@ -0,0 +1,9 @@
export { decodeOriginalScopes, encodeOriginalScopes, decodeGeneratedRanges, encodeGeneratedRanges, } from './scopes.cts';
export type { OriginalScope, GeneratedRange, CallSite, BindingExpressionRange } from './scopes.cts';
export type SourceMapSegment = [number] | [number, number, number, number] | [number, number, number, number, number];
export type SourceMapLine = SourceMapSegment[];
export type SourceMapMappings = SourceMapLine[];
export declare function decode(mappings: string): SourceMapMappings;
export declare function encode(decoded: SourceMapMappings): string;
export declare function encode(decoded: Readonly<SourceMapMappings>): string;
//# sourceMappingURL=sourcemap-codec.d.ts.map
+16
View File
@@ -0,0 +1,16 @@
export declare class StringWriter {
pos: number;
private out;
private buffer;
write(v: number): void;
flush(): string;
}
export declare class StringReader {
pos: number;
private buffer;
constructor(buffer: string);
next(): number;
peek(): number;
indexOf(char: string): number;
}
//# sourceMappingURL=strings.d.ts.map
+19
View File
@@ -0,0 +1,19 @@
Copyright 2024 Justin Ridgewell <justin@ridgewell.name>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
@@ -0,0 +1,33 @@
import type { SourceMapSegment, ReverseSegment } from './sourcemap-segment.cts';
export type MemoState = {
lastKey: number;
lastNeedle: number;
lastIndex: number;
};
export declare let found: boolean;
/**
* A binary search implementation that returns the index if a match is found.
* If no match is found, then the left-index (the index associated with the item that comes just
* before the desired index) is returned. To maintain proper sort order, a splice would happen at
* the next index:
*
* ```js
* const array = [1, 3];
* const needle = 2;
* const index = binarySearch(array, needle, (item, needle) => item - needle);
*
* assert.equal(index, 0);
* array.splice(index + 1, 0, needle);
* assert.deepEqual(array, [1, 2, 3]);
* ```
*/
export declare function binarySearch(haystack: SourceMapSegment[] | ReverseSegment[], needle: number, low: number, high: number): number;
export declare function upperBound(haystack: SourceMapSegment[] | ReverseSegment[], needle: number, index: number): number;
export declare function lowerBound(haystack: SourceMapSegment[] | ReverseSegment[], needle: number, index: number): number;
export declare function memoizedState(): MemoState;
/**
* This overly complicated beast is just to record the last tested line/column and the resulting
* index, allowing us to skip a few tests if mappings are monotonically increasing.
*/
export declare function memoizedBinarySearch(haystack: SourceMapSegment[] | ReverseSegment[], needle: number, state: MemoState, key: number): number;
//# sourceMappingURL=binary-search.d.ts.map
@@ -0,0 +1,4 @@
import type { ReverseSegment, SourceMapSegment } from './sourcemap-segment.cts';
export type Source = ReverseSegment[][];
export = function buildBySources(decoded: readonly SourceMapSegment[][], memos: unknown[]): Source[];
//# sourceMappingURL=by-source.d.ts.map
@@ -0,0 +1,9 @@
import { TraceMap } from './trace-mapping.cts';
import type { SectionedSourceMapInput, Ro } from './types.cts';
type FlattenMap = {
new (map: Ro<SectionedSourceMapInput>, mapUrl?: string | null): TraceMap;
(map: Ro<SectionedSourceMapInput>, mapUrl?: string | null): TraceMap;
};
export declare const FlattenMap: FlattenMap;
export {};
//# sourceMappingURL=flatten-map.d.ts.map
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Denis Malinochkin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+104
View File
@@ -0,0 +1,104 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.readdir = exports.readdirWithFileTypes = exports.read = void 0;
const fsStat = require("@nodelib/fs.stat");
const rpl = require("run-parallel");
const constants_1 = require("../constants");
const utils = require("../utils");
const common = require("./common");
function read(directory, settings, callback) {
if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) {
readdirWithFileTypes(directory, settings, callback);
return;
}
readdir(directory, settings, callback);
}
exports.read = read;
function readdirWithFileTypes(directory, settings, callback) {
settings.fs.readdir(directory, { withFileTypes: true }, (readdirError, dirents) => {
if (readdirError !== null) {
callFailureCallback(callback, readdirError);
return;
}
const entries = dirents.map((dirent) => ({
dirent,
name: dirent.name,
path: common.joinPathSegments(directory, dirent.name, settings.pathSegmentSeparator)
}));
if (!settings.followSymbolicLinks) {
callSuccessCallback(callback, entries);
return;
}
const tasks = entries.map((entry) => makeRplTaskEntry(entry, settings));
rpl(tasks, (rplError, rplEntries) => {
if (rplError !== null) {
callFailureCallback(callback, rplError);
return;
}
callSuccessCallback(callback, rplEntries);
});
});
}
exports.readdirWithFileTypes = readdirWithFileTypes;
function makeRplTaskEntry(entry, settings) {
return (done) => {
if (!entry.dirent.isSymbolicLink()) {
done(null, entry);
return;
}
settings.fs.stat(entry.path, (statError, stats) => {
if (statError !== null) {
if (settings.throwErrorOnBrokenSymbolicLink) {
done(statError);
return;
}
done(null, entry);
return;
}
entry.dirent = utils.fs.createDirentFromStats(entry.name, stats);
done(null, entry);
});
};
}
function readdir(directory, settings, callback) {
settings.fs.readdir(directory, (readdirError, names) => {
if (readdirError !== null) {
callFailureCallback(callback, readdirError);
return;
}
const tasks = names.map((name) => {
const path = common.joinPathSegments(directory, name, settings.pathSegmentSeparator);
return (done) => {
fsStat.stat(path, settings.fsStatSettings, (error, stats) => {
if (error !== null) {
done(error);
return;
}
const entry = {
name,
path,
dirent: utils.fs.createDirentFromStats(name, stats)
};
if (settings.stats) {
entry.stats = stats;
}
done(null, entry);
});
};
});
rpl(tasks, (rplError, entries) => {
if (rplError !== null) {
callFailureCallback(callback, rplError);
return;
}
callSuccessCallback(callback, entries);
});
});
}
exports.readdir = readdir;
function callFailureCallback(callback, error) {
callback(error);
}
function callSuccessCallback(callback, result) {
callback(null, result);
}
+13
View File
@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.joinPathSegments = void 0;
function joinPathSegments(a, b, separator) {
/**
* The correct handling of cases when the first segment is a root (`/`, `C:/`) or UNC path (`//?/C:/`).
*/
if (a.endsWith(separator)) {
return a + b;
}
return a + separator + b;
}
exports.joinPathSegments = joinPathSegments;
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Denis Malinochkin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+36
View File
@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.read = void 0;
function read(path, settings, callback) {
settings.fs.lstat(path, (lstatError, lstat) => {
if (lstatError !== null) {
callFailureCallback(callback, lstatError);
return;
}
if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) {
callSuccessCallback(callback, lstat);
return;
}
settings.fs.stat(path, (statError, stat) => {
if (statError !== null) {
if (settings.throwErrorOnBrokenSymbolicLink) {
callFailureCallback(callback, statError);
return;
}
callSuccessCallback(callback, lstat);
return;
}
if (settings.markSymbolicLink) {
stat.isSymbolicLink = () => true;
}
callSuccessCallback(callback, stat);
});
});
}
exports.read = read;
function callFailureCallback(callback, error) {
callback(error);
}
function callSuccessCallback(callback, result) {
callback(null, result);
}
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) Denis Malinochkin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+30
View File
@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const async_1 = require("../readers/async");
class AsyncProvider {
constructor(_root, _settings) {
this._root = _root;
this._settings = _settings;
this._reader = new async_1.default(this._root, this._settings);
this._storage = [];
}
read(callback) {
this._reader.onError((error) => {
callFailureCallback(callback, error);
});
this._reader.onEntry((entry) => {
this._storage.push(entry);
});
this._reader.onEnd(() => {
callSuccessCallback(callback, this._storage);
});
this._reader.read();
}
}
exports.default = AsyncProvider;
function callFailureCallback(callback, error) {
callback(error);
}
function callSuccessCallback(callback, entries) {
callback(null, entries);
}
+23
View File
@@ -0,0 +1,23 @@
TypeBox: JSON Schema Type Builder with Static Type Resolution for TypeScript
The MIT License (MIT)
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+372
View File
@@ -0,0 +1,372 @@
"use strict";
/*--------------------------------------------------------------------------
@sinclair/typebox/value
The MIT License (MIT)
Copyright (c) 2017-2023 Haydn Paterson (sinclair) <haydn.developer@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
---------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.ValueCast = exports.ValueCastDereferenceError = exports.ValueCastUnknownTypeError = exports.ValueCastRecursiveTypeError = exports.ValueCastNeverTypeError = exports.ValueCastArrayUniqueItemsTypeError = exports.ValueCastReferenceTypeError = void 0;
const Types = require("../typebox");
const create_1 = require("./create");
const check_1 = require("./check");
const clone_1 = require("./clone");
// ----------------------------------------------------------------------------------------------
// Errors
// ----------------------------------------------------------------------------------------------
class ValueCastReferenceTypeError extends Error {
constructor(schema) {
super(`ValueCast: Cannot locate referenced schema with $id '${schema.$ref}'`);
this.schema = schema;
}
}
exports.ValueCastReferenceTypeError = ValueCastReferenceTypeError;
class ValueCastArrayUniqueItemsTypeError extends Error {
constructor(schema, value) {
super('ValueCast: Array cast produced invalid data due to uniqueItems constraint');
this.schema = schema;
this.value = value;
}
}
exports.ValueCastArrayUniqueItemsTypeError = ValueCastArrayUniqueItemsTypeError;
class ValueCastNeverTypeError extends Error {
constructor(schema) {
super('ValueCast: Never types cannot be cast');
this.schema = schema;
}
}
exports.ValueCastNeverTypeError = ValueCastNeverTypeError;
class ValueCastRecursiveTypeError extends Error {
constructor(schema) {
super('ValueCast.Recursive: Cannot cast recursive schemas');
this.schema = schema;
}
}
exports.ValueCastRecursiveTypeError = ValueCastRecursiveTypeError;
class ValueCastUnknownTypeError extends Error {
constructor(schema) {
super('ValueCast: Unknown type');
this.schema = schema;
}
}
exports.ValueCastUnknownTypeError = ValueCastUnknownTypeError;
class ValueCastDereferenceError extends Error {
constructor(schema) {
super(`ValueCast: Unable to dereference schema with $id '${schema.$ref}'`);
this.schema = schema;
}
}
exports.ValueCastDereferenceError = ValueCastDereferenceError;
// ----------------------------------------------------------------------------------------------
// The following will score a schema against a value. For objects, the score is the tally of
// points awarded for each property of the value. Property points are (1.0 / propertyCount)
// to prevent large property counts biasing results. Properties that match literal values are
// maximally awarded as literals are typically used as union discriminator fields.
// ----------------------------------------------------------------------------------------------
var UnionCastCreate;
(function (UnionCastCreate) {
function Score(schema, references, value) {
if (schema[Types.Kind] === 'Object' && typeof value === 'object' && value !== null) {
const object = schema;
const keys = Object.keys(value);
const entries = globalThis.Object.entries(object.properties);
const [point, max] = [1 / entries.length, entries.length];
return entries.reduce((acc, [key, schema]) => {
const literal = schema[Types.Kind] === 'Literal' && schema.const === value[key] ? max : 0;
const checks = check_1.ValueCheck.Check(schema, references, value[key]) ? point : 0;
const exists = keys.includes(key) ? point : 0;
return acc + (literal + checks + exists);
}, 0);
}
else {
return check_1.ValueCheck.Check(schema, references, value) ? 1 : 0;
}
}
function Select(union, references, value) {
let [select, best] = [union.anyOf[0], 0];
for (const schema of union.anyOf) {
const score = Score(schema, references, value);
if (score > best) {
select = schema;
best = score;
}
}
return select;
}
function Create(union, references, value) {
if (union.default !== undefined) {
return union.default;
}
else {
const schema = Select(union, references, value);
return ValueCast.Cast(schema, references, value);
}
}
UnionCastCreate.Create = Create;
})(UnionCastCreate || (UnionCastCreate = {}));
var ValueCast;
(function (ValueCast) {
// ----------------------------------------------------------------------------------------------
// Guards
// ----------------------------------------------------------------------------------------------
function IsObject(value) {
return typeof value === 'object' && value !== null && !globalThis.Array.isArray(value);
}
function IsArray(value) {
return typeof value === 'object' && globalThis.Array.isArray(value);
}
function IsNumber(value) {
return typeof value === 'number' && !isNaN(value);
}
function IsString(value) {
return typeof value === 'string';
}
// ----------------------------------------------------------------------------------------------
// Cast
// ----------------------------------------------------------------------------------------------
function Any(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? clone_1.ValueClone.Clone(value) : create_1.ValueCreate.Create(schema, references);
}
function Array(schema, references, value) {
if (check_1.ValueCheck.Check(schema, references, value))
return clone_1.ValueClone.Clone(value);
const created = IsArray(value) ? clone_1.ValueClone.Clone(value) : create_1.ValueCreate.Create(schema, references);
const minimum = IsNumber(schema.minItems) && created.length < schema.minItems ? [...created, ...globalThis.Array.from({ length: schema.minItems - created.length }, () => null)] : created;
const maximum = IsNumber(schema.maxItems) && minimum.length > schema.maxItems ? minimum.slice(0, schema.maxItems) : minimum;
const casted = maximum.map((value) => Visit(schema.items, references, value));
if (schema.uniqueItems !== true)
return casted;
const unique = [...new Set(casted)];
if (!check_1.ValueCheck.Check(schema, references, unique))
throw new ValueCastArrayUniqueItemsTypeError(schema, unique);
return unique;
}
function BigInt(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? value : create_1.ValueCreate.Create(schema, references);
}
function Boolean(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? value : create_1.ValueCreate.Create(schema, references);
}
function Constructor(schema, references, value) {
if (check_1.ValueCheck.Check(schema, references, value))
return create_1.ValueCreate.Create(schema, references);
const required = new Set(schema.returns.required || []);
const result = function () { };
for (const [key, property] of globalThis.Object.entries(schema.returns.properties)) {
if (!required.has(key) && value.prototype[key] === undefined)
continue;
result.prototype[key] = Visit(property, references, value.prototype[key]);
}
return result;
}
function Date(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? clone_1.ValueClone.Clone(value) : create_1.ValueCreate.Create(schema, references);
}
function Function(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? value : create_1.ValueCreate.Create(schema, references);
}
function Integer(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? value : create_1.ValueCreate.Create(schema, references);
}
function Intersect(schema, references, value) {
const created = create_1.ValueCreate.Create(schema, references);
const mapped = IsObject(created) && IsObject(value) ? { ...created, ...value } : value;
return check_1.ValueCheck.Check(schema, references, mapped) ? mapped : create_1.ValueCreate.Create(schema, references);
}
function Literal(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? value : create_1.ValueCreate.Create(schema, references);
}
function Never(schema, references, value) {
throw new ValueCastNeverTypeError(schema);
}
function Not(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? value : create_1.ValueCreate.Create(schema.allOf[1], references);
}
function Null(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? value : create_1.ValueCreate.Create(schema, references);
}
function Number(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? value : create_1.ValueCreate.Create(schema, references);
}
function Object(schema, references, value) {
if (check_1.ValueCheck.Check(schema, references, value))
return value;
if (value === null || typeof value !== 'object')
return create_1.ValueCreate.Create(schema, references);
const required = new Set(schema.required || []);
const result = {};
for (const [key, property] of globalThis.Object.entries(schema.properties)) {
if (!required.has(key) && value[key] === undefined)
continue;
result[key] = Visit(property, references, value[key]);
}
// additional schema properties
if (typeof schema.additionalProperties === 'object') {
const propertyNames = globalThis.Object.getOwnPropertyNames(schema.properties);
for (const propertyName of globalThis.Object.getOwnPropertyNames(value)) {
if (propertyNames.includes(propertyName))
continue;
result[propertyName] = Visit(schema.additionalProperties, references, value[propertyName]);
}
}
return result;
}
function Promise(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? value : create_1.ValueCreate.Create(schema, references);
}
function Record(schema, references, value) {
if (check_1.ValueCheck.Check(schema, references, value))
return clone_1.ValueClone.Clone(value);
if (value === null || typeof value !== 'object' || globalThis.Array.isArray(value) || value instanceof globalThis.Date)
return create_1.ValueCreate.Create(schema, references);
const subschemaPropertyName = globalThis.Object.getOwnPropertyNames(schema.patternProperties)[0];
const subschema = schema.patternProperties[subschemaPropertyName];
const result = {};
for (const [propKey, propValue] of globalThis.Object.entries(value)) {
result[propKey] = Visit(subschema, references, propValue);
}
return result;
}
function Ref(schema, references, value) {
const index = references.findIndex((foreign) => foreign.$id === schema.$ref);
if (index === -1)
throw new ValueCastDereferenceError(schema);
const target = references[index];
return Visit(target, references, value);
}
function String(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? value : create_1.ValueCreate.Create(schema, references);
}
function Symbol(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? clone_1.ValueClone.Clone(value) : create_1.ValueCreate.Create(schema, references);
}
function TemplateLiteral(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? clone_1.ValueClone.Clone(value) : create_1.ValueCreate.Create(schema, references);
}
function This(schema, references, value) {
const index = references.findIndex((foreign) => foreign.$id === schema.$ref);
if (index === -1)
throw new ValueCastDereferenceError(schema);
const target = references[index];
return Visit(target, references, value);
}
function Tuple(schema, references, value) {
if (check_1.ValueCheck.Check(schema, references, value))
return clone_1.ValueClone.Clone(value);
if (!globalThis.Array.isArray(value))
return create_1.ValueCreate.Create(schema, references);
if (schema.items === undefined)
return [];
return schema.items.map((schema, index) => Visit(schema, references, value[index]));
}
function Undefined(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? clone_1.ValueClone.Clone(value) : create_1.ValueCreate.Create(schema, references);
}
function Union(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? clone_1.ValueClone.Clone(value) : UnionCastCreate.Create(schema, references, value);
}
function Uint8Array(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? clone_1.ValueClone.Clone(value) : create_1.ValueCreate.Create(schema, references);
}
function Unknown(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? clone_1.ValueClone.Clone(value) : create_1.ValueCreate.Create(schema, references);
}
function Void(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? clone_1.ValueClone.Clone(value) : create_1.ValueCreate.Create(schema, references);
}
function UserDefined(schema, references, value) {
return check_1.ValueCheck.Check(schema, references, value) ? clone_1.ValueClone.Clone(value) : create_1.ValueCreate.Create(schema, references);
}
function Visit(schema, references, value) {
const references_ = IsString(schema.$id) ? [...references, schema] : references;
const schema_ = schema;
switch (schema[Types.Kind]) {
case 'Any':
return Any(schema_, references_, value);
case 'Array':
return Array(schema_, references_, value);
case 'BigInt':
return BigInt(schema_, references_, value);
case 'Boolean':
return Boolean(schema_, references_, value);
case 'Constructor':
return Constructor(schema_, references_, value);
case 'Date':
return Date(schema_, references_, value);
case 'Function':
return Function(schema_, references_, value);
case 'Integer':
return Integer(schema_, references_, value);
case 'Intersect':
return Intersect(schema_, references_, value);
case 'Literal':
return Literal(schema_, references_, value);
case 'Never':
return Never(schema_, references_, value);
case 'Not':
return Not(schema_, references_, value);
case 'Null':
return Null(schema_, references_, value);
case 'Number':
return Number(schema_, references_, value);
case 'Object':
return Object(schema_, references_, value);
case 'Promise':
return Promise(schema_, references_, value);
case 'Record':
return Record(schema_, references_, value);
case 'Ref':
return Ref(schema_, references_, value);
case 'String':
return String(schema_, references_, value);
case 'Symbol':
return Symbol(schema_, references_, value);
case 'TemplateLiteral':
return TemplateLiteral(schema_, references_, value);
case 'This':
return This(schema_, references_, value);
case 'Tuple':
return Tuple(schema_, references_, value);
case 'Undefined':
return Undefined(schema_, references_, value);
case 'Union':
return Union(schema_, references_, value);
case 'Uint8Array':
return Uint8Array(schema_, references_, value);
case 'Unknown':
return Unknown(schema_, references_, value);
case 'Void':
return Void(schema_, references_, value);
default:
if (!Types.TypeRegistry.Has(schema_[Types.Kind]))
throw new ValueCastUnknownTypeError(schema_);
return UserDefined(schema_, references_, value);
}
}
ValueCast.Visit = Visit;
function Cast(schema, references, value) {
return Visit(schema, references, clone_1.ValueClone.Clone(value));
}
ValueCast.Cast = Cast;
})(ValueCast = exports.ValueCast || (exports.ValueCast = {}));
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Microsoft Corporation.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE

Some files were not shown because too many files have changed in this diff Show More