Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2 (#8911)

* Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2

* update distro

* fix layering

* update distro

* fix tests
This commit is contained in:
Anthony Dresser
2020-01-22 13:42:37 -08:00
committed by GitHub
parent 977111eb21
commit bd7aac8ee0
895 changed files with 24651 additions and 14520 deletions

View File

@@ -0,0 +1,59 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path_1 = require("path");
const minimatch = require("minimatch");
const utils_1 = require("./utils");
module.exports = new class {
constructor() {
this.meta = {
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) {
const configs = context.options;
for (const config of configs) {
if (minimatch(context.getFilename(), config.target)) {
return utils_1.createImportRuleListener((node, value) => this._checkImport(context, config, node, value));
}
}
return {};
}
_checkImport(context, config, node, path) {
// resolve relative paths
if (path[0] === '.') {
path = path_1.join(context.getFilename(), path);
}
let restrictions;
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,75 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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,68 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path_1 = require("path");
const utils_1 = require("./utils");
module.exports = new class {
constructor() {
this.meta = {
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) {
const fileDirname = path_1.dirname(context.getFilename());
const parts = fileDirname.split(/\\|\//);
const ruleArgs = context.options[0];
let config;
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 utils_1.createImportRuleListener((node, path) => {
if (path[0] === '.') {
path = path_1.join(path_1.dirname(context.getFilename()), path);
}
const parts = path_1.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,83 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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,38 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path_1 = require("path");
const utils_1 = require("./utils");
module.exports = new class NoNlsInStandaloneEditorRule {
constructor() {
this.meta = {
messages: {
noNls: 'Not allowed to import vs/nls in standalone editor modules. Use standaloneStrings.ts'
}
};
}
create(context) {
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 utils_1.createImportRuleListener((node, path) => {
// resolve relative paths
if (path[0] === '.') {
path = path_1.join(context.getFilename(), path);
}
if (/vs(\/|\\)nls/.test(path)) {
context.report({
loc: node.loc,
messageId: 'noNls'
});
}
});
}
return {};
}
};

View File

@@ -0,0 +1,48 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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,41 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path_1 = require("path");
const utils_1 = require("./utils");
module.exports = new class NoNlsInStandaloneEditorRule {
constructor() {
this.meta = {
messages: {
badImport: 'Not allowed to import standalone editor modules.'
},
docs: {
url: 'https://github.com/microsoft/vscode/wiki/Source-Code-Organization'
}
};
}
create(context) {
if (/vs(\/|\\)editor/.test(context.getFilename())) {
// the vs/editor folder is allowed to use the standalone editor
return {};
}
return utils_1.createImportRuleListener((node, path) => {
// resolve relative paths
if (path[0] === '.') {
path = path_1.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,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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,111 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var _a;
const experimental_utils_1 = require("@typescript-eslint/experimental-utils");
function isStringLiteral(node) {
return !!node && node.type === experimental_utils_1.AST_NODE_TYPES.Literal && typeof node.value === 'string';
}
function isDoubleQuoted(node) {
return node.raw[0] === '"' && node.raw[node.raw.length - 1] === '"';
}
module.exports = new (_a = class NoUnexternalizedStrings {
constructor() {
this.meta = {
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) {
const externalizedStringLiterals = new Map();
const doubleQuotedStringLiterals = new Set();
function collectDoubleQuotedStrings(node) {
if (isStringLiteral(node) && isDoubleQuoted(node)) {
doubleQuotedStringLiterals.add(node);
}
}
function visitLocalizeCall(node) {
// localize(key, message)
const [keyNode, messageNode] = node.arguments;
// (1)
// extract key so that it can be checked later
let key;
if (isStringLiteral(keyNode)) {
doubleQuotedStringLiterals.delete(keyNode); //todo@joh reconsider
key = keyNode.value;
}
else if (keyNode.type === experimental_utils_1.AST_NODE_TYPES.ObjectExpression) {
for (let property of keyNode.properties) {
if (property.type === experimental_utils_1.AST_NODE_TYPES.Property && !property.computed) {
if (property.key.type === experimental_utils_1.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(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(values[i - 1].message) !== context.getSourceCode().getText(values[i].message)) {
context.report({ loc: values[i].call.loc, messageId: 'duplicateKey', data: { key } });
}
}
}
}
}
return {
['Literal']: (node) => collectDoubleQuotedStrings(node),
['ExpressionStatement[directive] Literal:exit']: (node) => doubleQuotedStringLiterals.delete(node),
['CallExpression[callee.type="MemberExpression"][callee.object.name="nls"][callee.property.name="localize"]:exit']: (node) => visitLocalizeCall(node),
['CallExpression[callee.name="localize"][arguments.length>=2]:exit']: (node) => visitLocalizeCall(node),
['Program:exit']: reportBadStringsAndBadKeys,
};
}
},
_a._rNlsKeys = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/,
_a);

View File

@@ -0,0 +1,126 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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,145 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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';
//------------------------------------------------------------------------------
// 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) {
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 {ASTNode} node any node
* @returns {boolean} whether the given node structurally represents a directive
*/
function looksLikeDirective(node) {
return node.type === 'ExpressionStatement' &&
node.expression.type === 'Literal' && typeof node.expression.value === 'string';
}
// eslint-disable-next-line jsdoc/require-description
/**
* @param {Function} predicate ([a] -> Boolean) the function used to make the determination
* @param {a[]} list the input list
* @returns {a[]} the leading sequence of members in the given list that pass the given predicate
*/
function takeWhile(predicate, list) {
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 {ASTNode} node a Program or BlockStatement node
* @returns {ASTNode[]} the leading sequence of directive nodes in the given node's body
*/
function directives(node) {
return takeWhile(looksLikeDirective, node.body);
}
// eslint-disable-next-line jsdoc/require-description
/**
* @param {ASTNode} node any node
* @param {ASTNode[]} ancestors the given node's ancestors
* @returns {boolean} whether the given node is considered a directive in its current position
*/
function isDirective(node, ancestors) {
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 {ASTNode} node any node
* @returns {boolean} whether the given node is a valid expression
*/
function isValidExpression(node) {
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) {
if (!isValidExpression(node.expression) && !isDirective(node, context.getAncestors())) {
context.report({ node, message: 'Expected an assignment or function call and instead saw an expression.' });
}
}
};
}
};

View File

@@ -0,0 +1,57 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var _a;
const fs_1 = require("fs");
const utils_1 = require("./utils");
module.exports = new (_a = class TranslationRemind {
constructor() {
this.meta = {
messages: {
missing: 'Please add \'{{resource}}\' to ./build/lib/i18n.resources.json file to use translations here.'
}
};
}
create(context) {
return utils_1.createImportRuleListener((node, path) => this._checkImport(context, node, path));
}
_checkImport(context, node, path) {
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 = fs_1.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) => {
if (existingResource.name === resource) {
resourceDefined = true;
return;
}
});
if (!resourceDefined) {
context.report({
loc: node.loc,
messageId: 'missing',
data: { resource }
});
}
}
},
_a.NLS_MODULE = 'vs/nls',
_a);

View File

@@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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 }
});
}
}
};

36
build/lib/eslint/utils.js Normal file
View File

@@ -0,0 +1,36 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
function createImportRuleListener(validateImport) {
function _checkImport(node) {
if (node && node.type === 'Literal' && typeof node.value === 'string') {
validateImport(node, node.value);
}
}
return {
// import ??? from 'module'
ImportDeclaration: (node) => {
_checkImport(node.source);
},
// import('module').then(...) OR await import('module')
['CallExpression[callee.type="Import"][arguments.length=1] > Literal']: (node) => {
_checkImport(node);
},
// import foo = ...
['TSImportEqualsDeclaration > TSExternalModuleReference > Literal']: (node) => {
_checkImport(node);
},
// export ?? from 'module'
ExportAllDeclaration: (node) => {
_checkImport(node.source);
},
// export {foo} from 'module'
ExportNamedDeclaration: (node) => {
_checkImport(node.source);
},
};
}
exports.createImportRuleListener = createImportRuleListener;

40
build/lib/eslint/utils.ts Normal file
View File

@@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. 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

@@ -30,6 +30,14 @@
"name": "vs/workbench/api/common",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/backup",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/bulkEdit",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/contrib/cli",
"project": "vscode-workbench"
@@ -309,6 +317,10 @@
{
"name": "vs/workbench/services/userData",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/services/userDataSync",
"project": "vscode-workbench"
}
]
}

209
build/lib/layersChecker.js Normal file
View File

@@ -0,0 +1,209 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const fs_1 = require("fs");
const path_1 = require("path");
const minimatch_1 = require("minimatch");
//
// #############################################################################################
//
// A custom typescript checker for the specific task of detecting the use of certain types in a
// layer that does not allow such use. For example:
// - using DOM globals in common/node/electron-main layer (e.g. HTMLElement)
// - using node.js globals in common/browser layer (e.g. process)
//
// Make changes to below RULES to lift certain files from these checks only if absolutely needed
//
// #############################################################################################
//
// Types we assume are present in all implementations of JS VMs (node.js, browsers)
// Feel free to add more core types as you see needed if present in node.js and browsers
const CORE_TYPES = [
'require',
'atob',
'btoa',
'setTimeout',
'clearTimeout',
'setInterval',
'clearInterval',
'console',
'log',
'info',
'warn',
'error',
'group',
'groupEnd',
'table',
'Error',
'String',
'throws',
'stack',
'captureStackTrace',
'stackTraceLimit',
'TextDecoder',
'TextEncoder',
'encode',
'decode',
'self',
'trimLeft',
'trimRight'
];
const RULES = [
// Tests: skip
{
target: '**/{vs,sql}/**/test/**',
skip: true // -> skip all test files
},
// Common: vs/base/common/platform.ts
{
target: '**/vs/base/common/platform.ts',
allowedTypes: [
...CORE_TYPES,
// Safe access to postMessage() and friends
'MessageEvent',
'data'
],
disallowedDefinitions: [
'lib.dom.d.ts',
'@types/node' // no node.js
]
},
// Common: vs/workbench/api/common/extHostExtensionService.ts
{
target: '**/vs/workbench/api/common/extHostExtensionService.ts',
allowedTypes: [
...CORE_TYPES,
// Safe access to global
'global'
],
disallowedDefinitions: [
'lib.dom.d.ts',
'@types/node' // no node.js
]
},
// Common
{
target: '**/{vs,sql}/**/common/**',
allowedTypes: CORE_TYPES,
disallowedDefinitions: [
'lib.dom.d.ts',
'@types/node' // no node.js
]
},
// Browser
{
target: '**/{vs,sql}/**/browser/**',
allowedTypes: CORE_TYPES,
disallowedDefinitions: [
'@types/node' // no node.js
]
},
// node.js
{
target: '**/{vs,sql}/**/node/**',
allowedTypes: [
...CORE_TYPES,
// --> types from node.d.ts that duplicate from lib.dom.d.ts
'URL',
'protocol',
'hostname',
'port',
'pathname',
'search',
'username',
'password'
],
disallowedDefinitions: [
'lib.dom.d.ts' // no DOM
]
},
// Electron (renderer): skip
{
target: '**/{vs,sql}/**/electron-browser/**',
skip: true // -> supports all types
},
// Electron (main)
{
target: '**/{vs,sql}/**/electron-main/**',
allowedTypes: [
...CORE_TYPES,
// --> types from electron.d.ts that duplicate from lib.dom.d.ts
'Event',
'Request'
],
disallowedDefinitions: [
'lib.dom.d.ts' // no DOM
]
}
];
const TS_CONFIG_PATH = path_1.join(__dirname, '../../', 'src', 'tsconfig.json');
let hasErrors = false;
function checkFile(program, sourceFile, rule) {
checkNode(sourceFile);
function checkNode(node) {
var _a;
if (node.kind !== ts.SyntaxKind.Identifier) {
return ts.forEachChild(node, checkNode); // recurse down
}
const text = node.getText(sourceFile);
if ((_a = rule.allowedTypes) === null || _a === void 0 ? void 0 : _a.some(allowed => allowed === text)) {
return; // override
}
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(node);
if (symbol) {
const declarations = symbol.declarations;
if (Array.isArray(declarations)) {
for (const declaration of declarations) {
if (declaration) {
const parent = declaration.parent;
if (parent) {
const parentSourceFile = parent.getSourceFile();
if (parentSourceFile) {
const definitionFileName = parentSourceFile.fileName;
if (rule.disallowedDefinitions) {
for (const disallowedDefinition of rule.disallowedDefinitions) {
if (definitionFileName.indexOf(disallowedDefinition) >= 0) {
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
console.log(`[build/lib/layersChecker.ts]: Reference to '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`);
hasErrors = true;
return;
}
}
}
}
}
}
}
}
}
}
}
function createProgram(tsconfigPath) {
const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
const configHostParser = { fileExists: fs_1.existsSync, readDirectory: ts.sys.readDirectory, readFile: file => fs_1.readFileSync(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' };
const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, path_1.resolve(path_1.dirname(tsconfigPath)), { noEmit: true });
const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true);
return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost);
}
//
// Create program and start checking
//
const program = createProgram(TS_CONFIG_PATH);
for (const sourceFile of program.getSourceFiles()) {
for (const rule of RULES) {
if (minimatch_1.match([sourceFile.fileName], rule.target).length > 0) {
if (!rule.skip) {
checkFile(program, sourceFile, rule);
}
break;
}
}
}
if (hasErrors) {
process.exit(1);
}

245
build/lib/layersChecker.ts Normal file
View File

@@ -0,0 +1,245 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import { readFileSync, existsSync } from 'fs';
import { resolve, dirname, join } from 'path';
import { match } from 'minimatch';
//
// #############################################################################################
//
// A custom typescript checker for the specific task of detecting the use of certain types in a
// layer that does not allow such use. For example:
// - using DOM globals in common/node/electron-main layer (e.g. HTMLElement)
// - using node.js globals in common/browser layer (e.g. process)
//
// Make changes to below RULES to lift certain files from these checks only if absolutely needed
//
// #############################################################################################
//
// Types we assume are present in all implementations of JS VMs (node.js, browsers)
// Feel free to add more core types as you see needed if present in node.js and browsers
const CORE_TYPES = [
'require', // from our AMD loader
'atob',
'btoa',
'setTimeout',
'clearTimeout',
'setInterval',
'clearInterval',
'console',
'log',
'info',
'warn',
'error',
'group',
'groupEnd',
'table',
'Error',
'String',
'throws',
'stack',
'captureStackTrace',
'stackTraceLimit',
'TextDecoder',
'TextEncoder',
'encode',
'decode',
'self',
'trimLeft',
'trimRight'
];
const RULES = [
// Tests: skip
{
target: '**/{vs,sql}/**/test/**',
skip: true // -> skip all test files
},
// Common: vs/base/common/platform.ts
{
target: '**/vs/base/common/platform.ts',
allowedTypes: [
...CORE_TYPES,
// Safe access to postMessage() and friends
'MessageEvent',
'data'
],
disallowedDefinitions: [
'lib.dom.d.ts', // no DOM
'@types/node' // no node.js
]
},
// Common: vs/workbench/api/common/extHostExtensionService.ts
{
target: '**/vs/workbench/api/common/extHostExtensionService.ts',
allowedTypes: [
...CORE_TYPES,
// Safe access to global
'global'
],
disallowedDefinitions: [
'lib.dom.d.ts', // no DOM
'@types/node' // no node.js
]
},
// Common
{
target: '**/{vs,sql}/**/common/**',
allowedTypes: CORE_TYPES,
disallowedDefinitions: [
'lib.dom.d.ts', // no DOM
'@types/node' // no node.js
]
},
// Browser
{
target: '**/{vs,sql}/**/browser/**',
allowedTypes: CORE_TYPES,
disallowedDefinitions: [
'@types/node' // no node.js
]
},
// node.js
{
target: '**/{vs,sql}/**/node/**',
allowedTypes: [
...CORE_TYPES,
// --> types from node.d.ts that duplicate from lib.dom.d.ts
'URL',
'protocol',
'hostname',
'port',
'pathname',
'search',
'username',
'password'
],
disallowedDefinitions: [
'lib.dom.d.ts' // no DOM
]
},
// Electron (renderer): skip
{
target: '**/{vs,sql}/**/electron-browser/**',
skip: true // -> supports all types
},
// Electron (main)
{
target: '**/{vs,sql}/**/electron-main/**',
allowedTypes: [
...CORE_TYPES,
// --> types from electron.d.ts that duplicate from lib.dom.d.ts
'Event',
'Request'
],
disallowedDefinitions: [
'lib.dom.d.ts' // no DOM
]
}
];
const TS_CONFIG_PATH = join(__dirname, '../../', 'src', 'tsconfig.json');
interface IRule {
target: string;
skip?: boolean;
allowedTypes?: string[];
disallowedDefinitions?: string[];
}
let hasErrors = false;
function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) {
checkNode(sourceFile);
function checkNode(node: ts.Node): void {
if (node.kind !== ts.SyntaxKind.Identifier) {
return ts.forEachChild(node, checkNode); // recurse down
}
const text = node.getText(sourceFile);
if (rule.allowedTypes?.some(allowed => allowed === text)) {
return; // override
}
const checker = program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(node);
if (symbol) {
const declarations = symbol.declarations;
if (Array.isArray(declarations)) {
for (const declaration of declarations) {
if (declaration) {
const parent = declaration.parent;
if (parent) {
const parentSourceFile = parent.getSourceFile();
if (parentSourceFile) {
const definitionFileName = parentSourceFile.fileName;
if (rule.disallowedDefinitions) {
for (const disallowedDefinition of rule.disallowedDefinitions) {
if (definitionFileName.indexOf(disallowedDefinition) >= 0) {
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
console.log(`[build/lib/layersChecker.ts]: Reference to '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`);
hasErrors = true;
return;
}
}
}
}
}
}
}
}
}
}
}
function createProgram(tsconfigPath: string): ts.Program {
const tsConfig = ts.readConfigFile(tsconfigPath, ts.sys.readFile);
const configHostParser: ts.ParseConfigHost = { fileExists: existsSync, readDirectory: ts.sys.readDirectory, readFile: file => readFileSync(file, 'utf8'), useCaseSensitiveFileNames: process.platform === 'linux' };
const tsConfigParsed = ts.parseJsonConfigFileContent(tsConfig.config, configHostParser, resolve(dirname(tsconfigPath)), { noEmit: true });
const compilerHost = ts.createCompilerHost(tsConfigParsed.options, true);
return ts.createProgram(tsConfigParsed.fileNames, tsConfigParsed.options, compilerHost);
}
//
// Create program and start checking
//
const program = createProgram(TS_CONFIG_PATH);
for (const sourceFile of program.getSourceFiles()) {
for (const rule of RULES) {
if (match([sourceFile.fileName], rule.target).length > 0) {
if (!rule.skip) {
checkFile(program, sourceFile, rule);
}
break;
}
}
}
if (hasErrors) {
process.exit(1);
}

View File

@@ -1,45 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const Lint = require("tslint");
class AbstractGlobalsRuleWalker extends Lint.RuleWalker {
constructor(file, program, opts, _config) {
super(file, opts);
this.program = program;
this._config = _config;
}
visitIdentifier(node) {
if (this.getDisallowedGlobals().some(disallowedGlobal => disallowedGlobal === node.text)) {
if (this._config.allowed && this._config.allowed.some(allowed => allowed === node.text)) {
return; // override
}
const checker = this.program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(node);
if (symbol) {
const declarations = symbol.declarations;
if (Array.isArray(declarations) && symbol.declarations.some(declaration => {
if (declaration) {
const parent = declaration.parent;
if (parent) {
const sourceFile = parent.getSourceFile();
if (sourceFile) {
const fileName = sourceFile.fileName;
if (fileName && fileName.indexOf(this.getDefinitionPattern()) >= 0) {
return true;
}
}
}
}
return false;
})) {
this.addFailureAtNode(node, `Cannot use global '${node.text}' in '${this._config.target}'`);
}
}
}
super.visitIdentifier(node);
}
}
exports.AbstractGlobalsRuleWalker = AbstractGlobalsRuleWalker;

View File

@@ -1,57 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
interface AbstractGlobalsRuleConfig {
target: string;
allowed: string[];
}
export abstract class AbstractGlobalsRuleWalker extends Lint.RuleWalker {
constructor(file: ts.SourceFile, private program: ts.Program, opts: Lint.IOptions, private _config: AbstractGlobalsRuleConfig) {
super(file, opts);
}
protected abstract getDisallowedGlobals(): string[];
protected abstract getDefinitionPattern(): string;
visitIdentifier(node: ts.Identifier) {
if (this.getDisallowedGlobals().some(disallowedGlobal => disallowedGlobal === node.text)) {
if (this._config.allowed && this._config.allowed.some(allowed => allowed === node.text)) {
return; // override
}
const checker = this.program.getTypeChecker();
const symbol = checker.getSymbolAtLocation(node);
if (symbol) {
const declarations = symbol.declarations;
if (Array.isArray(declarations) && symbol.declarations.some(declaration => {
if (declaration) {
const parent = declaration.parent;
if (parent) {
const sourceFile = parent.getSourceFile();
if (sourceFile) {
const fileName = sourceFile.fileName;
if (fileName && fileName.indexOf(this.getDefinitionPattern()) >= 0) {
return true;
}
}
}
}
return false;
})) {
this.addFailureAtNode(node, `Cannot use global '${node.text}' in '${this._config.target}'`);
}
}
}
super.visitIdentifier(node);
}
}

View File

@@ -1,58 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const Lint = require("tslint");
/**
* Implementation of the double-quoted-string-arg rule which verifies that the specified index of calls matching
* the specified signatures is quoted with double-quotes only.
*/
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
return this.applyWithWalker(new DoubleQuotedStringArgRuleWalker(sourceFile, this.getOptions()));
}
}
exports.Rule = Rule;
class DoubleQuotedStringArgRuleWalker extends Lint.RuleWalker {
constructor(file, opts) {
super(file, opts);
this.signatures = Object.create(null);
this.argIndex = undefined;
const options = this.getOptions();
const first = options && options.length > 0 ? options[0] : null;
if (first) {
if (Array.isArray(first.signatures)) {
first.signatures.forEach((signature) => this.signatures[signature] = true);
}
if (typeof first.argIndex !== 'undefined') {
this.argIndex = first.argIndex;
}
}
}
visitCallExpression(node) {
this.checkCallExpression(node);
super.visitCallExpression(node);
}
checkCallExpression(node) {
// Not one of the functions we're looking for, continue on
const functionName = node.expression.getText();
if (functionName && !this.signatures[functionName]) {
return;
}
const arg = node.arguments[this.argIndex];
// Ignore if the arg isn't a string - we expect the compiler to warn if that's an issue
if (arg && ts.isStringLiteral(arg)) {
const argText = arg.getText();
const doubleQuotedArg = argText.length >= 2 && argText[0] === DoubleQuotedStringArgRuleWalker.DOUBLE_QUOTE && argText[argText.length - 1] === DoubleQuotedStringArgRuleWalker.DOUBLE_QUOTE;
if (!doubleQuotedArg) {
const fix = Lint.Replacement.replaceFromTo(arg.getStart(), arg.getEnd(), `"${arg.getText().slice(1, arg.getWidth() - 1)}"`);
this.addFailure(this.createFailure(arg.getStart(), arg.getWidth(), `Argument ${this.argIndex + 1} to '${functionName}' must be double quoted.`, fix));
return;
}
}
}
}
DoubleQuotedStringArgRuleWalker.DOUBLE_QUOTE = '"';

View File

@@ -1,80 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
/**
* Implementation of the double-quoted-string-arg rule which verifies that the specified index of calls matching
* the specified signatures is quoted with double-quotes only.
*/
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new DoubleQuotedStringArgRuleWalker(sourceFile, this.getOptions()));
}
}
interface Map<V> {
[key: string]: V;
}
interface DoubleQuotedStringArgOptions {
signatures?: string[];
argIndex?: number;
}
class DoubleQuotedStringArgRuleWalker extends Lint.RuleWalker {
private static DOUBLE_QUOTE: string = '"';
private signatures: Map<boolean>;
private argIndex: number | undefined;
constructor(file: ts.SourceFile, opts: Lint.IOptions) {
super(file, opts);
this.signatures = Object.create(null);
this.argIndex = undefined;
const options: any[] = this.getOptions();
const first: DoubleQuotedStringArgOptions = options && options.length > 0 ? options[0] : null;
if (first) {
if (Array.isArray(first.signatures)) {
first.signatures.forEach((signature: string) => this.signatures[signature] = true);
}
if (typeof first.argIndex !== 'undefined') {
this.argIndex = first.argIndex;
}
}
}
protected visitCallExpression(node: ts.CallExpression): void {
this.checkCallExpression(node);
super.visitCallExpression(node);
}
private checkCallExpression(node: ts.CallExpression): void {
// Not one of the functions we're looking for, continue on
const functionName = node.expression.getText();
if (functionName && !this.signatures[functionName]) {
return;
}
const arg = node.arguments[this.argIndex!];
// Ignore if the arg isn't a string - we expect the compiler to warn if that's an issue
if(arg && ts.isStringLiteral(arg)) {
const argText = arg.getText();
const doubleQuotedArg = argText.length >= 2 && argText[0] === DoubleQuotedStringArgRuleWalker.DOUBLE_QUOTE && argText[argText.length - 1] === DoubleQuotedStringArgRuleWalker.DOUBLE_QUOTE;
if (!doubleQuotedArg) {
const fix = Lint.Replacement.replaceFromTo(arg.getStart(), arg.getEnd(), `"${arg.getText().slice(1, arg.getWidth() - 1)}"`);
this.addFailure(this.createFailure(
arg.getStart(), arg.getWidth(),
`Argument ${this.argIndex! + 1} to '${functionName}' must be double quoted.`, fix));
return;
}
}
}
}

View File

@@ -1,32 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path");
const Lint = require("tslint");
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
return this.applyWithWalker(new ImportPatterns(sourceFile, this.getOptions()));
}
}
exports.Rule = Rule;
class ImportPatterns extends Lint.RuleWalker {
constructor(file, opts) {
super(file, opts);
this.imports = Object.create(null);
}
visitImportDeclaration(node) {
let path = node.moduleSpecifier.getText();
// remove quotes
path = path.slice(1, -1);
if (path[0] === '.') {
path = path_1.join(path_1.dirname(node.getSourceFile().fileName), path);
}
if (this.imports[path]) {
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), `Duplicate imports for '${path}'.`));
}
this.imports[path] = true;
}
}

View File

@@ -1,40 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import { join, dirname } from 'path';
import * as Lint from 'tslint';
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new ImportPatterns(sourceFile, this.getOptions()));
}
}
class ImportPatterns extends Lint.RuleWalker {
private imports: { [path: string]: boolean; } = Object.create(null);
constructor(file: ts.SourceFile, opts: Lint.IOptions) {
super(file, opts);
}
protected visitImportDeclaration(node: ts.ImportDeclaration): void {
let path = node.moduleSpecifier.getText();
// remove quotes
path = path.slice(1, -1);
if (path[0] === '.') {
path = join(dirname(node.getSourceFile().fileName), path);
}
if (this.imports[path]) {
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), `Duplicate imports for '${path}'.`));
}
this.imports[path] = true;
}
}

View File

@@ -1,70 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const Lint = require("tslint");
const minimatch = require("minimatch");
const path_1 = require("path");
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
const configs = this.getOptions().ruleArguments;
for (const config of configs) {
if (minimatch(sourceFile.fileName, config.target)) {
return this.applyWithWalker(new ImportPatterns(sourceFile, this.getOptions(), config));
}
}
return [];
}
}
exports.Rule = Rule;
class ImportPatterns extends Lint.RuleWalker {
constructor(file, opts, _config) {
super(file, opts);
this._config = _config;
}
visitImportEqualsDeclaration(node) {
if (node.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) {
this._validateImport(node.moduleReference.expression.getText(), node);
}
}
visitImportDeclaration(node) {
this._validateImport(node.moduleSpecifier.getText(), node);
}
visitCallExpression(node) {
super.visitCallExpression(node);
// import('foo') statements inside the code
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
const [path] = node.arguments;
this._validateImport(path.getText(), node);
}
}
_validateImport(path, node) {
// remove quotes
path = path.slice(1, -1);
// resolve relative paths
if (path[0] === '.') {
path = path_1.join(this.getSourceFile().fileName, path);
}
let restrictions;
if (typeof this._config.restrictions === 'string') {
restrictions = [this._config.restrictions];
}
else {
restrictions = this._config.restrictions;
}
let matched = false;
for (const pattern of restrictions) {
if (minimatch(path, pattern)) {
matched = true;
break;
}
}
if (!matched) {
// None of the restrictions matched
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), `Imports violates '${restrictions.join(' or ')}' restrictions. See https://github.com/Microsoft/vscode/wiki/Code-Organization`));
}
}
}

View File

@@ -1,87 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
import * as minimatch from 'minimatch';
import { join } from 'path';
interface ImportPatternsConfig {
target: string;
restrictions: string | string[];
}
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
const configs = <ImportPatternsConfig[]>this.getOptions().ruleArguments;
for (const config of configs) {
if (minimatch(sourceFile.fileName, config.target)) {
return this.applyWithWalker(new ImportPatterns(sourceFile, this.getOptions(), config));
}
}
return [];
}
}
class ImportPatterns extends Lint.RuleWalker {
constructor(file: ts.SourceFile, opts: Lint.IOptions, private _config: ImportPatternsConfig) {
super(file, opts);
}
protected visitImportEqualsDeclaration(node: ts.ImportEqualsDeclaration): void {
if (node.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) {
this._validateImport(node.moduleReference.expression.getText(), node);
}
}
protected visitImportDeclaration(node: ts.ImportDeclaration): void {
this._validateImport(node.moduleSpecifier.getText(), node);
}
protected visitCallExpression(node: ts.CallExpression): void {
super.visitCallExpression(node);
// import('foo') statements inside the code
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
const [path] = node.arguments;
this._validateImport(path.getText(), node);
}
}
private _validateImport(path: string, node: ts.Node): void {
// remove quotes
path = path.slice(1, -1);
// resolve relative paths
if (path[0] === '.') {
path = join(this.getSourceFile().fileName, path);
}
let restrictions: string[];
if (typeof this._config.restrictions === 'string') {
restrictions = [this._config.restrictions];
} else {
restrictions = this._config.restrictions;
}
let matched = false;
for (const pattern of restrictions) {
if (minimatch(path, pattern)) {
matched = true;
break;
}
}
if (!matched) {
// None of the restrictions matched
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), `Imports violates '${restrictions.join(' or ')}' restrictions. See https://github.com/Microsoft/vscode/wiki/Code-Organization`));
}
}
}

View File

@@ -1,83 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const Lint = require("tslint");
const path_1 = require("path");
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
const parts = path_1.dirname(sourceFile.fileName).split(/\\|\//);
const ruleArgs = this.getOptions().ruleArguments[0];
let config;
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) {
return [];
}
return this.applyWithWalker(new LayeringRule(sourceFile, config, this.getOptions()));
}
}
exports.Rule = Rule;
class LayeringRule extends Lint.RuleWalker {
constructor(file, config, opts) {
super(file, opts);
this._config = config;
}
visitImportEqualsDeclaration(node) {
if (node.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) {
this._validateImport(node.moduleReference.expression.getText(), node);
}
}
visitImportDeclaration(node) {
this._validateImport(node.moduleSpecifier.getText(), node);
}
visitCallExpression(node) {
super.visitCallExpression(node);
// import('foo') statements inside the code
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
const [path] = node.arguments;
this._validateImport(path.getText(), node);
}
}
_validateImport(path, node) {
// remove quotes
path = path.slice(1, -1);
if (path[0] === '.') {
path = path_1.join(path_1.dirname(node.getSourceFile().fileName), path);
}
const parts = path_1.dirname(path).split(/\\|\//);
for (let i = parts.length - 1; i >= 0; i--) {
const part = parts[i];
if (this._config.allowed.has(part)) {
// GOOD - same layer
return;
}
if (this._config.disallowed.has(part)) {
// BAD - wrong layer
const message = `Bad layering. You are not allowed to access '${part}' from here, allowed layers are: [${LayeringRule._print(this._config.allowed)}]`;
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), message));
return;
}
}
}
static _print(set) {
const r = [];
set.forEach(e => r.push(e));
return r.join(', ');
}
}

View File

@@ -1,105 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
import { join, dirname } from 'path';
interface Config {
allowed: Set<string>;
disallowed: Set<string>;
}
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
const parts = dirname(sourceFile.fileName).split(/\\|\//);
const ruleArgs = this.getOptions().ruleArguments[0];
let config: Config | undefined;
for (let i = parts.length - 1; i >= 0; i--) {
if (ruleArgs[parts[i]]) {
config = {
allowed: new Set<string>(<string[]>ruleArgs[parts[i]]).add(parts[i]),
disallowed: new Set<string>()
};
Object.keys(ruleArgs).forEach(key => {
if (!config!.allowed.has(key)) {
config!.disallowed.add(key);
}
});
break;
}
}
if (!config) {
return [];
}
return this.applyWithWalker(new LayeringRule(sourceFile, config, this.getOptions()));
}
}
class LayeringRule extends Lint.RuleWalker {
private _config: Config;
constructor(file: ts.SourceFile, config: Config, opts: Lint.IOptions) {
super(file, opts);
this._config = config;
}
protected visitImportEqualsDeclaration(node: ts.ImportEqualsDeclaration): void {
if (node.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) {
this._validateImport(node.moduleReference.expression.getText(), node);
}
}
protected visitImportDeclaration(node: ts.ImportDeclaration): void {
this._validateImport(node.moduleSpecifier.getText(), node);
}
protected visitCallExpression(node: ts.CallExpression): void {
super.visitCallExpression(node);
// import('foo') statements inside the code
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
const [path] = node.arguments;
this._validateImport(path.getText(), node);
}
}
private _validateImport(path: string, node: ts.Node): void {
// remove quotes
path = path.slice(1, -1);
if (path[0] === '.') {
path = join(dirname(node.getSourceFile().fileName), path);
}
const parts = dirname(path).split(/\\|\//);
for (let i = parts.length - 1; i >= 0; i--) {
const part = parts[i];
if (this._config.allowed.has(part)) {
// GOOD - same layer
return;
}
if (this._config.disallowed.has(part)) {
// BAD - wrong layer
const message = `Bad layering. You are not allowed to access '${part}' from here, allowed layers are: [${LayeringRule._print(this._config.allowed)}]`;
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), message));
return;
}
}
}
static _print(set: Set<string>): string {
const r: string[] = [];
set.forEach(e => r.push(e));
return r.join(', ');
}
}

View File

@@ -1,34 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const Lint = require("tslint");
const minimatch = require("minimatch");
const abstractGlobalsRule_1 = require("./abstractGlobalsRule");
class Rule extends Lint.Rules.TypedRule {
applyWithProgram(sourceFile, program) {
const configs = this.getOptions().ruleArguments;
for (const config of configs) {
if (minimatch(sourceFile.fileName, config.target)) {
return this.applyWithWalker(new NoDOMGlobalsRuleWalker(sourceFile, program, this.getOptions(), config));
}
}
return [];
}
}
exports.Rule = Rule;
class NoDOMGlobalsRuleWalker extends abstractGlobalsRule_1.AbstractGlobalsRuleWalker {
getDefinitionPattern() {
return 'lib.dom.d.ts';
}
getDisallowedGlobals() {
// intentionally not complete
return [
"window",
"document",
"HTMLElement"
];
}
}

View File

@@ -1,45 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
import * as minimatch from 'minimatch';
import { AbstractGlobalsRuleWalker } from './abstractGlobalsRule';
interface NoDOMGlobalsRuleConfig {
target: string;
allowed: string[];
}
export class Rule extends Lint.Rules.TypedRule {
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
const configs = <NoDOMGlobalsRuleConfig[]>this.getOptions().ruleArguments;
for (const config of configs) {
if (minimatch(sourceFile.fileName, config.target)) {
return this.applyWithWalker(new NoDOMGlobalsRuleWalker(sourceFile, program, this.getOptions(), config));
}
}
return [];
}
}
class NoDOMGlobalsRuleWalker extends AbstractGlobalsRuleWalker {
getDefinitionPattern(): string {
return 'lib.dom.d.ts';
}
getDisallowedGlobals(): string[] {
// intentionally not complete
return [
"window",
"document",
"HTMLElement"
];
}
}

View File

@@ -1,22 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const Lint = require("tslint");
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
return this.applyWithWalker(new NewBufferRuleWalker(sourceFile, this.getOptions()));
}
}
exports.Rule = Rule;
class NewBufferRuleWalker extends Lint.RuleWalker {
visitNewExpression(node) {
if (node.expression.kind === ts.SyntaxKind.Identifier && node.expression && node.expression.text === 'Buffer') {
this.addFailureAtNode(node, '`new Buffer` is deprecated. Consider Buffer.From or Buffer.alloc instead.');
}
super.visitNewExpression(node);
}
}

View File

@@ -1,23 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
export class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new NewBufferRuleWalker(sourceFile, this.getOptions()));
}
}
class NewBufferRuleWalker extends Lint.RuleWalker {
visitNewExpression(node: ts.NewExpression) {
if (node.expression.kind === ts.SyntaxKind.Identifier && node.expression && (node.expression as ts.Identifier).text === 'Buffer') {
this.addFailureAtNode(node, '`new Buffer` is deprecated. Consider Buffer.From or Buffer.alloc instead.');
}
super.visitNewExpression(node);
}
}

View File

@@ -1,54 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const Lint = require("tslint");
const path_1 = require("path");
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
if (/vs(\/|\\)editor(\/|\\)standalone(\/|\\)/.test(sourceFile.fileName)
|| /vs(\/|\\)editor(\/|\\)common(\/|\\)standalone(\/|\\)/.test(sourceFile.fileName)
|| /vs(\/|\\)editor(\/|\\)editor.api/.test(sourceFile.fileName)
|| /vs(\/|\\)editor(\/|\\)editor.main/.test(sourceFile.fileName)
|| /vs(\/|\\)editor(\/|\\)editor.worker/.test(sourceFile.fileName)) {
return this.applyWithWalker(new NoNlsInStandaloneEditorRuleWalker(sourceFile, this.getOptions()));
}
return [];
}
}
exports.Rule = Rule;
class NoNlsInStandaloneEditorRuleWalker extends Lint.RuleWalker {
constructor(file, opts) {
super(file, opts);
}
visitImportEqualsDeclaration(node) {
if (node.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) {
this._validateImport(node.moduleReference.expression.getText(), node);
}
}
visitImportDeclaration(node) {
this._validateImport(node.moduleSpecifier.getText(), node);
}
visitCallExpression(node) {
super.visitCallExpression(node);
// import('foo') statements inside the code
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
const [path] = node.arguments;
this._validateImport(path.getText(), node);
}
}
_validateImport(path, node) {
// remove quotes
path = path.slice(1, -1);
// resolve relative paths
if (path[0] === '.') {
path = path_1.join(this.getSourceFile().fileName, path);
}
if (/vs(\/|\\)nls/.test(path)) {
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), `Not allowed to import vs/nls in standalone editor modules. Use standaloneStrings.ts`));
}
}
}

View File

@@ -1,67 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
import { join } from 'path';
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (
/vs(\/|\\)editor(\/|\\)standalone(\/|\\)/.test(sourceFile.fileName)
|| /vs(\/|\\)editor(\/|\\)common(\/|\\)standalone(\/|\\)/.test(sourceFile.fileName)
|| /vs(\/|\\)editor(\/|\\)editor.api/.test(sourceFile.fileName)
|| /vs(\/|\\)editor(\/|\\)editor.main/.test(sourceFile.fileName)
|| /vs(\/|\\)editor(\/|\\)editor.worker/.test(sourceFile.fileName)
) {
return this.applyWithWalker(new NoNlsInStandaloneEditorRuleWalker(sourceFile, this.getOptions()));
}
return [];
}
}
class NoNlsInStandaloneEditorRuleWalker extends Lint.RuleWalker {
constructor(file: ts.SourceFile, opts: Lint.IOptions) {
super(file, opts);
}
protected visitImportEqualsDeclaration(node: ts.ImportEqualsDeclaration): void {
if (node.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) {
this._validateImport(node.moduleReference.expression.getText(), node);
}
}
protected visitImportDeclaration(node: ts.ImportDeclaration): void {
this._validateImport(node.moduleSpecifier.getText(), node);
}
protected visitCallExpression(node: ts.CallExpression): void {
super.visitCallExpression(node);
// import('foo') statements inside the code
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
const [path] = node.arguments;
this._validateImport(path.getText(), node);
}
}
private _validateImport(path: string, node: ts.Node): void {
// remove quotes
path = path.slice(1, -1);
// resolve relative paths
if (path[0] === '.') {
path = join(this.getSourceFile().fileName, path);
}
if (
/vs(\/|\\)nls/.test(path)
) {
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), `Not allowed to import vs/nls in standalone editor modules. Use standaloneStrings.ts`));
}
}
}

View File

@@ -1,41 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const Lint = require("tslint");
const minimatch = require("minimatch");
const abstractGlobalsRule_1 = require("./abstractGlobalsRule");
class Rule extends Lint.Rules.TypedRule {
applyWithProgram(sourceFile, program) {
const configs = this.getOptions().ruleArguments;
for (const config of configs) {
if (minimatch(sourceFile.fileName, config.target)) {
return this.applyWithWalker(new NoNodejsGlobalsRuleWalker(sourceFile, program, this.getOptions(), config));
}
}
return [];
}
}
exports.Rule = Rule;
class NoNodejsGlobalsRuleWalker extends abstractGlobalsRule_1.AbstractGlobalsRuleWalker {
getDefinitionPattern() {
return '@types/node';
}
getDisallowedGlobals() {
// https://nodejs.org/api/globals.html#globals_global_objects
return [
"NodeJS",
"Buffer",
"__dirname",
"__filename",
"clearImmediate",
"exports",
"global",
"module",
"process",
"setImmediate"
];
}
}

View File

@@ -1,52 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
import * as minimatch from 'minimatch';
import { AbstractGlobalsRuleWalker } from './abstractGlobalsRule';
interface NoNodejsGlobalsConfig {
target: string;
allowed: string[];
}
export class Rule extends Lint.Rules.TypedRule {
applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
const configs = <NoNodejsGlobalsConfig[]>this.getOptions().ruleArguments;
for (const config of configs) {
if (minimatch(sourceFile.fileName, config.target)) {
return this.applyWithWalker(new NoNodejsGlobalsRuleWalker(sourceFile, program, this.getOptions(), config));
}
}
return [];
}
}
class NoNodejsGlobalsRuleWalker extends AbstractGlobalsRuleWalker {
getDefinitionPattern(): string {
return '@types/node';
}
getDisallowedGlobals(): string[] {
// https://nodejs.org/api/globals.html#globals_global_objects
return [
"NodeJS",
"Buffer",
"__dirname",
"__filename",
"clearImmediate",
"exports",
"global",
"module",
"process",
"setImmediate"
];
}
}

View File

@@ -1,57 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const Lint = require("tslint");
const path_1 = require("path");
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
if (/vs(\/|\\)editor/.test(sourceFile.fileName)) {
// the vs/editor folder is allowed to use the standalone editor
return [];
}
return this.applyWithWalker(new NoStandaloneEditorRuleWalker(sourceFile, this.getOptions()));
}
}
exports.Rule = Rule;
class NoStandaloneEditorRuleWalker extends Lint.RuleWalker {
constructor(file, opts) {
super(file, opts);
}
visitImportEqualsDeclaration(node) {
if (node.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) {
this._validateImport(node.moduleReference.expression.getText(), node);
}
}
visitImportDeclaration(node) {
this._validateImport(node.moduleSpecifier.getText(), node);
}
visitCallExpression(node) {
super.visitCallExpression(node);
// import('foo') statements inside the code
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
const [path] = node.arguments;
this._validateImport(path.getText(), node);
}
}
// {{SQL CARBON EDIT}} - Rename node argument to _node to prevent errors since it is not used
_validateImport(path, _node) {
// remove quotes
path = path.slice(1, -1);
// resolve relative paths
if (path[0] === '.') {
path = path_1.join(this.getSourceFile().fileName, 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)) {
// {{SQL CARBON EDIT}}
//this.addFailure(this.createFailure(node.getStart(), node.getWidth(), `Not allowed to import standalone editor modules. See https://github.com/Microsoft/vscode/wiki/Code-Organization`));
}
}
}

View File

@@ -1,67 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
import { join } from 'path';
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (/vs(\/|\\)editor/.test(sourceFile.fileName)) {
// the vs/editor folder is allowed to use the standalone editor
return [];
}
return this.applyWithWalker(new NoStandaloneEditorRuleWalker(sourceFile, this.getOptions()));
}
}
class NoStandaloneEditorRuleWalker extends Lint.RuleWalker {
constructor(file: ts.SourceFile, opts: Lint.IOptions) {
super(file, opts);
}
protected visitImportEqualsDeclaration(node: ts.ImportEqualsDeclaration): void {
if (node.moduleReference.kind === ts.SyntaxKind.ExternalModuleReference) {
this._validateImport(node.moduleReference.expression.getText(), node);
}
}
protected visitImportDeclaration(node: ts.ImportDeclaration): void {
this._validateImport(node.moduleSpecifier.getText(), node);
}
protected visitCallExpression(node: ts.CallExpression): void {
super.visitCallExpression(node);
// import('foo') statements inside the code
if (node.expression.kind === ts.SyntaxKind.ImportKeyword) {
const [path] = node.arguments;
this._validateImport(path.getText(), node);
}
}
// {{SQL CARBON EDIT}} - Rename node argument to _node to prevent errors since it is not used
private _validateImport(path: string, _node: ts.Node): void {
// remove quotes
path = path.slice(1, -1);
// resolve relative paths
if (path[0] === '.') {
path = join(this.getSourceFile().fileName, 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)
) {
// {{SQL CARBON EDIT}}
//this.addFailure(this.createFailure(node.getStart(), node.getWidth(), `Not allowed to import standalone editor modules. See https://github.com/Microsoft/vscode/wiki/Code-Organization`));
}
}
}

View File

@@ -1,33 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const Lint = require("tslint");
const minimatch = require("minimatch");
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
const args = this.getOptions().ruleArguments[0];
if (args.exclude.every(x => !minimatch(sourceFile.fileName, x))) {
return this.applyWithWalker(new NoSyncRuleWalker(sourceFile, this.getOptions()));
}
return [];
}
}
exports.Rule = Rule;
class NoSyncRuleWalker extends Lint.RuleWalker {
constructor(file, opts) {
super(file, opts);
}
visitCallExpression(node) {
if (node.expression && NoSyncRuleWalker.operations.some(x => node.expression.getText().indexOf(x) >= 0)) {
this.addFailureAtNode(node, `Do not use Sync operations`);
}
super.visitCallExpression(node);
}
}
NoSyncRuleWalker.operations = ['readFileSync', 'writeFileSync', 'existsSync', 'fchmodSync', 'lchmodSync',
'statSync', 'fstatSync', 'lstatSync', 'linkSync', 'symlinkSync', 'readlinkSync', 'realpathSync', 'unlinkSync', 'rmdirSync',
'mkdirSync', 'mkdtempSync', 'readdirSync', 'openSync', 'utimesSync', 'futimesSync', 'fsyncSync', 'writeSync', 'readSync',
'appendFileSync', 'accessSync', 'fdatasyncSync', 'copyFileSync'];

View File

@@ -1,45 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
import * as minimatch from 'minimatch';
interface NoSyncRuleConfig {
exclude: string[];
}
export class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
const args = <NoSyncRuleConfig>this.getOptions().ruleArguments[0];
if (args.exclude.every(x => !minimatch(sourceFile.fileName, x))) {
return this.applyWithWalker(new NoSyncRuleWalker(sourceFile, this.getOptions()));
}
return [];
}
}
class NoSyncRuleWalker extends Lint.RuleWalker {
private static readonly operations = ['readFileSync', 'writeFileSync', 'existsSync', 'fchmodSync', 'lchmodSync',
'statSync', 'fstatSync', 'lstatSync', 'linkSync', 'symlinkSync', 'readlinkSync', 'realpathSync', 'unlinkSync', 'rmdirSync',
'mkdirSync', 'mkdtempSync', 'readdirSync', 'openSync', 'utimesSync', 'futimesSync', 'fsyncSync', 'writeSync', 'readSync',
'appendFileSync', 'accessSync', 'fdatasyncSync', 'copyFileSync'];
constructor(file: ts.SourceFile, opts: Lint.IOptions) {
super(file, opts);
}
visitCallExpression(node: ts.CallExpression) {
if (node.expression && NoSyncRuleWalker.operations.some(x => node.expression.getText().indexOf(x) >= 0)) {
this.addFailureAtNode(node, `Do not use Sync operations`);
}
super.visitCallExpression(node);
}
}

View File

@@ -1,49 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const Lint = require("tslint");
/**
* Implementation of the no-localize-keys-with-underscore rule which verifies that keys to the localize
* calls don't contain underscores (_) since those break the localization process.
*/
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
return this.applyWithWalker(new NoLocalizeKeysWithUnderscore(sourceFile, this.getOptions()));
}
}
exports.Rule = Rule;
const signatures = [
"localize",
"nls.localize"
];
class NoLocalizeKeysWithUnderscore extends Lint.RuleWalker {
constructor(file, opts) {
super(file, opts);
}
visitCallExpression(node) {
this.checkCallExpression(node);
super.visitCallExpression(node);
}
checkCallExpression(node) {
// If this isn't one of the localize functions then continue on
const functionName = node.expression.getText();
if (functionName && !signatures.some(s => s === functionName)) {
return;
}
const arg = node && node.arguments && node.arguments.length > 0 ? node.arguments[0] : undefined; // The key is the first element
// Ignore if the arg isn't a string - we expect the compiler to warn if that's an issue
if (arg && ts.isStringLiteral(arg)) {
if (arg.getText().indexOf('_') >= 0) {
const fix = [
Lint.Replacement.replaceFromTo(arg.getStart(), arg.getEnd(), `${arg.getText().replace(/_/g, '.')}`),
];
this.addFailure(this.createFailure(arg.getStart(), arg.getWidth(), `Keys for localize calls must not contain underscores. Use periods (.) instead.`, fix));
return;
}
}
}
}

View File

@@ -1,55 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
/**
* Implementation of the no-localize-keys-with-underscore rule which verifies that keys to the localize
* calls don't contain underscores (_) since those break the localization process.
*/
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new NoLocalizeKeysWithUnderscore(sourceFile, this.getOptions()));
}
}
const signatures = [
"localize",
"nls.localize"
];
class NoLocalizeKeysWithUnderscore extends Lint.RuleWalker {
constructor(file: ts.SourceFile, opts: Lint.IOptions) {
super(file, opts);
}
protected visitCallExpression(node: ts.CallExpression): void {
this.checkCallExpression(node);
super.visitCallExpression(node);
}
private checkCallExpression(node: ts.CallExpression): void {
// If this isn't one of the localize functions then continue on
const functionName = node.expression.getText();
if (functionName && !signatures.some(s => s === functionName)) {
return;
}
const arg = node && node.arguments && node.arguments.length > 0 ? node.arguments[0] : undefined; // The key is the first element
// Ignore if the arg isn't a string - we expect the compiler to warn if that's an issue
if(arg && ts.isStringLiteral(arg)) {
if (arg.getText().indexOf('_') >= 0) {
const fix = [
Lint.Replacement.replaceFromTo(arg.getStart(), arg.getEnd(), `${arg.getText().replace(/_/g, '.')}`),
];
this.addFailure(this.createFailure(
arg.getStart(), arg.getWidth(),
`Keys for localize calls must not contain underscores. Use periods (.) instead.`, fix));
return;
}
}
}
}

View File

@@ -1,183 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const ts = require("typescript");
const Lint = require("tslint");
/**
* Implementation of the no-unexternalized-strings rule.
*/
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
if (/\.d.ts$/.test(sourceFile.fileName)) {
return [];
}
return this.applyWithWalker(new NoUnexternalizedStringsRuleWalker(sourceFile, this.getOptions()));
}
}
exports.Rule = Rule;
function isStringLiteral(node) {
return node && node.kind === ts.SyntaxKind.StringLiteral;
}
function isObjectLiteral(node) {
return node && node.kind === ts.SyntaxKind.ObjectLiteralExpression;
}
function isPropertyAssignment(node) {
return node && node.kind === ts.SyntaxKind.PropertyAssignment;
}
class NoUnexternalizedStringsRuleWalker extends Lint.RuleWalker {
constructor(file, opts) {
super(file, opts);
this.signatures = Object.create(null);
this.ignores = Object.create(null);
this.messageIndex = undefined;
this.keyIndex = undefined;
this.usedKeys = Object.create(null);
const options = this.getOptions();
const first = options && options.length > 0 ? options[0] : null;
if (first) {
if (Array.isArray(first.signatures)) {
first.signatures.forEach((signature) => this.signatures[signature] = true);
}
if (Array.isArray(first.ignores)) {
first.ignores.forEach((ignore) => this.ignores[ignore] = true);
}
if (typeof first.messageIndex !== 'undefined') {
this.messageIndex = first.messageIndex;
}
if (typeof first.keyIndex !== 'undefined') {
this.keyIndex = first.keyIndex;
}
}
}
visitSourceFile(node) {
super.visitSourceFile(node);
Object.keys(this.usedKeys).forEach(key => {
// Keys are quoted.
let identifier = key.substr(1, key.length - 2);
if (!NoUnexternalizedStringsRuleWalker.IDENTIFIER.test(identifier)) {
let occurrence = this.usedKeys[key][0];
this.addFailure(this.createFailure(occurrence.key.getStart(), occurrence.key.getWidth(), `The key ${occurrence.key.getText()} doesn't conform to a valid localize identifier`));
}
const occurrences = this.usedKeys[key];
if (occurrences.length > 1) {
occurrences.forEach(occurrence => {
this.addFailure((this.createFailure(occurrence.key.getStart(), occurrence.key.getWidth(), `Duplicate key ${occurrence.key.getText()} with different message value.`)));
});
}
});
}
visitStringLiteral(node) {
this.checkStringLiteral(node);
super.visitStringLiteral(node);
}
checkStringLiteral(node) {
const text = node.getText();
const doubleQuoted = text.length >= 2 && text[0] === NoUnexternalizedStringsRuleWalker.DOUBLE_QUOTE && text[text.length - 1] === NoUnexternalizedStringsRuleWalker.DOUBLE_QUOTE;
const info = this.findDescribingParent(node);
// Ignore strings in import and export nodes.
if (info && info.isImport && doubleQuoted) {
const fix = [
Lint.Replacement.replaceFromTo(node.getStart(), 1, '\''),
Lint.Replacement.replaceFromTo(node.getStart() + text.length - 1, 1, '\''),
];
this.addFailureAtNode(node, NoUnexternalizedStringsRuleWalker.ImportFailureMessage, fix);
return;
}
const callInfo = info ? info.callInfo : null;
const functionName = callInfo ? callInfo.callExpression.expression.getText() : null;
if (functionName && this.ignores[functionName]) {
return;
}
if (doubleQuoted && (!callInfo || callInfo.argIndex === -1 || !this.signatures[functionName])) {
const s = node.getText();
const fix = [
Lint.Replacement.replaceFromTo(node.getStart(), node.getWidth(), `nls.localize('KEY-${s.substring(1, s.length - 1)}', ${s})`),
];
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), `Unexternalized string found: ${node.getText()}`, fix));
return;
}
// We have a single quoted string outside a localize function name.
if (!doubleQuoted && !this.signatures[functionName]) {
return;
}
// We have a string that is a direct argument into the localize call.
const keyArg = callInfo && callInfo.argIndex === this.keyIndex
? callInfo.callExpression.arguments[this.keyIndex]
: null;
if (keyArg) {
if (isStringLiteral(keyArg)) {
this.recordKey(keyArg, this.messageIndex && callInfo ? callInfo.callExpression.arguments[this.messageIndex] : undefined);
}
else if (isObjectLiteral(keyArg)) {
for (const property of keyArg.properties) {
if (isPropertyAssignment(property)) {
const name = property.name.getText();
if (name === 'key') {
const initializer = property.initializer;
if (isStringLiteral(initializer)) {
this.recordKey(initializer, this.messageIndex && callInfo ? callInfo.callExpression.arguments[this.messageIndex] : undefined);
}
break;
}
}
}
}
}
const messageArg = callInfo.callExpression.arguments[this.messageIndex];
if (messageArg && messageArg.kind !== ts.SyntaxKind.StringLiteral) {
this.addFailure(this.createFailure(messageArg.getStart(), messageArg.getWidth(), `Message argument to '${callInfo.callExpression.expression.getText()}' must be a string literal.`));
return;
}
}
recordKey(keyNode, messageNode) {
const text = keyNode.getText();
// We have an empty key
if (text.match(/(['"]) *\1/)) {
if (messageNode) {
this.addFailureAtNode(keyNode, `Key is empty for message: ${messageNode.getText()}`);
}
else {
this.addFailureAtNode(keyNode, `Key is empty.`);
}
return;
}
let occurrences = this.usedKeys[text];
if (!occurrences) {
occurrences = [];
this.usedKeys[text] = occurrences;
}
if (messageNode) {
if (occurrences.some(pair => pair.message ? pair.message.getText() === messageNode.getText() : false)) {
return;
}
}
occurrences.push({ key: keyNode, message: messageNode });
}
findDescribingParent(node) {
let parent;
while ((parent = node.parent)) {
const kind = parent.kind;
if (kind === ts.SyntaxKind.CallExpression) {
const callExpression = parent;
return { callInfo: { callExpression: callExpression, argIndex: callExpression.arguments.indexOf(node) } };
}
else if (kind === ts.SyntaxKind.ImportEqualsDeclaration || kind === ts.SyntaxKind.ImportDeclaration || kind === ts.SyntaxKind.ExportDeclaration) {
return { isImport: true };
}
else if (kind === ts.SyntaxKind.VariableDeclaration || kind === ts.SyntaxKind.FunctionDeclaration || kind === ts.SyntaxKind.PropertyDeclaration
|| kind === ts.SyntaxKind.MethodDeclaration || kind === ts.SyntaxKind.VariableDeclarationList || kind === ts.SyntaxKind.InterfaceDeclaration
|| kind === ts.SyntaxKind.ClassDeclaration || kind === ts.SyntaxKind.EnumDeclaration || kind === ts.SyntaxKind.ModuleDeclaration
|| kind === ts.SyntaxKind.TypeAliasDeclaration || kind === ts.SyntaxKind.SourceFile) {
return null;
}
node = parent;
}
return null;
}
}
NoUnexternalizedStringsRuleWalker.ImportFailureMessage = 'Do not use double quotes for imports.';
NoUnexternalizedStringsRuleWalker.DOUBLE_QUOTE = '"';
NoUnexternalizedStringsRuleWalker.IDENTIFIER = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/;

View File

@@ -1,222 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
/**
* Implementation of the no-unexternalized-strings rule.
*/
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
if (/\.d.ts$/.test(sourceFile.fileName)) {
return [];
}
return this.applyWithWalker(new NoUnexternalizedStringsRuleWalker(sourceFile, this.getOptions()));
}
}
interface Map<V> {
[key: string]: V;
}
interface UnexternalizedStringsOptions {
signatures?: string[];
messageIndex?: number;
keyIndex?: number;
ignores?: string[];
}
function isStringLiteral(node: ts.Node): node is ts.StringLiteral {
return node && node.kind === ts.SyntaxKind.StringLiteral;
}
function isObjectLiteral(node: ts.Node): node is ts.ObjectLiteralExpression {
return node && node.kind === ts.SyntaxKind.ObjectLiteralExpression;
}
function isPropertyAssignment(node: ts.Node): node is ts.PropertyAssignment {
return node && node.kind === ts.SyntaxKind.PropertyAssignment;
}
interface KeyMessagePair {
key: ts.StringLiteral;
message: ts.Node | undefined;
}
class NoUnexternalizedStringsRuleWalker extends Lint.RuleWalker {
private static ImportFailureMessage = 'Do not use double quotes for imports.';
private static DOUBLE_QUOTE: string = '"';
private signatures: Map<boolean>;
private messageIndex: number | undefined;
private keyIndex: number | undefined;
private ignores: Map<boolean>;
private usedKeys: Map<KeyMessagePair[]>;
constructor(file: ts.SourceFile, opts: Lint.IOptions) {
super(file, opts);
this.signatures = Object.create(null);
this.ignores = Object.create(null);
this.messageIndex = undefined;
this.keyIndex = undefined;
this.usedKeys = Object.create(null);
const options: any[] = this.getOptions();
const first: UnexternalizedStringsOptions = options && options.length > 0 ? options[0] : null;
if (first) {
if (Array.isArray(first.signatures)) {
first.signatures.forEach((signature: string) => this.signatures[signature] = true);
}
if (Array.isArray(first.ignores)) {
first.ignores.forEach((ignore: string) => this.ignores[ignore] = true);
}
if (typeof first.messageIndex !== 'undefined') {
this.messageIndex = first.messageIndex;
}
if (typeof first.keyIndex !== 'undefined') {
this.keyIndex = first.keyIndex;
}
}
}
private static IDENTIFIER = /^[_a-zA-Z0-9][ .\-_a-zA-Z0-9]*$/;
protected visitSourceFile(node: ts.SourceFile): void {
super.visitSourceFile(node);
Object.keys(this.usedKeys).forEach(key => {
// Keys are quoted.
let identifier = key.substr(1, key.length - 2);
if (!NoUnexternalizedStringsRuleWalker.IDENTIFIER.test(identifier)) {
let occurrence = this.usedKeys[key][0];
this.addFailure(this.createFailure(occurrence.key.getStart(), occurrence.key.getWidth(), `The key ${occurrence.key.getText()} doesn't conform to a valid localize identifier`));
}
const occurrences = this.usedKeys[key];
if (occurrences.length > 1) {
occurrences.forEach(occurrence => {
this.addFailure((this.createFailure(occurrence.key.getStart(), occurrence.key.getWidth(), `Duplicate key ${occurrence.key.getText()} with different message value.`)));
});
}
});
}
protected visitStringLiteral(node: ts.StringLiteral): void {
this.checkStringLiteral(node);
super.visitStringLiteral(node);
}
private checkStringLiteral(node: ts.StringLiteral): void {
const text = node.getText();
const doubleQuoted = text.length >= 2 && text[0] === NoUnexternalizedStringsRuleWalker.DOUBLE_QUOTE && text[text.length - 1] === NoUnexternalizedStringsRuleWalker.DOUBLE_QUOTE;
const info = this.findDescribingParent(node);
// Ignore strings in import and export nodes.
if (info && info.isImport && doubleQuoted) {
const fix = [
Lint.Replacement.replaceFromTo(node.getStart(), 1, '\''),
Lint.Replacement.replaceFromTo(node.getStart() + text.length - 1, 1, '\''),
];
this.addFailureAtNode(
node,
NoUnexternalizedStringsRuleWalker.ImportFailureMessage,
fix
);
return;
}
const callInfo = info ? info.callInfo : null;
const functionName = callInfo ? callInfo.callExpression.expression.getText() : null;
if (functionName && this.ignores[functionName]) {
return;
}
if (doubleQuoted && (!callInfo || callInfo.argIndex === -1 || !this.signatures[functionName!])) {
const s = node.getText();
const fix = [
Lint.Replacement.replaceFromTo(node.getStart(), node.getWidth(), `nls.localize('KEY-${s.substring(1, s.length - 1)}', ${s})`),
];
this.addFailure(this.createFailure(node.getStart(), node.getWidth(), `Unexternalized string found: ${node.getText()}`, fix));
return;
}
// We have a single quoted string outside a localize function name.
if (!doubleQuoted && !this.signatures[functionName!]) {
return;
}
// We have a string that is a direct argument into the localize call.
const keyArg: ts.Expression | null = callInfo && callInfo.argIndex === this.keyIndex
? callInfo.callExpression.arguments[this.keyIndex]
: null;
if (keyArg) {
if (isStringLiteral(keyArg)) {
this.recordKey(keyArg, this.messageIndex && callInfo ? callInfo.callExpression.arguments[this.messageIndex] : undefined);
} else if (isObjectLiteral(keyArg)) {
for (const property of keyArg.properties) {
if (isPropertyAssignment(property)) {
const name = property.name.getText();
if (name === 'key') {
const initializer = property.initializer;
if (isStringLiteral(initializer)) {
this.recordKey(initializer, this.messageIndex && callInfo ? callInfo.callExpression.arguments[this.messageIndex] : undefined);
}
break;
}
}
}
}
}
const messageArg = callInfo!.callExpression.arguments[this.messageIndex!];
if (messageArg && messageArg.kind !== ts.SyntaxKind.StringLiteral) {
this.addFailure(this.createFailure(
messageArg.getStart(), messageArg.getWidth(),
`Message argument to '${callInfo!.callExpression.expression.getText()}' must be a string literal.`));
return;
}
}
private recordKey(keyNode: ts.StringLiteral, messageNode: ts.Node | undefined) {
const text = keyNode.getText();
// We have an empty key
if (text.match(/(['"]) *\1/)) {
if (messageNode) {
this.addFailureAtNode(keyNode, `Key is empty for message: ${messageNode.getText()}`);
} else {
this.addFailureAtNode(keyNode, `Key is empty.`);
}
return;
}
let occurrences: KeyMessagePair[] = this.usedKeys[text];
if (!occurrences) {
occurrences = [];
this.usedKeys[text] = occurrences;
}
if (messageNode) {
if (occurrences.some(pair => pair.message ? pair.message.getText() === messageNode.getText() : false)) {
return;
}
}
occurrences.push({ key: keyNode, message: messageNode });
}
private findDescribingParent(node: ts.Node): { callInfo?: { callExpression: ts.CallExpression, argIndex: number }, isImport?: boolean; } | null {
let parent: ts.Node;
while ((parent = node.parent)) {
const kind = parent.kind;
if (kind === ts.SyntaxKind.CallExpression) {
const callExpression = parent as ts.CallExpression;
return { callInfo: { callExpression: callExpression, argIndex: callExpression.arguments.indexOf(<any>node) } };
} else if (kind === ts.SyntaxKind.ImportEqualsDeclaration || kind === ts.SyntaxKind.ImportDeclaration || kind === ts.SyntaxKind.ExportDeclaration) {
return { isImport: true };
} else if (kind === ts.SyntaxKind.VariableDeclaration || kind === ts.SyntaxKind.FunctionDeclaration || kind === ts.SyntaxKind.PropertyDeclaration
|| kind === ts.SyntaxKind.MethodDeclaration || kind === ts.SyntaxKind.VariableDeclarationList || kind === ts.SyntaxKind.InterfaceDeclaration
|| kind === ts.SyntaxKind.ClassDeclaration || kind === ts.SyntaxKind.EnumDeclaration || kind === ts.SyntaxKind.ModuleDeclaration
|| kind === ts.SyntaxKind.TypeAliasDeclaration || kind === ts.SyntaxKind.SourceFile) {
return null;
}
node = parent;
}
return null;
}
}

View File

@@ -1,28 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const Lint = require("tslint");
class Rule extends Lint.Rules.TypedRule {
applyWithProgram(sourceFile, program) {
if (program.getCompilerOptions().alwaysStrict) {
return this.applyWithWalker(new NoUselessStrictRuleWalker(sourceFile, this.getOptions()));
}
return [];
}
}
exports.Rule = Rule;
class NoUselessStrictRuleWalker extends Lint.RuleWalker {
visitStringLiteral(node) {
this.checkStringLiteral(node);
super.visitStringLiteral(node);
}
checkStringLiteral(node) {
const text = node.getText();
if (text === '\'use strict\'' || text === '"use strict"') {
this.addFailureAtNode(node, 'use strict directive is unnecessary');
}
}
}

View File

@@ -1,30 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as Lint from 'tslint';
import * as ts from 'typescript';
export class Rule extends Lint.Rules.TypedRule {
public applyWithProgram(sourceFile: ts.SourceFile, program: ts.Program): Lint.RuleFailure[] {
if (program.getCompilerOptions().alwaysStrict) {
return this.applyWithWalker(new NoUselessStrictRuleWalker(sourceFile, this.getOptions()));
}
return [];
}
}
class NoUselessStrictRuleWalker extends Lint.RuleWalker {
protected visitStringLiteral(node: ts.StringLiteral): void {
this.checkStringLiteral(node);
super.visitStringLiteral(node);
}
private checkStringLiteral(node: ts.StringLiteral): void {
const text = node.getText();
if (text === '\'use strict\'' || text === '"use strict"') {
this.addFailureAtNode(node, 'use strict directive is unnecessary');
}
}
}

View File

@@ -1,62 +0,0 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
const Lint = require("tslint");
const fs = require("fs");
class Rule extends Lint.Rules.AbstractRule {
apply(sourceFile) {
return this.applyWithWalker(new TranslationRemindRuleWalker(sourceFile, this.getOptions()));
}
}
exports.Rule = Rule;
class TranslationRemindRuleWalker extends Lint.RuleWalker {
constructor(file, opts) {
super(file, opts);
}
visitImportDeclaration(node) {
const declaration = node.moduleSpecifier.getText();
if (declaration !== `'${TranslationRemindRuleWalker.NLS_MODULE}'`) {
return;
}
this.visitImportLikeDeclaration(node);
}
visitImportEqualsDeclaration(node) {
const reference = node.moduleReference.getText();
if (reference !== `require('${TranslationRemindRuleWalker.NLS_MODULE}')`) {
return;
}
this.visitImportLikeDeclaration(node);
}
visitImportLikeDeclaration(node) {
const currentFile = node.getSourceFile().fileName;
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 = fs.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) => {
if (existingResource.name === resource) {
resourceDefined = true;
return;
}
});
if (!resourceDefined) {
this.addFailureAtNode(node, `Please add '${resource}' to ./build/lib/i18n.resources.json file to use translations here.`);
}
}
}
TranslationRemindRuleWalker.NLS_MODULE = 'vs/nls';

View File

@@ -1,73 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as ts from 'typescript';
import * as Lint from 'tslint';
import * as fs from 'fs';
export class Rule extends Lint.Rules.AbstractRule {
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
return this.applyWithWalker(new TranslationRemindRuleWalker(sourceFile, this.getOptions()));
}
}
class TranslationRemindRuleWalker extends Lint.RuleWalker {
private static NLS_MODULE: string = 'vs/nls';
constructor(file: ts.SourceFile, opts: Lint.IOptions) {
super(file, opts);
}
protected visitImportDeclaration(node: ts.ImportDeclaration): void {
const declaration = node.moduleSpecifier.getText();
if (declaration !== `'${TranslationRemindRuleWalker.NLS_MODULE}'`) {
return;
}
this.visitImportLikeDeclaration(node);
}
protected visitImportEqualsDeclaration(node: ts.ImportEqualsDeclaration): void {
const reference = node.moduleReference.getText();
if (reference !== `require('${TranslationRemindRuleWalker.NLS_MODULE}')`) {
return;
}
this.visitImportLikeDeclaration(node);
}
private visitImportLikeDeclaration(node: ts.ImportDeclaration | ts.ImportEqualsDeclaration) {
const currentFile = node.getSourceFile().fileName;
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 = fs.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) {
this.addFailureAtNode(node, `Please add '${resource}' to ./build/lib/i18n.resources.json file to use translations here.`);
}
}
}