save code
This commit is contained in:
@@ -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: 慢速测试
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
# Backend Tests Package
|
||||
@@ -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"]
|
||||
@@ -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"] == "登出成功"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
@@ -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
@@ -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"><</button>
|
||||
<button class="page-btn active">1</button>
|
||||
<button class="page-btn">2</button>
|
||||
<button class="page-btn">3</button>
|
||||
<button class="page-btn">></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
@@ -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
@@ -16,7 +16,7 @@
|
||||
- 无需复杂配置,降低使用门槛
|
||||
- 基于角色的权限控制(RBAC)
|
||||
- 简单易用的用户界面
|
||||
- 支持Excel数据导入,便于数据迁移
|
||||
- 数据初始化在部署时完成,无需手动导入
|
||||
|
||||
## 2. 功能需求
|
||||
|
||||
@@ -181,21 +181,6 @@
|
||||
- 项目管理相关字段
|
||||
- 不能修改:合同编号、项目名称、工程类别、业主信息等基础字段
|
||||
|
||||
### 2.5 数据导入
|
||||
|
||||
#### 2.5.1 Excel导入功能
|
||||
- 支持从Excel文件批量导入项目数据
|
||||
- 系统自动验证数据格式
|
||||
- 导入前显示数据预览
|
||||
- 导入后显示导入结果(成功数、失败数)
|
||||
- 支持错误日志导出
|
||||
|
||||
#### 2.5.2 数据管理
|
||||
- 查看已导入的数据
|
||||
- 编辑和删除导入的数据
|
||||
- 数据验证和提示
|
||||
- 数据备份功能
|
||||
|
||||
## 3. 用户界面
|
||||
|
||||
### 3.1 界面设计原则
|
||||
@@ -250,7 +235,6 @@
|
||||
- 关键词搜索
|
||||
- 排序功能
|
||||
- 创建新项目按钮
|
||||
- 批量操作(可选)
|
||||
- 分页功能
|
||||
|
||||
#### 3.2.6 项目详情页
|
||||
@@ -341,9 +325,6 @@ A: 目前系统不支持批量编辑,请逐个编辑。
|
||||
**Q: 项目创建后能否修改工程类别?**
|
||||
A: 可以,但有权限限制,请联系管理员。
|
||||
|
||||
**Q: 导入Excel失败怎么办?**
|
||||
A: 请检查Excel格式是否符合要求,查看错误日志。
|
||||
|
||||
### 6.3 联系支持
|
||||
- 技术支持:提供技术帮助和问题解答
|
||||
- 系统管理员:负责用户管理和权限分配
|
||||
@@ -351,8 +332,13 @@ A: 请检查Excel格式是否符合要求,查看错误日志。
|
||||
|
||||
## 7. 数据来源
|
||||
|
||||
### 7.1 原始数据
|
||||
系统支持从Excel文件导入项目数据,Excel文件格式参照提供的模板。
|
||||
### 7.1 初始数据
|
||||
系统初始数据在部署时直接写入数据库,包括:
|
||||
- 默认管理员账号
|
||||
- 基础项目分类信息
|
||||
- 示例项目数据(可选)
|
||||
|
||||
部署完成后,用户可以直接使用系统,无需手动导入数据。
|
||||
|
||||
### 7.2 工程分类
|
||||
根据业务需求,工程项目分为以下类别:
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
VITE_API_BASE_URL=http://localhost:5000/api/v1
|
||||
VITE_APP_TITLE=海洋项目管理系统
|
||||
@@ -0,0 +1,2 @@
|
||||
VITE_API_BASE_URL=https://api.example.com/api/v1
|
||||
VITE_APP_TITLE=海洋项目管理系统
|
||||
@@ -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'] }],
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 100,
|
||||
"arrowParens": "avoid",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
@@ -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 API,loading=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 API,loading=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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
```
|
||||
+70
@@ -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
|
||||
+1
File diff suppressed because one or more lines are too long
+22
@@ -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
@@ -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
|
||||
```
|
||||
+39
@@ -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
|
||||
+1
@@ -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
Binary file not shown.
+21
@@ -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.
|
||||
+1883
File diff suppressed because it is too large
Load Diff
+21
@@ -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.
|
||||
+1604
File diff suppressed because it is too large
Load Diff
+19
@@ -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.
|
||||
+1104
File diff suppressed because it is too large
Load Diff
+19
@@ -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
@@ -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
@@ -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
@@ -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.
|
||||
+1128
File diff suppressed because it is too large
Load Diff
+63
@@ -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
@@ -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.
|
||||
+22
@@ -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;
|
||||
+63
@@ -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
@@ -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
@@ -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;
|
||||
+53
@@ -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
@@ -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
@@ -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.
|
||||
+60
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
+13
@@ -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
|
||||
+19
@@ -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.
|
||||
+15
@@ -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
@@ -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
|
||||
+46
@@ -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
@@ -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.
|
||||
+1
File diff suppressed because one or more lines are too long
+240
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
+9
@@ -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
@@ -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
@@ -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.
|
||||
+33
@@ -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
|
||||
+4
@@ -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
|
||||
+9
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user