snapshot of https://github.com/dream-num/univer.git (dev, 2026-09-16; blobs > 131072B omitted)
Some checks failed
📦 Build / build (push) Has been cancelled
🧪 CI Test / test shard (1/4) (push) Has been cancelled
🧪 CI Test / test shard (2/4) (push) Has been cancelled
🧪 CI Test / test shard (3/4) (push) Has been cancelled
🧪 CI Test / test shard (4/4) (push) Has been cancelled
🧪 CI Test / test (push) Has been cancelled
🧪 CI Test / eslint (push) Has been cancelled
🧪 CI Test / typecheck (push) Has been cancelled
🔫 Sync to Pro / dispatch-sync (push) Has been cancelled
🔮 Sync Mirror / sync (push) Has been cancelled

This commit is contained in:
upstream-mirror
2026-09-16 05:09:45 +05:00
commit bcf6ef63ad
7118 changed files with 1236854 additions and 0 deletions

View File

@@ -0,0 +1,54 @@
/**
* Copyright 2023-present DreamNum Co., Ltd.
*
* 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.
*/
import { RuleTester } from 'eslint';
import { describe, it } from 'vitest';
import noExternalImportsInFacade from './no-external-imports-in-facade';
const ruleTester = new RuleTester({
languageOptions: {
ecmaVersion: 2022,
sourceType: 'module',
},
});
describe('no-external-imports-in-facade', () => {
it('explains the facade package-entry migration path', () => {
ruleTester.run('no-external-imports-in-facade', noExternalImportsInFacade, {
valid: [
{
code: 'import { PublicCommand } from "@univerjs/example";',
filename: '/repo/packages/example/src/facade/f-univer.ts',
},
{
code: 'import { FThing } from "./f-thing";',
filename: '/repo/packages/example/src/facade/f-univer.ts',
},
],
invalid: [
{
code: 'import { PrivateCommand } from "../commands/private.command";',
filename: '/repo/packages/example/src/facade/f-univer.ts',
errors: [
{
message: 'Facade files are published as a separate package entry. Do not import package-internal runtime modules through "../commands/private.command"; it can duplicate module singletons when consumers import both the package root and the facade entry. Move the needed symbol to the package root export if necessary, then import it through the package name. Type-only imports should also use a public package entry.',
},
],
},
],
});
});
});

View File

@@ -0,0 +1,72 @@
import type { Rule } from 'eslint';
import path from 'node:path';
function getImportSourceValue(node: Rule.Node): string | null {
if (!('source' in node)) {
return null;
}
const source = (node as { source?: { value?: unknown } }).source;
if (!source || typeof source.value !== 'string') {
return null;
}
return source.value;
}
function getRuleFilename(context: Rule.RuleContext): string {
const filenameFromProperty = (context as { filename?: unknown }).filename;
if (typeof filenameFromProperty === 'string' && filenameFromProperty) {
return filenameFromProperty;
}
const getFilename = (context as { getFilename?: () => string }).getFilename;
return typeof getFilename === 'function' ? getFilename.call(context) : '';
}
const rule: Rule.RuleModule = {
meta: {
type: 'problem',
docs: {
description: 'Disallow imports from outside facade directory in facade files',
},
messages: {
noExternalImports: 'Facade files are published as a separate package entry. Do not import package-internal runtime modules through "{{importPath}}"; it can duplicate module singletons when consumers import both the package root and the facade entry. Move the needed symbol to the package root export if necessary, then import it through the package name. Type-only imports should also use a public package entry.',
},
},
create(context) {
const filename = getRuleFilename(context);
const normalizedPath = filename.split(path.sep).join('/');
const isFacadeFile = normalizedPath.includes('/facade/');
if (!isFacadeFile) {
return {};
}
const currentDir = path.dirname(filename);
return {
ImportDeclaration(node: Rule.Node) {
const importPath = getImportSourceValue(node);
if (!importPath || !importPath.startsWith('.')) {
return;
}
const resolvedPath = path.resolve(currentDir, importPath).split(path.sep).join('/');
if (!resolvedPath.includes('/facade/')) {
context.report({
node,
messageId: 'noExternalImports',
data: { importPath },
});
}
},
};
},
};
export default rule;

View File

@@ -0,0 +1,137 @@
import type { Rule } from 'eslint';
import path from 'node:path';
interface IRuleOptions { ignore?: unknown[] }
function getImportSourceValue(node: Rule.Node): string | null {
if (!('source' in node)) {
return null;
}
const source = (node as { source?: { value?: unknown } }).source;
if (!source || typeof source.value !== 'string') {
return null;
}
return source.value;
}
function normalizePath(filePath: string): string {
return filePath.split(path.sep).join('/');
}
function shouldIgnoreFile(normalizedFilePath: string, ignorePaths: readonly unknown[]): boolean {
return ignorePaths.some((ignorePath: unknown) => {
if (typeof ignorePath !== 'string') {
return false;
}
const normalizedIgnorePath = normalizePath(ignorePath).replace(/^\.\//, '');
if (!normalizedIgnorePath) {
return false;
}
return normalizedFilePath === normalizedIgnorePath
|| normalizedFilePath.endsWith(`/${normalizedIgnorePath}`)
|| normalizedFilePath.includes(normalizedIgnorePath);
});
}
function getRuleFilename(context: Rule.RuleContext): string {
const filenameFromProperty = (context as { filename?: unknown }).filename;
if (typeof filenameFromProperty === 'string' && filenameFromProperty) {
return filenameFromProperty;
}
const getFilename = (context as { getFilename?: () => string }).getFilename;
return typeof getFilename === 'function' ? getFilename.call(context) : '';
}
const rule: Rule.RuleModule = {
meta: {
type: 'problem',
docs: {
description: 'Disallow imports containing facade in non-facade files',
},
schema: [
{
type: 'object',
properties: {
ignore: {
type: 'array',
items: {
type: 'string',
},
},
},
additionalProperties: false,
},
],
messages: {
noFacadeImports: 'Imports containing "facade" are not allowed in non-facade files: "{{importPath}}"',
},
},
create(context) {
const filename = getRuleFilename(context);
const normalizedPath = normalizePath(filename);
const [ruleOptions = {} as IRuleOptions] = context.options as [IRuleOptions?];
const ignorePaths = Array.isArray(ruleOptions.ignore) ? ruleOptions.ignore : [];
if (shouldIgnoreFile(normalizedPath, ignorePaths)) {
return {};
}
const isInPackages = normalizedPath.includes('/packages/');
const isInFacade = normalizedPath.includes('/facade/');
if (!isInPackages || isInFacade) {
return {};
}
// get parent dir
const parentDirMatch = normalizedPath.match(/\/([^/]+)\/packages\//);
if (!parentDirMatch) {
return {};
}
return {
ImportDeclaration(node: Rule.Node) {
const importPath = getImportSourceValue(node);
// Check if the import path contains 'facade'
if (importPath?.includes('facade')) {
context.report({
node,
messageId: 'noFacadeImports',
data: { importPath },
});
}
},
ExportNamedDeclaration(node: Rule.Node) {
const exportPath = getImportSourceValue(node);
if (exportPath?.includes('facade')) {
context.report({
node,
messageId: 'noFacadeImports',
data: { importPath: exportPath },
});
}
},
ExportAllDeclaration(node: Rule.Node) {
const exportPath = getImportSourceValue(node);
if (exportPath?.includes('facade')) {
context.report({
node,
messageId: 'noFacadeImports',
data: { importPath: exportPath },
});
}
},
};
},
};
export default rule;

View File

@@ -0,0 +1,122 @@
import type { Rule, SourceCode } from 'eslint';
import path from 'node:path';
type ImportOrExportSpecifier = Rule.Node & {
importKind?: 'type' | 'value';
exportKind?: 'type' | 'value';
};
function normalizePath(filePath: string): string {
return filePath.split(path.sep).join('/');
}
function getRuleFilename(context: Rule.RuleContext): string {
const filenameFromProperty = (context as { filename?: unknown }).filename;
if (typeof filenameFromProperty === 'string' && filenameFromProperty) {
return filenameFromProperty;
}
const getFilename = (context as { getFilename?: () => string }).getFilename;
return typeof getFilename === 'function' ? getFilename.call(context) : '';
}
function isSrcIndexFile(context: Rule.RuleContext): boolean {
return normalizePath(getRuleFilename(context)).endsWith('/src/index.ts');
}
function isInlineTypeSpecifier(specifier: ImportOrExportSpecifier, kind: 'import' | 'export'): boolean {
return kind === 'import' ? specifier.importKind === 'type' : specifier.exportKind === 'type';
}
function trimInlineTypeKeyword(specifierText: string): string {
return specifierText.replace(/^type\s+/, '');
}
function buildReplacement(
sourceCode: SourceCode,
node: Rule.Node,
kind: 'import' | 'export'
): string | null {
const specifiers = (node as { specifiers?: ImportOrExportSpecifier[] }).specifiers;
if (!specifiers?.length) {
return null;
}
const typeSpecifiers = specifiers.filter((specifier) => isInlineTypeSpecifier(specifier, kind));
const valueSpecifiers = specifiers.filter((specifier) => !isInlineTypeSpecifier(specifier, kind));
if (typeSpecifiers.length === 0 || valueSpecifiers.length === 0) {
return null;
}
const nodeText = sourceCode.getText(node);
const openingBraceIndex = nodeText.indexOf('{');
const closingBraceIndex = nodeText.lastIndexOf('}');
if (openingBraceIndex === -1 || closingBraceIndex === -1 || closingBraceIndex < openingBraceIndex) {
return null;
}
const suffixWithSemicolon = nodeText.slice(closingBraceIndex + 1);
const hasSemicolon = suffixWithSemicolon.trimEnd().endsWith(';');
const suffix = hasSemicolon ? suffixWithSemicolon.replace(/;\s*$/, '') : suffixWithSemicolon;
const semicolon = hasSemicolon ? ';' : '';
const valueSpecifierText = valueSpecifiers.map((specifier) => sourceCode.getText(specifier)).join(', ');
const typeSpecifierText = typeSpecifiers
.map((specifier) => trimInlineTypeKeyword(sourceCode.getText(specifier)))
.join(', ');
return `${kind} { ${valueSpecifierText} }${suffix}${semicolon}\n${kind} type { ${typeSpecifierText} }${suffix}${semicolon}`;
}
const rule: Rule.RuleModule = {
meta: {
type: 'suggestion',
docs: {
description: 'Disallow mixing inline type specifiers with value specifiers in src/index.ts',
},
fixable: 'code',
messages: {
noMixedExportSpecifiers: 'Do not mix type and value export specifiers in src/index.ts.',
noMixedImportSpecifiers: 'Do not mix type and value import specifiers in src/index.ts.',
},
},
create(context) {
if (!isSrcIndexFile(context)) {
return {};
}
return {
ImportDeclaration(node: Rule.Node) {
const replacement = buildReplacement(context.sourceCode, node, 'import');
if (!replacement) {
return;
}
context.report({
node,
messageId: 'noMixedImportSpecifiers',
fix(fixer) {
return fixer.replaceText(node, replacement);
},
});
},
ExportNamedDeclaration(node: Rule.Node) {
const replacement = buildReplacement(context.sourceCode, node, 'export');
if (!replacement) {
return;
}
context.report({
node,
messageId: 'noMixedExportSpecifiers',
fix(fixer) {
return fixer.replaceText(node, replacement);
},
});
},
};
},
};
export default rule;

View File

@@ -0,0 +1,95 @@
import type { Rule } from 'eslint';
import path from 'node:path';
function getImportSourceValue(node: Rule.Node): string | null {
if (!('source' in node)) {
return null;
}
const source = (node as { source?: { value?: unknown } }).source;
if (!source || typeof source.value !== 'string') {
return null;
}
return source.value;
}
function getRuleFilename(context: Rule.RuleContext): string {
const filenameFromProperty = (context as { filename?: unknown }).filename;
if (typeof filenameFromProperty === 'string' && filenameFromProperty) {
return filenameFromProperty;
}
const getFilename = (context as { getFilename?: () => string }).getFilename;
return typeof getFilename === 'function' ? getFilename.call(context) : '';
}
const rule: Rule.RuleModule = {
meta: {
type: 'problem',
docs: {
description: 'Disallow self package imports in packages directory except facade',
},
messages: {
noSelfImport: 'Package cannot import itself: "{{importPath}}" in {{packageName}}',
},
},
create(context) {
const filename = getRuleFilename(context);
const normalizedPath = filename.split(path.sep).join('/');
const isInPackages = normalizedPath.includes('/packages/');
const isInFacade = normalizedPath.includes('/facade/');
if (!isInPackages || isInFacade) {
return {};
}
// get parent dir
const parentDirMatch = normalizedPath.match(/\/([^/]+)\/packages\//);
if (!parentDirMatch) {
return {};
}
const parentDir = parentDirMatch[1];
const packagePrefix = parentDir === 'univer'
? '@univerjs/' :
parentDir === 'univer-pro'
? '@univerjs-pro/' :
null;
if (!packagePrefix) {
return {};
}
// get package name
const packageMatch = normalizedPath.match(/\/packages\/([^/]+)/);
if (!packageMatch) {
return {};
}
const packageName = packageMatch[1];
const possiblePackageName = `${packagePrefix}${packageName}`;
return {
ImportDeclaration(node: Rule.Node) {
const importPath = getImportSourceValue(node);
if (importPath && importPath === possiblePackageName) {
context.report({
node,
messageId: 'noSelfImport',
data: {
importPath,
packageName: importPath,
},
});
}
},
};
},
};
export default rule;