Merge commit 'be3e8236086165e5e45a5a10783823874b3f3ebd' as 'lib/vscode'

This commit is contained in:
Joe Previte
2020-12-15 15:52:33 -07:00
4649 changed files with 1311795 additions and 0 deletions

View File

@@ -0,0 +1,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import { TSESTree } from '@typescript-eslint/experimental-utils';
import { join } from 'path';
import * as minimatch from 'minimatch';
import { createImportRuleListener } from './utils';
interface ImportPatternsConfig {
target: string;
restrictions: string | string[];
}
export = new class implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
badImport: 'Imports violates \'{{restrictions}}\' restrictions. See https://github.com/microsoft/vscode/wiki/Source-Code-Organization'
},
docs: {
url: 'https://github.com/microsoft/vscode/wiki/Source-Code-Organization'
}
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
const configs = <ImportPatternsConfig[]>context.options;
for (const config of configs) {
if (minimatch(context.getFilename(), config.target)) {
return createImportRuleListener((node, value) => this._checkImport(context, config, node, value));
}
}
return {};
}
private _checkImport(context: eslint.Rule.RuleContext, config: ImportPatternsConfig, node: TSESTree.Node, path: string) {
// resolve relative paths
if (path[0] === '.') {
path = join(context.getFilename(), path);
}
let restrictions: string[];
if (typeof config.restrictions === 'string') {
restrictions = [config.restrictions];
} else {
restrictions = config.restrictions;
}
let matched = false;
for (const pattern of restrictions) {
if (minimatch(path, pattern)) {
matched = true;
break;
}
}
if (!matched) {
// None of the restrictions matched
context.report({
loc: node.loc,
messageId: 'badImport',
data: {
restrictions: restrictions.join(' or ')
}
});
}
}
};

View File

@@ -0,0 +1,83 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import { join, dirname } from 'path';
import { createImportRuleListener } from './utils';
type Config = {
allowed: Set<string>;
disallowed: Set<string>;
};
export = new class implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
layerbreaker: 'Bad layering. You are not allowed to access {{from}} from here, allowed layers are: [{{allowed}}]'
},
docs: {
url: 'https://github.com/microsoft/vscode/wiki/Source-Code-Organization'
}
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
const fileDirname = dirname(context.getFilename());
const parts = fileDirname.split(/\\|\//);
const ruleArgs = <Record<string, string[]>>context.options[0];
let config: Config | undefined;
for (let i = parts.length - 1; i >= 0; i--) {
if (ruleArgs[parts[i]]) {
config = {
allowed: new Set(ruleArgs[parts[i]]).add(parts[i]),
disallowed: new Set()
};
Object.keys(ruleArgs).forEach(key => {
if (!config!.allowed.has(key)) {
config!.disallowed.add(key);
}
});
break;
}
}
if (!config) {
// nothing
return {};
}
return createImportRuleListener((node, path) => {
if (path[0] === '.') {
path = join(dirname(context.getFilename()), path);
}
const parts = dirname(path).split(/\\|\//);
for (let i = parts.length - 1; i >= 0; i--) {
const part = parts[i];
if (config!.allowed.has(part)) {
// GOOD - same layer
break;
}
if (config!.disallowed.has(part)) {
// BAD - wrong layer
context.report({
loc: node.loc,
messageId: 'layerbreaker',
data: {
from: part,
allowed: [...config!.allowed.keys()].join(', ')
}
});
break;
}
}
});
}
};

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import { join } from 'path';
import { createImportRuleListener } from './utils';
export = new class NoNlsInStandaloneEditorRule implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
noNls: 'Not allowed to import vs/nls in standalone editor modules. Use standaloneStrings.ts'
}
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
const fileName = context.getFilename();
if (
/vs(\/|\\)editor(\/|\\)standalone(\/|\\)/.test(fileName)
|| /vs(\/|\\)editor(\/|\\)common(\/|\\)standalone(\/|\\)/.test(fileName)
|| /vs(\/|\\)editor(\/|\\)editor.api/.test(fileName)
|| /vs(\/|\\)editor(\/|\\)editor.main/.test(fileName)
|| /vs(\/|\\)editor(\/|\\)editor.worker/.test(fileName)
) {
return createImportRuleListener((node, path) => {
// resolve relative paths
if (path[0] === '.') {
path = join(context.getFilename(), path);
}
if (
/vs(\/|\\)nls/.test(path)
) {
context.report({
loc: node.loc,
messageId: 'noNls'
});
}
});
}
return {};
}
};

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import { join } from 'path';
import { createImportRuleListener } from './utils';
export = new class NoNlsInStandaloneEditorRule implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
badImport: 'Not allowed to import standalone editor modules.'
},
docs: {
url: 'https://github.com/microsoft/vscode/wiki/Source-Code-Organization'
}
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
if (/vs(\/|\\)editor/.test(context.getFilename())) {
// the vs/editor folder is allowed to use the standalone editor
return {};
}
return createImportRuleListener((node, path) => {
// resolve relative paths
if (path[0] === '.') {
path = join(context.getFilename(), path);
}
if (
/vs(\/|\\)editor(\/|\\)standalone(\/|\\)/.test(path)
|| /vs(\/|\\)editor(\/|\\)common(\/|\\)standalone(\/|\\)/.test(path)
|| /vs(\/|\\)editor(\/|\\)editor.api/.test(path)
|| /vs(\/|\\)editor(\/|\\)editor.main/.test(path)
|| /vs(\/|\\)editor(\/|\\)editor.worker/.test(path)
) {
context.report({
loc: node.loc,
messageId: 'badImport'
});
}
});
}
};

View File

@@ -0,0 +1,126 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
function isStringLiteral(node: TSESTree.Node | null | undefined): node is TSESTree.StringLiteral {
return !!node && node.type === AST_NODE_TYPES.Literal && typeof node.value === 'string';
}
function isDoubleQuoted(node: TSESTree.StringLiteral): boolean {
return node.raw[0] === '"' && node.raw[node.raw.length - 1] === '"';
}
export = new class NoUnexternalizedStrings implements eslint.Rule.RuleModule {
private static _rNlsKeys = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/;
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
doubleQuoted: 'Only use double-quoted strings for externalized strings.',
badKey: 'The key \'{{key}}\' doesn\'t conform to a valid localize identifier.',
duplicateKey: 'Duplicate key \'{{key}}\' with different message value.',
badMessage: 'Message argument to \'{{message}}\' must be a string literal.'
}
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
const externalizedStringLiterals = new Map<string, { call: TSESTree.CallExpression, message: TSESTree.Node }[]>();
const doubleQuotedStringLiterals = new Set<TSESTree.Node>();
function collectDoubleQuotedStrings(node: TSESTree.Literal) {
if (isStringLiteral(node) && isDoubleQuoted(node)) {
doubleQuotedStringLiterals.add(node);
}
}
function visitLocalizeCall(node: TSESTree.CallExpression) {
// localize(key, message)
const [keyNode, messageNode] = (<TSESTree.CallExpression>node).arguments;
// (1)
// extract key so that it can be checked later
let key: string | undefined;
if (isStringLiteral(keyNode)) {
doubleQuotedStringLiterals.delete(keyNode); //todo@joh reconsider
key = keyNode.value;
} else if (keyNode.type === AST_NODE_TYPES.ObjectExpression) {
for (let property of keyNode.properties) {
if (property.type === AST_NODE_TYPES.Property && !property.computed) {
if (property.key.type === AST_NODE_TYPES.Identifier && property.key.name === 'key') {
if (isStringLiteral(property.value)) {
doubleQuotedStringLiterals.delete(property.value); //todo@joh reconsider
key = property.value.value;
break;
}
}
}
}
}
if (typeof key === 'string') {
let array = externalizedStringLiterals.get(key);
if (!array) {
array = [];
externalizedStringLiterals.set(key, array);
}
array.push({ call: node, message: messageNode });
}
// (2)
// remove message-argument from doubleQuoted list and make
// sure it is a string-literal
doubleQuotedStringLiterals.delete(messageNode);
if (!isStringLiteral(messageNode)) {
context.report({
loc: messageNode.loc,
messageId: 'badMessage',
data: { message: context.getSourceCode().getText(<any>node) }
});
}
}
function reportBadStringsAndBadKeys() {
// (1)
// report all strings that are in double quotes
for (const node of doubleQuotedStringLiterals) {
context.report({ loc: node.loc, messageId: 'doubleQuoted' });
}
for (const [key, values] of externalizedStringLiterals) {
// (2)
// report all invalid NLS keys
if (!key.match(NoUnexternalizedStrings._rNlsKeys)) {
for (let value of values) {
context.report({ loc: value.call.loc, messageId: 'badKey', data: { key } });
}
}
// (2)
// report all invalid duplicates (same key, different message)
if (values.length > 1) {
for (let i = 1; i < values.length; i++) {
if (context.getSourceCode().getText(<any>values[i - 1].message) !== context.getSourceCode().getText(<any>values[i].message)) {
context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } });
}
}
}
}
}
return {
['Literal']: (node: any) => collectDoubleQuotedStrings(node),
['ExpressionStatement[directive] Literal:exit']: (node: any) => doubleQuotedStringLiterals.delete(node),
['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize"]:exit']: (node: any) => visitLocalizeCall(node),
['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node: any) => visitLocalizeCall(node),
['Program:exit']: reportBadStringsAndBadKeys,
};
}
};

View File

@@ -0,0 +1,149 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// FORKED FROM https://github.com/eslint/eslint/blob/b23ad0d789a909baf8d7c41a35bc53df932eaf30/lib/rules/no-unused-expressions.js
// and added support for `OptionalCallExpression`, see https://github.com/facebook/create-react-app/issues/8107 and https://github.com/eslint/eslint/issues/12642
/**
* @fileoverview Flag expressions in statement position that do not side effect
* @author Michael Ficarra
*/
'use strict';
import * as eslint from 'eslint';
import { TSESTree } from '@typescript-eslint/experimental-utils';
import * as ESTree from 'estree';
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
type: 'suggestion',
docs: {
description: 'disallow unused expressions',
category: 'Best Practices',
recommended: false,
url: 'https://eslint.org/docs/rules/no-unused-expressions'
},
schema: [
{
type: 'object',
properties: {
allowShortCircuit: {
type: 'boolean',
default: false
},
allowTernary: {
type: 'boolean',
default: false
},
allowTaggedTemplates: {
type: 'boolean',
default: false
}
},
additionalProperties: false
}
]
},
create(context: eslint.Rule.RuleContext) {
const config = context.options[0] || {},
allowShortCircuit = config.allowShortCircuit || false,
allowTernary = config.allowTernary || false,
allowTaggedTemplates = config.allowTaggedTemplates || false;
// eslint-disable-next-line jsdoc/require-description
/**
* @param node any node
* @returns whether the given node structurally represents a directive
*/
function looksLikeDirective(node: TSESTree.Node): boolean {
return node.type === 'ExpressionStatement' &&
node.expression.type === 'Literal' && typeof node.expression.value === 'string';
}
// eslint-disable-next-line jsdoc/require-description
/**
* @param predicate ([a] -> Boolean) the function used to make the determination
* @param list the input list
* @returns the leading sequence of members in the given list that pass the given predicate
*/
function takeWhile<T>(predicate: (item: T) => boolean, list: T[]): T[] {
for (let i = 0; i < list.length; ++i) {
if (!predicate(list[i])) {
return list.slice(0, i);
}
}
return list.slice();
}
// eslint-disable-next-line jsdoc/require-description
/**
* @param node a Program or BlockStatement node
* @returns the leading sequence of directive nodes in the given node's body
*/
function directives(node: TSESTree.Program | TSESTree.BlockStatement): TSESTree.Node[] {
return takeWhile(looksLikeDirective, node.body);
}
// eslint-disable-next-line jsdoc/require-description
/**
* @param node any node
* @param ancestors the given node's ancestors
* @returns whether the given node is considered a directive in its current position
*/
function isDirective(node: TSESTree.Node, ancestors: TSESTree.Node[]): boolean {
const parent = ancestors[ancestors.length - 1],
grandparent = ancestors[ancestors.length - 2];
return (parent.type === 'Program' || parent.type === 'BlockStatement' &&
(/Function/u.test(grandparent.type))) &&
directives(parent).indexOf(node) >= 0;
}
/**
* Determines whether or not a given node is a valid expression. Recurses on short circuit eval and ternary nodes if enabled by flags.
* @param node any node
* @returns whether the given node is a valid expression
*/
function isValidExpression(node: TSESTree.Node): boolean {
if (allowTernary) {
// Recursive check for ternary and logical expressions
if (node.type === 'ConditionalExpression') {
return isValidExpression(node.consequent) && isValidExpression(node.alternate);
}
}
if (allowShortCircuit) {
if (node.type === 'LogicalExpression') {
return isValidExpression(node.right);
}
}
if (allowTaggedTemplates && node.type === 'TaggedTemplateExpression') {
return true;
}
return /^(?:Assignment|OptionalCall|Call|New|Update|Yield|Await)Expression$/u.test(node.type) ||
(node.type === 'UnaryExpression' && ['delete', 'void'].indexOf(node.operator) >= 0);
}
return {
ExpressionStatement(node: TSESTree.ExpressionStatement) {
if (!isValidExpression(node.expression) && !isDirective(node, <TSESTree.Node[]>context.getAncestors())) {
context.report({ node: <ESTree.Node>node, message: 'Expected an assignment or function call and instead saw an expression.' });
}
}
};
}
};

View File

@@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import { TSESTree } from '@typescript-eslint/experimental-utils';
import { readFileSync } from 'fs';
import { createImportRuleListener } from './utils';
export = new class TranslationRemind implements eslint.Rule.RuleModule {
private static NLS_MODULE = 'vs/nls';
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
missing: 'Please add \'{{resource}}\' to ./build/lib/i18n.resources.json file to use translations here.'
}
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
return createImportRuleListener((node, path) => this._checkImport(context, node, path));
}
private _checkImport(context: eslint.Rule.RuleContext, node: TSESTree.Node, path: string) {
if (path !== TranslationRemind.NLS_MODULE) {
return;
}
const currentFile = context.getFilename();
const matchService = currentFile.match(/vs\/workbench\/services\/\w+/);
const matchPart = currentFile.match(/vs\/workbench\/contrib\/\w+/);
if (!matchService && !matchPart) {
return;
}
const resource = matchService ? matchService[0] : matchPart![0];
let resourceDefined = false;
let json;
try {
json = readFileSync('./build/lib/i18n.resources.json', 'utf8');
} catch (e) {
console.error('[translation-remind rule]: File with resources to pull from Transifex was not found. Aborting translation resource check for newly defined workbench part/service.');
return;
}
const workbenchResources = JSON.parse(json).workbench;
workbenchResources.forEach((existingResource: any) => {
if (existingResource.name === resource) {
resourceDefined = true;
return;
}
});
if (!resourceDefined) {
context.report({
loc: node.loc,
messageId: 'missing',
data: { resource }
});
}
}
};

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import { TSESTree } from '@typescript-eslint/experimental-utils';
export function createImportRuleListener(validateImport: (node: TSESTree.Literal, value: string) => any): eslint.Rule.RuleListener {
function _checkImport(node: TSESTree.Node | null) {
if (node && node.type === 'Literal' && typeof node.value === 'string') {
validateImport(node, node.value);
}
}
return {
// import ??? from 'module'
ImportDeclaration: (node: any) => {
_checkImport((<TSESTree.ImportDeclaration>node).source);
},
// import('module').then(...) OR await import('module')
['CallExpression[callee.type="Import"][arguments.length=1] > Literal']: (node: any) => {
_checkImport(node);
},
// import foo = ...
['TSImportEqualsDeclaration > TSExternalModuleReference > Literal']: (node: any) => {
_checkImport(node);
},
// export ?? from 'module'
ExportAllDeclaration: (node: any) => {
_checkImport((<TSESTree.ExportAllDeclaration>node).source);
},
// export {foo} from 'module'
ExportNamedDeclaration: (node: any) => {
_checkImport((<TSESTree.ExportNamedDeclaration>node).source);
},
};
}

View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#creating-objects' },
messages: { sync: '`createXYZ`-functions are constructor-replacements and therefore must return sync', }
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
return {
['TSDeclareFunction Identifier[name=/create.*/]']: (node: any) => {
const decl = <TSESTree.FunctionDeclaration>(<TSESTree.Identifier>node).parent;
if (decl.returnType?.typeAnnotation.type !== AST_NODE_TYPES.TSTypeReference) {
return;
}
if (decl.returnType.typeAnnotation.typeName.type !== AST_NODE_TYPES.Identifier) {
return;
}
const ident = decl.returnType.typeAnnotation.typeName.name;
if (ident === 'Promise' || ident === 'Thenable') {
context.report({
node,
messageId: 'sync'
});
}
}
};
}
};

View File

@@ -0,0 +1,98 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/experimental-utils';
export = new class ApiEventNaming implements eslint.Rule.RuleModule {
private static _nameRegExp = /on(Did|Will)([A-Z][a-z]+)([A-Z][a-z]+)?/;
readonly meta: eslint.Rule.RuleMetaData = {
docs: {
url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#event-naming'
},
messages: {
naming: 'Event names must follow this patten: `on[Did|Will]<Verb><Subject>`',
verb: 'Unknown verb \'{{verb}}\' - is this really a verb? Iff so, then add this verb to the configuration',
subject: 'Unknown subject \'{{subject}}\' - This subject has not been used before but it should refer to something in the API',
unknown: 'UNKNOWN event declaration, lint-rule needs tweaking'
}
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
const config = <{ allowed: string[], verbs: string[] }>context.options[0];
const allowed = new Set(config.allowed);
const verbs = new Set(config.verbs);
return {
['TSTypeAnnotation TSTypeReference Identifier[name="Event"]']: (node: any) => {
const def = (<TSESTree.Identifier>node).parent?.parent?.parent;
const ident = this.getIdent(def);
if (!ident) {
// event on unknown structure...
return context.report({
node,
message: 'unknown'
});
}
if (allowed.has(ident.name)) {
// configured exception
return;
}
const match = ApiEventNaming._nameRegExp.exec(ident.name);
if (!match) {
context.report({
node: ident,
messageId: 'naming'
});
return;
}
// check that <verb> is spelled out (configured) as verb
if (!verbs.has(match[2].toLowerCase())) {
context.report({
node: ident,
messageId: 'verb',
data: { verb: match[2] }
});
}
// check that a subject (if present) has occurred
if (match[3]) {
const regex = new RegExp(match[3], 'ig');
const parts = context.getSourceCode().getText().split(regex);
if (parts.length < 3) {
context.report({
node: ident,
messageId: 'subject',
data: { subject: match[3] }
});
}
}
}
};
}
private getIdent(def: TSESTree.Node | undefined): TSESTree.Identifier | undefined {
if (!def) {
return;
}
if (def.type === AST_NODE_TYPES.Identifier) {
return def;
} else if ((def.type === AST_NODE_TYPES.TSPropertySignature || def.type === AST_NODE_TYPES.ClassProperty) && def.key.type === AST_NODE_TYPES.Identifier) {
return def.key;
}
return this.getIdent(def.parent);
}
};

View File

@@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
import { TSESTree } from '@typescript-eslint/experimental-utils';
export = new class ApiInterfaceNaming implements eslint.Rule.RuleModule {
private static _nameRegExp = /I[A-Z]/;
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
naming: 'Interfaces must not be prefixed with uppercase `I`',
}
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
return {
['TSInterfaceDeclaration Identifier']: (node: any) => {
const name = (<TSESTree.Identifier>node).name;
if (ApiInterfaceNaming._nameRegExp.test(name)) {
context.report({
node,
messageId: 'naming'
});
}
}
};
}
};

View File

@@ -0,0 +1,28 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
export = new class ApiLiteralOrTypes implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
docs: { url: 'https://github.com/microsoft/vscode/wiki/Extension-API-guidelines#enums' },
messages: { useEnum: 'Use enums, not literal-or-types', }
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
return {
['TSTypeAnnotation TSUnionType TSLiteralType']: (node: any) => {
if (node.literal?.type === 'TSNullKeyword') {
return;
}
context.report({
node: node,
messageId: 'useEnum'
});
}
};
}
};